본문 바로가기

iOS/CS193p - Developing Apps for iOS

[CS193p] Lecture 11 - Persistence에 대해서

728x90

2021 CS193P Lecture 11 : Error Handling Persistence 강의 중에서 persistence 관련 강의 내용을 정리한 것입니다.

 

# 데이터를 영구적으로 저장하는 방법

- 파일 시스템에 저장 : FileManager

- SQL DB에 저장 : CoreData

- iCloud에 저장

- Cloud db에 저장 : CloudKit

- 등등

- 설정값 같은 lightweight data : UserDefaults

 

해당 강의에서는 FileManager와 UserDefaults에 대해서 알아봅니다.

# File System

iOS의 file system은 UNIX 파일 시스템을 이용합니다.

앱에서 허용된 공간은 sandbox라고 합니다.

이 sandbox 내에서만 읽기와 쓰기가 가능합니다.

 

## sandbox의 목적

- Security : 애플리케이션에 손상을 줄 수 없음

- Privacy : 다른 애플리케이션이 해당 애플리케이션의 영역에 접근할 수 없음

- Cleanup : 애플리케이션을 지우면, 내용들도 함께 사라짐

- Backup : 특정 부분만 백업할 수 있음

 

## sandbox에는 무엇이 있는가?

- Application directory : 실행 가능한 파일들, assets.Xcassets에 있는 JPEG 파일. 읽기만 가능

- Document directory : 유저가 만들고 사용할 수 있는 공간

- Application Support directory : 유저가 직접 접근할 수 없는 공간

- Caches directory : 임시 파일을 보관

- Others

 

## sandbox directory에 접근하기

URL을 이용해서 접근이 가능하며, FileManager를 통해 URL을 얻을 수 있습니다.

let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
print(url)
// file:///Users/<user name>/Library/Developer/CoreSimulator/Devices/<device id>/data/Containers/Data/Application/<App id>/Documents/

.documentDirectory는 어떤 디렉터리에 접근할 것인지를 결정합니다.

FileManager.SearchPathDirectory Enum으로 정해져있습니다.

applicationDirectory도 있고 applicationSupportDirectory도 선택할 수 있습니다.

 

iOS에서는 userDomainMask만 활용합니다.

iOS에서는 해당 url이 무조건 한 개가 나옵니다. 그래서 .first로 첫 번째 값을 얻습니다.

 

이제 특정 디렉터리에 대한 URL을 만들었으며, URL의 appending 관련 메서드로 파일명이나 확장자를 추가할 수 있습니다.

let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
print(url?.appendingPathComponent("abc"))	// ~~~/abc
print(url?.appendingPathExtension("def"))	// ~~~.abc

 

## 파일을 읽고 쓰기

파일 읽기 : Data.init(contentsOf: URL, options: Data.ReadingOptions)

파일 쓰기 : Data.write(to url: URL, options: Data.WritingOptions)

Data structure의 메서드를 이용합니다.

 

# Codable

특정 struct를 file system에 저장하고 싶다면, Codable protocol을 구현하면 됩니다.

struct의 property들이 모두 Codable protocol을 구현해야 합니다.

많은 standard type들은 Codable을 구현하고 있다.(String, Bool, Int, Double, Optional, URL, Array, Dictionary 등)

 

## convert it to JSON

Codable을 구현한 struct를 JSON으로 변경할 수 있습니다.

encoding : struct를 JSON으로 변환하는 것

decoding : JSON을 struct로 변환하는 것

 

변환 과정과 그 데이터의 값들은 다음과 같이 확인할 수 있습니다.

struct MyType: Codable {
    var a: String
    var b: Int
}

///////////////////////

    func testCodable() {
        let obj = MyType(a: "myString", b: 23)
        print("obj : \(obj)")	// obj : MyType(a: "myString", b: 23)
        let jsonData: Data = try! JSONEncoder().encode(obj)
        print("jsonData : \(jsonData)")		// jsonData : 23 bytes
        let jsonString = String(data: jsonData, encoding: .utf8)
        print("jsonString : \(jsonString)")		// jsonString : Optional("{\"a\":\"myString\",\"b\":23}")
        let myType = try! JSONDecoder().decode(MyType.self, from: jsonData)
        print("myType : \(myType)")		// myType : MyType(a: "myString", b: 23)
    }

 

## decoding error

decoding시 발생하는 에러는 document에서 확인이 가능합니다.

https://developer.apple.com/documentation/swift/decodingerror

 

 

## CodingKey

CodingKey를 이용해서 JSON key를 변경할 수 있습니다.

struct MyType: Codable {
    var a: String
    var b: Int
    
    private enum CodingKeys : String, CodingKey {
        case a = "AAA"
        case b = "BBB"
    }
}

// jsonString : Optional("{\"AAA\":\"myString\",\"BBB\":23}")

 

 

# UserDefaults

key-value 데이터를 저장하는 db에 대한 인터페이스입니다.

 

## Property Lists

UserDefaults는 Property List라고 불리는 데이터만 저장할 수 있습니다.

Property List는 (String, Int, Bool, floating point, Date, Data, Array, Dictionary)의 조합입니다.

* 어떤 struct가 Codable을 구현한다면, Data 객체로 변경할 수 있고, Data 객체는 Property list이므로 UserDefaults에 저장이 가능합니다.

 

## Any Type

Any는 모든 타입을 나타낼 수 있습니다. 

 

## UserDefaults 사용하기

    func testUserDefaults() {
        UserDefaults.standard.set(1, forKey: "key.a")
        let a = UserDefaults.standard.integer(forKey: "key.a")
        print(a)	// 1
        
        
        let myArray = ["a","b"]
        UserDefaults.standard.set(myArray, forKey: "key.myArray")
        if let myArray = UserDefaults.standard.array(forKey: "key.myArray") as? [String] {
            print(myArray)	// ["a", "b"]
        }
    }

UserDefault.standard : 싱글턴으로 생성되어 있던 shared default object를 반환합니다.

set으로 데이터를 저장하고, 데이터를 받아올 때는 데이터 형식에 맞춰서 메서드를 사용합니다.

array의 경우는 Array<Any>를 반환하기 때문에 사용하고자 하는 데이터 형태에 맞춰 casting 하여 이용하는 것이 좋습니다.

 

 

# Reference

File manager 관련

https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html

https://baked-corn.tistory.com/98?category=718235 

 

user defaults 관련

https://developer.apple.com/documentation/foundation/userdefaults