Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- uiscrollview
- 애니메이션
- swift documentation
- MVVM
- Protocol
- Human interface guide
- RxCocoa
- UICollectionView
- SWIFT
- rxswift
- 리펙토링
- uitableview
- Xcode
- 리펙터링
- ribs
- Refactoring
- collectionview
- Observable
- 스위프트
- HIG
- 리팩토링
- tableView
- 클린 코드
- ios
- clean architecture
- Clean Code
- map
- combine
- swiftUI
- UITextView
Archives
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[iOS - swift] UserDefaults에 struct 형태 저장 방법 (“Attempt to insert non-property list object” 오류, UserDefaultsWrapper, UserDefaultManager, propertyWrapper, UserDefault) 본문
iOS 응용 (swift)
[iOS - swift] UserDefaults에 struct 형태 저장 방법 (“Attempt to insert non-property list object” 오류, UserDefaultsWrapper, UserDefaultManager, propertyWrapper, UserDefault)
jake-kim 2021. 9. 14. 23:35UserDefaults를 이해하기 위한 기본 지식
- Byte buffer: 연속적으로 할당된 raw bytes를 저장하는 역할
- random access가 가능하여 데이터를 key-value쌍으로 저장하고 로드할때 용이
- 보통 스위프트에서 메모리나 디스크에 객체의 정보를 저장할 때 ByteBuffer를 사용하여 저장
- Data: 메모리에서의 byte buffer
- 객체를 byte buffer형태로 취하게 할 수 있는 구조체
UserDefauls의 원리
- 저장: 요청 > struct 객체 > Data형 > 메모리, 디스크에 저장
- *아카이빙: 객체를 Data형과 같이 바이트형태로 변경하는 작업이며 객체를 메모리, 디스크에 저장할 수 있는 파일 형식으로 만드는 것
- 로드: 요청 > 메모리, 디스크에서 저장된 형태 탐색 > Data형 > struct 객체 > 획득
- *언아카이빙: 메모리, 디스크에 저장된 Data형태의 바이트형태를 스위프트의 struct 객체와 같은 형태로 변경하는 것
- NSCoding을 이용한 아카이빙, 언아카이빙 개념 참고
UserDefaults에 struct형
- 기본 타입인 Int, Double, String은 아카아빙, 언아카이빙이 내부적으로 UserDefaults를 사용할 때 적용이 되어서 바로 사용 가능하지만, struct같은 경우 아카이빙, 언아카이빙 작업이 별도로 필요
- 아카이빙, 언아카이빙을 하지 않으면 아래처럼 에러 발생 "Attempt to insert non-property list object"
- 아카이빙, 언아카이빙을 위해서 UserDefaults를 사용할 struct에 Codable 프로토콜 준수
struct Person: Codable {
let name: String
let age: Int
}
- 저장
- JSONEncoder를 이용하여 객체를 아카이빙: 객체를 Memory, Disk에 저장할 수 있는 Data형으로 변환 후 변환된 Data형으로 UserDefaults에 저장
let person = Person(name: "jake", age: 20)
let encoder = JSONEncoder()
/// encoded는 Data형
if let encoded = try? encoder.encode(person) {
UserDefaults.standard.setValue(encoded, forKey: "person")
}
- 로드
- decode 없이(언아카이빙) 로드 시 Data형태로 로드
struct Person: Codable {
let name: String
let age: Int
}
let person = Person(name: "jake", age: 20)
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(person) {
UserDefaults.standard.setValue(encoded, forKey: "person")
}
let savedPerson = UserDefaults.standard.object(forKey: "person")
print(savedPerson) // Optional(<7b226e61 6d65223a 226a616b 65222c22 61676522 3a32307d>)
- JSONDecoder를 이용 언아카이빙하여 로드
if let savedData = UserDefaults.standard.object(forKey: "person") as? Data {
let decoder = JSONDecoder()
if let savedObject = try? decoder.decode(Person.self, from: savedData) {
print(savedObject) // Person(name: "jake", age: 20)
}
}
propertyWrapper를 이용하여 아카이빙, 언아카이빙 내부적으로 구현
- propertyWrapper 개념 참고
- 아이디어
- propertyWrapper는 특정 프로퍼티에 관한 성격을 더하는 역할 - wrapping되어 computed property 내부인 get과 set을 정의
- static var로 UserDefaults에 접근하는 computedProperty를 만드는데, 이 프로퍼티에서 get할때는 언아카이빙을 하고, set할때는 아카이빙을 해서 구조체도 저장되도록 구현
- 프로퍼티 시그니처 예시) 사용할때 UserDefaultManager.personList으로 접근하여 set, get이 가능하고, UserDefaults의 key값은 "personList"이고 defaultValue는 nil로 초기화되어 사용할수 있는 @UserDefaultWrapper정의
struct UserDefaultsManager {
@UserDefaultWrapper(key: "personList", defaultValue: nil)
static var personList: [Person]?
@UserDefaultWrapper(key: "wordList", defaultValue: nil)
static var wordList: [Word]?
}
- UserDefaultWrapper 정의
@propertyWrapper
struct UserDefaultWrapper<T: Codable> {
private let key: String
private let defaultValue: T?
init(key: String, defaultValue: T?) {
self.key = key
self.defaultValue = defaultValue
}
wrappedValue: T? {
get {
// 언아카이빙 정의: JSONDecoder를 이용
}
set {
// 아카이빙 정의: JSONEnocder를 이용
}
}
}
- 아카이빙, 언아카이빙 구현
var wrappedValue: T? {
get {
if let savedData = UserDefaults.standard.object(forKey: key) as? Data {
let decoder = JSONDecoder()
if let lodedObejct = try? decoder.decode(T.self, from: savedData) {
return lodedObejct
}
}
return defaultValue
}
set {
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(newValue) {
UserDefaults.standard.setValue(encoded, forKey: key)
}
}
}
- 사용하는 쪽
struct Person: Codable {
let name: String
let age: Int
}
let person1 = Person(name: "jake", age: 20)
let person2 = Person(name: "Kim", age: 20)
UserDefaultsManager.personList = [person1, person2]
let savedPeople = UserDefaultsManager.personList
print(savedPeople)
* 전체 소스 코드: https://github.com/JK0369/UserDefaultsExample
* 참고
- byte buffer: https://apple.github.io/swift-nio/docs/current/NIO/Structs/ByteBuffer.html
- Data: https://developer.apple.com/documentation/foundation/data
'iOS 응용 (swift)' 카테고리의 다른 글
Comments