나만의 공부 노트
13강 정리 본문
학습 내용
Persistent
데이터를 저장하는 방법으로
UserDefaults(Property List)
Archiving and Codable
Filesystem
Core Data
Cloud Kit
UIDocument
UIDocumentBrowserViewController
가 있습니다.
UserDefaults
UserDefaults는 마치 딕셔너리처럼 키와 밸류를 통해 값들을 저장할 수 있습니다.
가볍지만 제한적인 데이터 베이스이기 때문에 셋팅과 같은 값들을 저장하는데 사용하면 됩니다.
UserDefaults에는 Property List 데이터만을 저장할 수 있습니다.
* Property List는 정확히 타입이 아닌 개념의 의미입니다.
- 문자열 : <string>
- 숫자 : <integer>
- 실수 : <real>
- 배열 : <array>
- 딕셔너리 : <dict>
- 날짜, Base64* : <data>
등의 타입만을 제한적으로 사용할 수 있고, 특히 .plist가 프로퍼티 리스트로 만들어졌다는 특징이 있습니다.
이를 사용하는 이유는, 객체 직렬화(객체를 바이트 단위로 변환)를 사용하고 있기 때문입니다.
XML 포맷으로 저장(그렇기 때문에 key-value형식)되며, 데이터 추상화를 사용하기 때문에 타입 형변환을 시켜주어야 합니다.
UserDefault에 대한 인스턴스를 만들수도 있지만 통상 잘 만들지는 않고 static var인 standard를 주로 사용합니다. UserDefaults.standard.set(value, forKey: "keyString")으로 저장하고, UserDefaults.standard.<Type>(forKey: "keyString")으로 꺼내어 사용합니다.
저장하는 것은 통상 자동으로 저장이 되지만, 디버깅할 때 등 필요에 의해 defaults.synchronize()를 통해 강제 할 수도 있습니다.
이처럼 UserDefaults를 사용하면 손쉽게 저장이 가능하지만, 가벼운 데이터나 또는 속성 리스트인 객체만 사용할 수 있는 단점이 있습니다.
Archiving(아카이빙)
User defaults는 가벼운 데이터 및 프로퍼티 리스트만 저장할 수 있는 단점이 있습니다.
이를 해결할 수 있는 방법으로 Archiving(아카이빙)이 있습니다.
아카이빙에는 크게 NSCoder(old), Codable(new)의 2가지 방법이 있습니다.
1. 스토리보드가 사용하는 방법의 NSCoder mechanism 아카이빙 = old
NSCoder는 모든 객체를 object graph(객체 그래프 = 객체 안에도 다른 객체를 참조하는 변수가 있을 수 있고, 이러한 구조를 나타내는 그래프)형태로 저장합니다.
참조된 객체들 또한 저장해야하므로 NSCoder를 채택해야 합니다.(encode 함수는 객체 그래프 내에 있는 모든 객체에 호출됩니다.)
NSCoder를 채택하면 다음과 같은 2개의 함수를 구현해야합니다.
fun encode(with aCoder: NSCoder) {
aCoder.encode~(value, key) <- 를 통해서 저장할 수 있고(딕셔너리 형태로 저장합니다.)
}
required init(coder: NSCoder) {
var xxx = coder.edcode~(key) <- 를 통해서 값을 가져올 수 있습니다.
}
모든 객체 그래프 내의 NSCoder 구현을 마쳤다면, 아래 예제처럼
let data: Data = NSKeyedArchiver.archivedDataWithRootObject(object) <- 아카이브
let object: SomeClass = NSKeyedUnarchiver.unarchiveObjectWithData(data) as SomeClass <- 언아카이브
NSKeyedArchiver/NSKeyedUnarchiver를 사용해 Data화 시킬 수 있고, 이러한 Data는 User Defaults, fileSystem 등을 이용해서 저장하면 됩니다. (NSKeyedArchiver는 키를 이용한 저장방법이고, NSArchiver는 키를 이용하지 않고 순서대로 저장합니다.)
2. iOS11 이후 스위프트 언어를 이용하는 방법인 Codable mechanism 아카이빙 = new
Codable은 Decodable과 Encodable 프로토콜을 함께 가지는 프로토콜입니다.
일반적으로 NSCoder와 크게 다르지 않고(init, encode, 객체 그래프, 딕셔너리 형태 등), 모든 객체 그래프에 있는 객체들은 Codable 프로토콜을 준수해야합니다.
다른 점은 스위프트에서 구현한 것이기 때문에 standard 스위프트 타입(URL, CGFloat 같은 타입들)에 대해서 자동으로 지원해준다는 점입니다. (아닌 경우는 1번과 유사한 방식을 사용해야합니다.)
보통 JSON 또는 Property List 형태로 인코딩하며, JSON은 let jsonData: Data? = try? JSONEncoder().encode(object)와 같이 사용합니다.
JSON은 utf8 형태로 저장하고, 만약 날짜와 같은 데이터를 변환할 때 양식이 다르다면, JSONDecoder().dateDecodingStrategy에 값을 바꿔주면 됩니다.
JSON은 스위프트와 달리 변수에 카멜케이스를 따르지 않기 때문에 키값도 카멜케이스가 되지 않는 문제가 생깁니다
따라서 codable을 따르는 객체 안에 enum CodingKeys: String, CodingKey {}를 선언해준다면 원시값을 바탕으로 key값을 저장해줍니다.
만약 디코딩이 안되는 경우를 다루고 싶다면, init(from decoder: Decoder) throws {}을 구현하여, decoder.container(keyedBy: CodingKeys.self)를 통해 하나하나 값들을 꺼내올 수 있습니다.
File System
iOS, Mac(+ 리눅스)는 Unix 계열의 운영체제입니다.
iOS는 샌드박스라는 개념을 사용하는데, 앱에서 마치 자기 자신이 루트 디렉토리인 것처럼 인식합니다.
따라서, 다른 앱의 영역을 침범할 수 없고 자신의 sandbox 안에서만 읽고 쓰는 작업을 할 수 있습니다.
크게 3가지의 장점 " Security = 앱을 보호, Privacy = 데이터 보호, Cleanup = 앱을 지울 때 지울 영역 이 있습니다.
시작점으로 사용할 수 있는 디렉토리는
Application directory = 실행 가능하고 쓰기는 불가능한 자료의 디렉토리
Documents directory = 영구 보관이 가능하고 유저에 의해 생성되는 자료의 디렉토리(글로벌(?) 파일 앱을 통해 볼 수도 있습니다.)
Application Support dicrectory = 영구 보관이 가능하지만, 유저에게는 직접적으로 보이지 않는 자료의 디렉토리
Caches directory = 임시 저장소이며 iTunes 등에 백업되지 않는 자료의 디렉토리
등등 다양하고 더 많은 디렉토리가 존재하므로 알맞게 찾아서 사용하면 됩니다.
각각의 디렉토리 시작점을 찾는 법은 다음과 같습니다.
sandbox안에서 시작점 + 디렉토리들 + 데이터 + 확장자가 붙은 URL을 만들기 위해 아래의 함수를 사용합니다.
File System을 이용해서 데이터를 저장하고 싶다면, Data 구조를 이용하면 됩니다.
샌드박스 내에서 url을 구성하고 해당 url에 저장하면 됩니다.
FileManager
File System을 효율적으로 다루기 위해서 File Manager를 이용합니다. 다양한 기능(디렉토리를 수정, 추가, 삭제 등)을 지원하며, 가장 큰 장점이 여러 쓰레드에서 동시에 파일 매니저를 이용해도 하나의 쓰레드에서만 파일 매니저가 동작하기 때문에 thread safe합니다.
Core Data
(2016년 Stanford 강의를 참고하면 됩니다.)
코어 데이터는 데이터 베이스를 객체 지향적으로 접근할 수 있도록 도와주는 프레임워크입니다.
이를 가능하게 만들어주는 것은 Xcode안의 Visual Mapper라는 툴이 있는데, 이것이 마치 데이터 베이스를 객체 지향으로 접근하는 것처럼 보이게 만들기 때문입니다.
Visual Mapper는 객체와 데이터 베이스의 데이터가 어떻게 일치시킬지에 대한 방법을 찾아줍니다.(데이터베이스는 테이블을 가지고 있고 이를 객체처럼 바라본다면 해결이 가능합니다.)
*구조도
(설명은 그림 아래에..)
Persistent Container는 Model, Context, Store Coordinator를 한꺼번에 다룰 수 있도록 도와주는 컨테이너입니다.
*상세 구조도
Persistent Store는 저장소이며, xml, sqlite, binary, memory 형태 등 다양한 저장소를 사용할 수 있습니다.
이렇게 저장된 데이터를 객체 지향적으로 보일려면 Managed Object Model에 객체 구조를 정의해야합니다.
Managed Object Model은 마치 스키마라고도 볼 수도 있고, 데이터베이스의 스키마 정확히 일치하는 것이 아니기 때문에, Coordinator를 통해 매핑이 필요합니다.
Context는 transaction을 제공하며, 테이블 생성, 변경, 삭제 및 데이터 추가, 삭제 등을 수행할 수 있습니다.
마찬가지로 transcation이 마치 객체 지향적으로 작동하는 것처럼 보일려면 coordinator를 이용해 매핑을 해야합니다.
Context는 concurrency를 제공하기 때문에, 여러개의 Managed Object를 가질 순 있지만, thread safe 해야하므로 여러개의 Managed Object를 관리하는 Context가 있습니다.
따라서 NSManagedObjectContext를 통해 코어 데이터에 접근할 수 있습니다.
또는 Context를 UIDocument를 이용해 얻어올 수도 있습니다.(이 때 데이터 베이스를 마치 도큐먼트처럼 봅니다.)
NSManagedObjectContext는 데이터 베이스 기반이기 때문에 sql 에러, 저장 공간 부족 등 다양한 오류가 발생할 수 있는 가능성이 있기때문에 저장을 명시적으로 해주어야합니다.
하지만, UIManagedDocuments를 이용하면 auto save됩니다.(아래에서 다룹니다.)
쿼리문은 데이터 베이스의 sql과는 조금 다릅니다.
쿼리문은 NSFetchRequest를 통해 생성할 수 있고(데이터 베이스에서 from절 역할), 여기에 predicate(== where절 역할) 및 sortDescriptors(순서)를 설정해줍니다.
이후 데이터 베이스를 가르키는 컨텍스트에 쿼리문을 실행하면, 만족하는 엔티티를 모두 fetch해옵니다.
(데이터 베이스처럼 select 기능보단 전체를 가져오고 join 등의 기능이 없는 듯합니다.)
CloutKit
(마찬가지로 2016년 스탠포드 강의를 확인하시면 됩니다.)
데이터 베이스이지만 아주 간단한 쿼리만 수행할 수 있습니다.
또한 데이터 베이스 자체가 서버에 있기 때문에 네트워킹과 관련된 문제들을 잘 생각해야합니다.
클라우드 킷은 동적 스키마를 사용하기 때문에, 스키마를 미리 정의하지 않아도 사용이 가능합니다.
UIDocument
Persistent를 위한 프레임워크로, 사용자가 인식하기에 도큐먼트(디렉토리 아래의 개별 파일, UTI로 구성)로 느껴지도록 저장합니다.
UIDocument는 파일 시스템과 iCloud 등 전반에 사용이 가능합니다.(기존의 파일 시스템은 동기적으로 로컬 저장소에만 저장이 가능합니다. 파일 시스템과 iCloud를 좀 더 편리하게 이용할 수 있도록 만들어주는 툴이라고 생각하면 편할 것 같습니다.)
데이터가 큰 경우나, iCloud는 네트워크를 사용하기 때문에 비동기적으로 데이터를 가져오는 작업을 수행합니다.
아카이빙과 비슷하게 Data로 인코딩, 디코딩을 수행하며, 이를 미리 지정한 url에 저장합니다.
사용하기에 앞서 우선 도큐먼트가 저장될 url을 지정해야합니다.
파일 매니저를 통해 앱이 사용할 수 있는 url을 얻은 후, url을 구성합니다.
override func contents함수를 통해 모델을 Data 타입으로 변환 후 리턴합니다.
여기서 함수를 Any로 지정한 이유는, 여러 파일들을 구성한 후 해당 파일들을 감싸는 FileWrapper을 반환할 수도 있기 때문입니다.
override func load함수를 통해 Data를 모델로 변환 합니다.
url을 가지고 있는 도큐먼트를 열어서, 비동기적으로 성공하면 안의 모델 변수를 가져와 사용하면 됩니다.
자동 저장을 이용하거나, 강제적으로 save함수를 통해 저장할 수 있습니다.
save는 두 개의 인수를 받는데, 변화된 모델을 원래의 도큐먼트 자리가 아닌 다른 위치에 별도로 저장하고 싶을 때 이 url 인자를 통해서 다른 곳에 저장할 수 있습니다.
그렇기 때문에 해당 도큐먼트가 최초 저장 공간인지 후속 저장 공간인지 알고 있어야하며 이에 따라 for 인자에 알맞는 옵션을 선택하여야 합니다.
open을 이용해 도큐먼트를 열었다면, 사용이 끝난 후 close를 통해 도큐먼트를 닫아주어야 합니다.
여기서 도큐먼트의 상태에 따라 close가 실패할 수도 있습니다.
*normal, closed, savingError, editingDisable(사용자가 모델을 변경하고 있을때, 도큐먼트 상태를 이 상태로 만들어줄수도 있습니다) 등의 도큐먼트 상태가 있습니다.
도큐먼트에 기본 아이콘(or 썸네일)이 붙는데, 이를 변경해줄 수 있습니다.