본문 링크 (Original Link)

Swift Codable을 Property List와 함께 사용하기

2017.08.30

# • # • #

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 모두 약간의 못생긴 캐스팅(예. NSDictionaryDictionary, NSArrayArray)을 사용하면 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은 오래된 NSDictionaryNSArray를 더 이상 사용되지 않게 처리(deprecated) 할 것이라는게 사실로 밝혀졌습니다.

프로퍼티 리스트 읽기

예를 들어, 불리언과 문자열, 정수를 가지고 있는 딕셔너리인 프로퍼티 리스트가 있다고 해봅시다:

image1

이 포맷에 맞는 구조를 정의하고 Codable 프로토콜을 적용해봅시다:

struct MySettings: Codable {
    var someFlag: Bool
    var someString: String
    var someInt: Int
}

참고하기:

프로퍼티 리스트를 읽고 해석하기 위해서는 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)
}

참고:

Objective-C

만약 당신이 프로퍼티 리스트를 읽고 쓸 때 Objective-C를 쓴다면 Apple이 미래에 NSDictionaryNSArray에 관련된 아래의 메소드들을 사용 불가하게 처리(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
}

더 읽을 거리