Swift Codable을 Property List와 함께 사용하기
2017.08.30
#Swift • #Codable • #Development
by kharrison, translated by pilgwon
Swift 4와 iOS 11은 우리에게 외부 포맷을 타입으로 또는 반대로 변환시키는 방법으로 Codable
프로토콜을 가져왔습니다. 가장 유명한 포맷은 JSON일 수 있고 또한 과거의 Cocoa 프로퍼티 리스트에서도 잘 작동합니다.
NSDictionary와 NSArray
지난 주에 제가 커스텀 폰트와 유동적 크기번역에 관한 글을 쓰고 있을 때, 저는 Cocoa 프로퍼티 리스트의 설정 딕셔너리 파일을 읽어오고 싶었습니다. 과거에는 Objetive-C에서 이렇게 썼었습니다:
NSURL *settingsURL = ... // location of plist file
NSDictionary *settings = [NSDictionary dictionaryWithContentsOfURL:settingsURL];
딕셔너리를 프로퍼티 리스트로 다시 작성하려면 이렇게 썼습니다:
[settings writeToURL:settingsURL atomically:YES];
NSArray
를 읽고 쓰기 위한 비슷한 API도 존재합니다. 두 API 모두 약간의 못생긴 캐스팅(예. NSDictionary
와 Dictionary
, NSArray
와 Array
)을 사용하면 Swift에서도 유용합니다:
let settingsURL: URL = ... // location of plist file
let settings = NSDictionary(contentsOf: settingsURL) as? Dictionary<String, AnyObject>
딕셔너리를 파일로 작성하려면 NSDictionary
로 캐스팅을 해야합니다:
(settings as NSDictionary).write(to: settingsURL, atomically: true)
이 코드들은 작동은 하지만 이러한 캐스팅들과 AnyObject
를 쓰는 것이 Swift를 쓰고 있다고 느껴지지 않았습니다.
프로퍼티 리스트에 Codable 사용하기
Codable
프로토콜은 iOS 11의 Swift에 나온 새로운 프로토콜이고 JSON과 같은 외부 포맷과 타입을 서로 변환하는데에 사용합니다. 그것은 또한 프로퍼티 리스트에도 잘 작동합니다. 미래에 Apple은 오래된 NSDictionary
와 NSArray
를 더 이상 사용되지 않게 처리(deprecated) 할 것이라는게 사실로 밝혀졌습니다.
프로퍼티 리스트 읽기
예를 들어, 불리언과 문자열, 정수를 가지고 있는 딕셔너리인 프로퍼티 리스트가 있다고 해봅시다:
이 포맷에 맞는 구조를 정의하고 Codable
프로토콜을 적용해봅시다:
struct MySettings: Codable {
var someFlag: Bool
var someString: String
var someInt: Int
}
참고하기:
- 프로퍼티의 이름은 프로퍼티 리스트의 아이템의 키 이름과 동일합니다.
Codable
은Decodable & Encodable
의 약자입니다. 그래서 당신이 프로퍼티 리스트를 읽고 싶다면Decodable
만 적용하면 됩니다.- 프로퍼티 리스트는 딕셔너리, 배열, 문자열, 숫자, 날짜, 바이너리 데이터 그리고 불리언 값을 가질 수 있습니다.
프로퍼티 리스트를 읽고 해석하기 위해서는 PropertyListDecoder
객체를 만들고 decode
메소드를 사용해야 합니다:
let settingsURL: URL = ... // location of plist file
var settings: MySettings?
if let data = try? Data(contentsOf: settingsURL) {
let decoder = PropertyListDecoder()
settings = try? decoder.decode(MySettings.self, from: data)
}
만약 에러를 관리하고 싶다면 do…catch 블록을 쓰세요:
do {
let data = try Data(contentsOf: settingsURL)
let decoder = PropertyListDecoder()
settings = try decoder.decode(MySettings.self, from: data)
} catch {
// Handle error
print(error)
}
만약 프로퍼티 리스트 파일의 최상위에 딕셔너리 대신에 배열이 있다면:
typealias Settings = [MySettings]
var settings: Settings?
...
settings = try decoder.decode(Settings.self, from: data)
프로퍼티 리스트에 있는 이름과 다른 이름을 쓰고 싶다면 CodingKeys
enum을 추가하면 됩니다. 아래 예는 someInt
대신에 id
를 쓰는 경우입니다.
struct MySettings: Codable {
var someFlag: Bool
var someString: String
var id: Int
private enum CodingKeys: String, CodingKey {
case someFlag
case someString
case id = "someInt"
}
}
참 쉽죠. 저번주의 ScaledFont 프로젝트를 보기 위한 딕셔너리안의 딕셔너리에 대한 더 복잡한 예제입니다.
프로퍼티 리스트 작성하기
프로퍼티 리스트를 작성하거나 인코딩하는 것은 쉽습니다. PropertyListEncoder
를 만들고, 출력 포맷을 정하고 encode 메소드를 사용합니다:
let someSettings = MySettings(someFlag: true, someString: "Apple", someInt: 42)
let encoder = PropertyListEncoder()
encoder.outputFormat = .xml
do {
let data = try encoder.encode(someSettings)
try data.write(to: settingsURL)
} catch {
// Handle error
print(error)
}
참고:
outputFormat
은.binary
,.openStep
또는.xml
이 될 수 있습니다.
Objective-C
만약 당신이 프로퍼티 리스트를 읽고 쓸 때 Objective-C를 쓴다면 Apple이 미래에 NSDictionary
와 NSArray
에 관련된 아래의 메소드들을 사용 불가하게 처리(deprecated)할 수 있으니 조심하세요:
+ dictionaryWithContentsOfFile:
+ dictionaryWithContentsOfURL:
- initWithContentsOfFile:
- initWithContentsOfURL:
- writeToFile:atomically:
- writeToURL:atomically:(BOOL)atomically
Foundation은 iOS 11에서 아래와 같이 에러 파라미터가 포함된 메소드가 추가됐습니다:
+ dictionaryWithContentsOfURL:error:
- dictionaryWithContentsOfURL:error:
- writeToURL:error:
프로퍼티 읽는 작업을 iOS 11에 대응하려면 아래와 같이 해야합니다:
if (@available(iOS 11.0, *)) {
NSError *error = nil;
settings = [NSDictionary dictionaryWithContentsOfURL:settingsURL error:&error];
} else {
// fallback to old method
settings = [NSDictionary dictionaryWithContentsOfURL:settingsURL];
}
프로퍼티 쓰는 작업을 iOS 11에 대응하려면 아래와 같이 해야합니다:
if (@available(iOS 11.0, *)) {
NSError *error = nil;
[settings writeToURL:settingsURL error:&error];
// Handle error
} else {
// fallback to old method
BOOL success = [settings writeToURL:saveURL atomically:YES];
// Handle error
}