본문 바로가기
Swift/TOPIC

Swift | UserDefaults는 어디에 저장될까? (+ PropertyWrapper)

by UDDT 2025. 7. 9.

 

   간단한 데이터를 저장하려고 할 때 가장 먼저 떠오르는 키워드는 UserDefaults일거 같습니다

  물론 CoreData에 저장해도 되겠지만,

  프로젝트를 만드는 시점부터 CoreData를 포함하여 생성하지 않았다면

  중간에 추가하는 과정이 순탄하지만은 않습니다

 

    UserDefaults에 간단한 사용법은

   데이터 저장을 꿈꾸는 초보 개발자들에게 한 줄기 빛과도 같습니다

// Save
UserDefaults.standard.set(value, forKey: key)

// Load
UserDefaults.standard.string(forKey: key)

 

 

    사용하는건 쉽지만, 그냥 모르고 사용할 수는 없습니다

   이 UserDefaults 대체 어디에 저장될까요?

App Sandbox

   혹시 앱을 사용하던 중 권한 요청 화면을 본 적이 있으신가요?

 

  갑자기 무슨 권한 얘기냐 하시겠지만,

 UserDefaults의 저장 위치를 이해하려면, 이 Sandbox System을 알아야 합니다

 

   App Sandbox는 권한을 통해 요청된 리소스에 대한

앱의 액세스를 제한하여 시스템 리소스와 사용자 데이터를 보호합니다

앱이 특정 하드웨어 또는 데이터에 접근하려면 권한을 요청해야하고,

사용자의 승인을 통해서만 해당 권한에 접근이 가능합니다

 

  외부에서 아무렇게나 사용자의 데이터를 바꾸면 안되니까요

 

 바로 이런 것들이 App Sandbox입니다

 

 

     App Sandbox가 없다면 모든 시스템과 데이터에 쉽게 접근할 수 있겠지만,

  App Sandbox가 있을 때는 필요한(권한이 승인된) 데이터만 접근할 수 있기 때문에 

  외부 공격으로부터 1차 방어선이 생기는 것이죠

 

 

    각각의 앱은 하나의 파일로 이루어진 것이 아니라 내부적으로 폴더링되어 구분되어 있습니다

   뿐만 아니라 기능과 역할에 따라 Container도 구분되어 있죠

 

 

    그렇다면 정말 그런지 확인해봐야겠습니다

   프로젝트로 들어가서 시뮬레이터 상의 앱 설치 경로를 받아옵니다

print(NSHomeDirectory())

  

...Developer/CoreSimulator/Devices/B16E05BC-36B5-4268-....

 

    뭐 대략 이런식으로 나올텐데 해당 경로로 들어가줍니다

 

   그러면 아까 본 이미지처럼,

   Documents / Library / SystemData / temp로 구분되어 저장되어 있는 걸 볼 수 있습니다

   각각의 디렉토리는 다음의 데이터를 저장하고 있습니다

 

  • Documents: 앱을 통해 생성한 문서나 데이터 등이 저장되는 공간
  • Library: 유저 데이터 파일 및 임시 파일을 제외한 모든 파일을 관리하는 공간
  • Temp: 임시 파일을 저장하는 공간(현재 앱을 실행하는 동안만 필요한 것들)

 UserDefaults의 저장

     본론입니다

 

     UserDefaults는 위 공간 중 어디에 저장될까요?

 

    "UserDefaults는 딕셔너리 형태로 저장된다"

    "Key를 통해서 value를 꺼낸다"

    "plist 형태로 저장된다" 등 많은 얘기를 들어보셨을텐데요

    정말 그럴까요?

 

     자, Library 디렉토리를 확인해봅시다

    Library - Preference 경로로 이동하면,

    plist 파일이 있고 해당 파일을 열어보면 저장한 데이터를 확인해볼 수 있습니다

 

 

     물론 실제 디바이스의 경우, 내부적으로 경로가 변경되기 때문에 확인하기는 어렵겠지만

    시뮬레이터를 통해 학습은 가능할 것 같습니다

+ PropertyWrapper

   UserDefaults를 사용하다보면 겪는 문제가 있습니다

    private func loadData() {
        level = UserDefaults.standard.integer(forKey: "level")
        foodCount = UserDefaults.standard.integer(forKey: "foodCount")
        waterCount = UserDefaults.standard.integer(forKey: "waterCount")
    }

    private func saveData() {
        UserDefaults.standard.set(level, forKey: "level")
        UserDefaults.standard.set(foodCount, forKey: "foodCount")
        UserDefaults.standard.set(waterCount, forKey: "waterCount")
    }

 

    사용할 때마다 동일한 형태의 코드를 작성해야하고, 재사용성이 떨어집니다

  이를 개선하기 위해 다음과 같이 UserDefaultsManager를 만들기도 합니다

import Foundation

final class UserDefaultsManager {
    static let shared = UserDefaultsManager()
    private let defaults = UserDefaults.standard

