데이터를 저장하기 위해서, storage layer가 MVC 아키텍처에 추가된다.
controller object를 통해 storage layer에 접근한다.
앱의 데이터를 저장하는 방법
앱이 시작되면 저장된 데이터를 가져와 사용하고, 앱의 데이터가 수정되거나 앱이 꺼질 때 데이터가 저장된다.
Codable
model object가 인코딩, 디코딩되기 위해선 반드시 Codable
protocol을 차용해야한다.
Codable
protocol을 차용하기 위해서 Decoder
와 Encoder
를 파라미터로 쓰는 init(from: Decoder)
, encode(to: Encoder)
method를 작성해야한다.
만약 object의 property가 이미 Codable
을 차용한 타입들인 경우, 위의 method들을 작성하지 않아도 된다.
// String, Date는 Codable을 차용한 Swift type이므로
// protocol의 요구 method들을 차용하지 않아도 된다.
struct Note: Codable {
let title: String
let text: String
let timestamp: Date
}
encode(_:)
Encoder
object
Data
object를 반환
let newNote = Note(title: "Grocery run", text: "Pick up mayonnaise, mustard, lettuce, tomato, and pickles.", timestamp: Date())
let propertyListEncoder = PropertyListEncoder()
// try? 사용을 통해 encode(_:)`가 에러를 던지는 대신 optional `Data`를 반환
if let encodedNote = try? propertyListEncoder.encode(newNote) {
// Data object에 저장된 bytes
print(encodedNote)
}
Data
- byte형식으로 데이터를 표현하는 Swift structure
- 파일로부터 읽고 쓰는 역할의 instance method 제공
decode(_:from:)
Decoder
object
parameter: Data
object
return: Codable
object instance
let propertyListDecoder = PropertyListDecoder()
// Note.self: 특정한 Note object가 아닌 실제 Note Swift type
if let decodedNote = try? propertyListDecoder.decode(Note.self, from: encodedNote) {
print(decodedNote)
}
많은 OS에서 앱은 디스크 어디에서나 파일들을 읽고 쓸 수 있는 권한을 가진다.
ex)
Numbers: 모든 폴더로부터 spreadsheet 열기 가능
Web browser: 모든 directory에 파일 쓰기 가능
이러한 행동은 불안정한 앱이 앱과 관련없는 중요한 데이터를 삭제할 수도 있는 본질적 위험을 가진다.
ex) 음악 파일을 수정하는 앱이 모든 사진을 삭제할 수 있음
iOS App은 sandbox model을 사용하여 프로그램에 그들이 생성하거나 접근을 요청받은 자원에 대한 접근 권한을 부여한다.
각 앱은 자신의 sandbox(생성, 수정, 삭제가 가능한 자신의 환경)를 가지고, sandbox 밖의 자원은 접근할 수 없다.
OS가 앱 바깥의 자원에 접근을 허락하더라도, app은 사용자로부터 명백한 허가를 받을 때만 가능하다.
앱은 데이터를 저장할 수 있는 몇몇개의 디렉토리를 가진다.
ex) Document directory: 앱과 관련된 정보를 저장하거나 수정할 수 있는 곳
디렉토리의 파일 경로는 중요한 보안 이슈를 막기 위해 앱이 메모리에 로딩될 때마다 바뀔 수 있다.
Foundation
framework는 디스크의 파일과 상호작용하는데 쓰이는 FileManager
class를 가진다.
FileManager
Foundation
framework에서 앱에 directory 접근 권한을 주고, 해당 directory의 파일을 읽고 쓸 수 있게하는 함수를 갖고있는 classurls(for:in:)
// .userDomainMask: 사용자의 앱과 관련된 모든 데이터를 갖고있는 home 폴더
// Document directory는 1개뿐이므로 반환값에서 첫번째 값만 가져온다.
// ~~~/Documents/
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
appendingPathComponent()
& appendingPathExtension()
// ~~~/Documents/notes_test.plist
let archiveURL = documentsDirectory.appendingPathComponent("notes_test").appendingPathExtension("plist")
write(to:options:)
Data
가 담긴 파일을 생성하는 throwing function// .noFileProtection: 덮어쓰기 허락
try? encodedNote?.write(to: archiveURL, options: .noFileProtection)
init(contentsOf:)
Data
object를 초기화하는 throwing initializerlet propertyListDecoder = PropertyListDecoder()
if let retrievedNoteData = try? Data(contentsOf: archiveURL),
let decodedNote = try? propertyListDecoder.decode(Note.self, from: retrievedNoteData) {
print(decodedNote)
}
let note1 = Note(title: "Note One",
text: "This is a sample note.", timestamp: Date())
let note2 = Note(title: "Note Two", text: "This is another sample
note.", timestamp: Date())
let note3 = Note(title: "Note Three", text: "This is yet another
sample note.", timestamp: Date())
let notes = [note1, note2, note3]
let documentsDirectory =
FileManager.default.urls(for: .documentDirectory,
in: .userDomainMask).first!
let archiveURL =
documentsDirectory.appendingPathComponent("notes_test")
.appendingPathExtension("plist")
let propertyListEncoder = PropertyListEncoder()
let encodedNotes = try? propertyListEncoder.encode(notes)
try? encodedNotes?.write(to: archiveURL,
options: .noFileProtection)
let propertyListDecoder = PropertyListDecoder()
if let retrievedNotesData = try? Data(contentsOf: archiveURL),
let decodedNotes = try?
propertyListDecoder.decode(Array<Note>.self,
from: retrievedNotesData) {
print(decodedNotes)
}
model object는 Codable
protocol을 차용해야 한다.
파일로부터 읽고 쓰는 역할을 model의 static method로 작성한다.
ex) saveToFile()
, loadFromFile()
model data를 추가, 제거, 수정할 때마다 file을 update한다.
loadFromFile()
앱이 시작되는 초반에 디스크로부터 데이터 로딩
AppDelegate
또는 first view controller의 viewDidLoad()
method 내부에 수행