1.7 Saving Data

Joohyun·2022년 5월 15일
0

Saving Data

  • 데이터를 저장하기 위해서, storage layer가 MVC 아키텍처에 추가된다.

  • controller object를 통해 storage layer에 접근한다.

  • 앱의 데이터를 저장하는 방법

    1. 디바이스 디스크의 파일에 아카이빙된 model data를 저장
    2. SQLite, Core Data와 같은 데이터 저장 툴 사용
  • 앱이 시작되면 저장된 데이터를 가져와 사용하고, 앱의 데이터가 수정되거나 앱이 꺼질 때 데이터가 저장된다.

Codable

  • model object가 인코딩, 디코딩되기 위해선 반드시 Codable protocol을 차용해야한다.

  • Codable protocol을 차용하기 위해서 DecoderEncoder를 파라미터로 쓰는 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
}

1. encode(_:)

  • Encoder object

    • 디스크에 저장하기 위한 데이터 형식으로 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 제공

2. decode(_:from:)

  • Decoder object

    • 인코딩된 데이터를 상응하는 model 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)
}

Sandboxing and Documents Directory

Operating System

  • 많은 OS에서 앱은 디스크 어디에서나 파일들을 읽고 쓸 수 있는 권한을 가진다.

    ex)
    Numbers: 모든 폴더로부터 spreadsheet 열기 가능
    Web browser: 모든 directory에 파일 쓰기 가능

  • 이러한 행동은 불안정한 앱이 앱과 관련없는 중요한 데이터를 삭제할 수도 있는 본질적 위험을 가진다.

    ex) 음악 파일을 수정하는 앱이 모든 사진을 삭제할 수 있음

iOS App: Sandboxing

  • iOS App은 sandbox model을 사용하여 프로그램에 그들이 생성하거나 접근을 요청받은 자원에 대한 접근 권한을 부여한다.

  • 각 앱은 자신의 sandbox(생성, 수정, 삭제가 가능한 자신의 환경)를 가지고, sandbox 밖의 자원은 접근할 수 없다.

  • OS가 앱 바깥의 자원에 접근을 허락하더라도, app은 사용자로부터 명백한 허가를 받을 때만 가능하다.

  • 앱은 데이터를 저장할 수 있는 몇몇개의 디렉토리를 가진다.

    ex) Document directory: 앱과 관련된 정보를 저장하거나 수정할 수 있는 곳

  • 디렉토리의 파일 경로는 중요한 보안 이슈를 막기 위해 앱이 메모리에 로딩될 때마다 바뀔 수 있다.

  • Foundation framework는 디스크의 파일과 상호작용하는데 쓰이는 FileManager class를 가진다.

FileManager

  • Foundation framework에서 앱에 directory 접근 권한을 주고, 해당 directory의 파일을 읽고 쓸 수 있게하는 함수를 갖고있는 class

1. urls(for:in:)

  • 요청받은 도메인의 특정 디렉토리에 대한 URL배열을 반환
// .userDomainMask: 사용자의 앱과 관련된 모든 데이터를 갖고있는 home 폴더
// Document directory는 1개뿐이므로 반환값에서 첫번째 값만 가져온다.
// ~~~/Documents/
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!

2. appendingPathComponent() & appendingPathExtension()

  • 디렉토리에 파일이름과 확장자를 덧붙여 full path 반환
// ~~~/Documents/notes_test.plist
let archiveURL = documentsDirectory.appendingPathComponent("notes_test").appendingPathExtension("plist")

3. write(to:options:)

  • 특정 URL에 인코딩된 Data가 담긴 파일을 생성하는 throwing function
// .noFileProtection: 덮어쓰기 허락
try? encodedNote?.write(to: archiveURL, options: .noFileProtection)

4. init(contentsOf:)

  • Data object를 초기화하는 throwing initializer
  • 데이터가 저장된 URL을 함께 사용하면 데이터를 디코딩할 수 있다.
let 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)
}

Translate into full Xcode project

  • model object는 Codable protocol을 차용해야 한다.

  • 파일로부터 읽고 쓰는 역할을 model의 static method로 작성한다.

    ex) saveToFile(), loadFromFile()

  • model data를 추가, 제거, 수정할 때마다 file을 update한다.

    • loadFromFile()

      • 앱이 시작되는 초반에 디스크로부터 데이터 로딩

      • AppDelegate 또는 first view controller의 viewDidLoad() method 내부에 수행

profile
Developer

0개의 댓글