    private init() {}

    func loadData<T: Decodable>(with key: userDefaultsKey) throws -> T {
        guard let data = defaults.data(forKey: key.rawValue) else {
            throw UserDefaultsError.failDataFetch
        }
        do {
            let decodedData = try JSONDecoder().decode(T.self, from: data)
            return decodedData
        } catch {
            throw UserDefaultsError.failDecoding
        }
    }

    func saveData<T: Encodable>(key: userDefaultsKey, value: T) {
        if let encodedData = try? JSONEncoder().encode(value) {
            defaults.set(encodedData, forKey: key.rawValue)
        }
    }
}

extension UserDefaultsManager {
    enum userDefaultsKey: String {
        case currentLocation
        case locationHistory
    }
}

 

    위의 코드도 괜찮지만,

  PropertyWrapper로 간단하게 UserDefaults를 구현하는 방법을 가져와봤습니다

@propertyWrapper
struct UserDefault<T> {

    private let key: String
    private let defaultValue: T

    init(key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }

    var wrappedValue: T {
        get {
            return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.setValue(newValue, forKey: key)
        }
    }
}

 

   다음과 같이 UserDefault 구조체를 만들어줍니다

  이때, key와 defaultValue를 주입 받도록 해주었습니다

  여기서 wrappedValue는 Property wrapper를 선언할 때 포함해야하는 기본 값이에요

 

   사용할 때는 프로퍼티에 @UserDefault를 달아주면 됩니다

@UserDefault(key: "nickname", defaultValue: "대장")
var nickname: String

 

PropertyWrapper 리팩토링 전, 후

   - 리팩토링 전

import UIKit

final class ViewController: UIViewController {

    { ... }
    
    var nickname: String = "대장"
    var level: Int = 0
    var foodCount: Int = 0
    var waterCount: Int = 0

    override func viewDidLoad() {
        { ... }
        loadData()
        updateState()
    }

    private func loadData() {
        level = UserDefaults.standard.integer(forKey: "level")
        foodCount = UserDefaults.standard.integer(forKey: "foodCount")
        waterCount = UserDefaults.standard.integer(forKey: "waterCount")
    }

    private func saveData() {
        UserDefaults.standard.set(level, forKey: "level")
        UserDefaults.standard.set(foodCount, forKey: "foodCount")
        UserDefaults.standard.set(waterCount, forKey: "waterCount")
    }
    
    private func updateState() {
        level = calculateLevel(foodCount: foodCount,
                                   waterCount: waterCount)
        saveData()
    }
    
    { ... }
}

  

   - 리팩토링 후

import UIKit

final class ViewController: UIViewController {

    { ... }
    
    @UserDefault(key: "nickname", defaultValue: "대장")
    var nickname: String

    @UserDefault(key: "level", defaultValue: 0)
    var level: Int

    @UserDefault(key: "foodCount", defaultValue: 0)
    var foodCount: Int

    @UserDefault(key: "waterCount", defaultValue: 0)
    var waterCount: Int

    override func viewDidLoad() {
        { ... }
        updateState()
    }
    
    private func updateState() {
        level = calculateLevel(foodCount: foodCount,
                                   waterCount: waterCount)
    }
    
    { ... }
}

 

   propertyWrapper에서 정의된 get과 set 구문에서 알아서 처리해주기 때문에,

  이제부터는 loadData()와 saveData()는 필요가 없어졌습니다

 

  다음에는 propertyWrapper에 대해서 공부하고 밑에 달아둘게요!

 

 결론

   애플은 SandBox 시스템을 통해 공간을 구분하고,

  사용자는 권한을 부여하고 거부함으로써 외부로부터 자신의 데이터를 보호할 수 있습니다

  UserDefaults는 이러한 시스템 하에 내부 공간에 저장되고, Library - preference에 plist 형태로 저장됩니다

 

 Reference

   Base : 새싹 내부 비공개 강의자료

 

https://developer.apple.com/documentation/Security/app-sandbox

 

https://zeddios.tistory.com/432

 

iOS ) Apple의 Sandbox정책과 Files앱

안녕하세요 :) Zedd입니다.오늘은 Apple의 Sandbox정책과 Files앱에 대해서 알아봅시댜 Apple의 Sandbox정책과 Files앱 SandBox....제가 엄청ㅇㅇㅇㅇㅇ~~예전에 Sandbox에 대해서 딱 한번 언급한적이 있는데...바

zeddios.tistory.com

 

 

https://gyuios.tistory.com/229

 

iOS) Property Wrapper 로 User Defaults 리펙토링하기

내용 Property Wrapper 를 사용하여 UserDefaults 읽고 쓰고 삭제하는 매커니즘을 캡슐화 해보자. Swift-Evolution swift-evolution 에서 property wrappers 를 소개하면서 예시로 UserDefaults 의 매커니즘을 캡슐화하여

gyuios.tistory.com

 

 

최근댓글

최근글

skin by © 2024 ttuttak