오늘은 UserDefaults
에 알아보려고 한다.
얼마나 걸리려나..
(참고로 Object-C 에서는 NSUserDefaults 라고 함.)
공식 문서에서 UserDefaults에 대해 아주 잘 설명하고 있다.
앱을 실행하는 동안 지속적으로 키-값 쌍을 저장하는 사용자의 기본 데이터베이스에 대한 인터페이스입니다.
UserDefaults는 런타임 환경에서 동작하고, 앱이 실행되는 동안 기본 데이터베이스에 접근해 key-value
의 형태로 데이터를 기록하거나 가져온다.
우리가 iOS에 앱을 설치하면 앱이 실행되는 시점에 데이터를 저장할 수 있는 기본 데이터베이스가 생성된다.
기본 데이터베이스는 Property List를 기반으로 하며, .plist
파일에 xml 형식으로 데이터가 저장된다.
여기서 지난번에 공부한 SandBox와 연관이 있는데... 바로 기본 데이터베이스인 plist 파일이 SandBox 내의 Bundle Container에 저장된다!
모르는 사람은 [iOS] SandBox를 알아보자! 참고하기.
때문에 UserDefaults를 사용할 때는 대용량의 데이터나 사용자 정보같은 데이터 보다 자동로그인 여부, 아이디 저장, 알림 설정 여부 등과 같은 단일 데이터를 저장한다.
macOS의 경우 아래의 디렉토리를 살펴보자.
/Users/사용자 이름/Library/Preferences/
Library 폴더가 보이지 않는다면
shift + command + .
입력
해당 폴더에 들어가면 여러 plist 파일을 발견할 수 있다.
파일을 보면 UserDefaults를 통해 저장된 데이터 뿐만이 아니라, 인터페이스 빌더 등에서 사용하는 AutoSave 항목 등 앱의 정보 또한 저장되어 있는 것을 알 수 있다.
iOS의 경우, 아이폰 자체에서는 확인할 수 없다. 하지만 xcode를 활용하면 확인할 수 있다.
먼저 아이폰을 맥 기기와 연결한다.
그 다음 xcode를 실행하고 다음 경로로 이동한다.
Window > Devices and Simulators
Devices 탭을 보면 연결한 기기의 이름이 나타난다.
plist 파일이 보고싶은 앱을 선택한 뒤, 연두색 아이콘을 누르고 Download Container를 클릭한다.
이후, 다운로드한 xcappdata
파일을 우클릭해 패키지 내용 보기를 클릭한다.
Appdata > Library > Preferences
위 경로로 이동하면 plist 파일을 확인할 수 있다.
이제 직접 저장해보자.
싱글톤 인스턴스로, 공유된 기본 값 객체를 반환하는 함수이다.
let defaults = UserDefaults.stand
데이터를 저장할 때는 set
메서드를 사용한다.
defaults.set("value", forkey: "key")
이때, key 값은 항상 String 이어야 한다.
String 외에도 Float, Int, Bool, URL 등의 타입을 저장할 수 있다.
참고로 nil 값을 저장하면 해당 데이터를 지우는 것과 동일한 효과다. 필요없다면 nil 을 넣어버리자.
저장한 데이터를 꺼내올 때는 타입에 맞게 가져와야 한다.
let data1 = defaults.object(forkey: "key") as! String
let data2 = defaults.string(forkey: "key")
타입을 신경쓰지 않고 꺼낸 뒤 올바른 타입으로 타입캐스팅을 해주거나, 타입에 맞는 메서드를 사용한다.
기본 데이터베이스에 저장되어 있는 모든 key-value 쌍을 가져올 때는 dictionaryRepresentation()
메서드를 사용한다.
let defaults = UserDefaults.standard
for (key, value) in defaults.dictionaryRepresentation() {
print("\\(key): \\(value)")
}
_ = defaults.dictionaryRepresentation().map {
print("\\($0.key): \\($0.value)")
}
defaults.dictionaryRepresentation().forEach{
print("\\($0.key): \\($0.value)")
}
dictionaryRepresentation() 메서드만 사용하고 for문이던, map이던 불러오는 형식은 자기 마음이다.
기본 데이터베이스 내의 모든 key 또는 value만 보고 싶을 때는 이렇게 하자.
defaults.dictionaryRepresentation().keys
defaults.dictionaryRepresentation().values
앞에서 알아본 바로는 굉장히 다양한 타입을 저장할 수 있다. 하지만 구조체나 클래스의 인스턴스 등 사용자 정의 타입의 경우에는... 직접 바꾸어야 한다.
💡 구조체를 그냥 저장하면 안 되나요?
UserDefaults는 Data 형을 따르는데, 이 Data 타입은 Base64 인코딩 형식을 따른다. 즉, 저장할 객체는 모두 Base64 인코딩을 거쳐야 한다.
기본 타입인 Int, String 등은 UserDefaults를 사용할 때 내부적으로 적용되지만, 구조체 같은 경우에는 직접 변환해야 한다.
첫번째 방법은 바로 '그냥 바꿔 넣기'이다 ..ㅎㅎ
let data = self.diaryList.map {
[
"uuidString": $0.uuid,
"title": $0.title,
"contents": $0.contents,
]
}
let userDefaults = UserDefaults.standard
userDefaults.set(data, forKey: "diaryList")
이런 식으로 직접 매핑하여 넣는 방식이다!!
방법이라기에도 뭐함..
JSONEncoder/Decoder를 사용해 객체를 저장 가능한 바이너리 타입으로 인코딩/디코딩해서 사용하는 방법이다.
이번엔 정말 방법다운 방법이다.
struct Diary: Codable {
var uuid: String
var title: String
var contents: String
}
데이터를 넣은 뒤, JSONEncoder를 사용하여 데이터를 저장한다.
let diary = Diary(uuid: "uuid", title: "title", contnets: "contents")
let defaults = Userdefaults.standard
let encoder = JSONEncoder()
if let encodeData = try? encoder.encode(diary) {
defaults.set(encodeData, forkey: "diary)
}
데이터를 불러올 때는 JsonDecoder()를 사용한다.
let decoder = JSONDecoder()
if let data = defaults.object(forkey: "diary") as? Diary {
if let decodeData = try? decoder.decode(Diary.self, from: data) {
print(decodeData)
}
}
조금 깊게 알아보고 있어서 이것 저것 구글링 하다보니, 하루가 금새 떠나버렸다. 개꿀
그래도 UserDefaults 하나 만큼은 끝내주게 사용할 수 있을 것 같다.
이제 keychain, coredata, sqlite 공부하면 된다!!
많이 남았네..