Swift의 커스텀 타입을 직렬화하기

이 글은 https://developer.apple.com/documentation/foundation/archives_and_serialization/encoding_and_decoding_custom_types 의 내용을 일부 발췌, 의역한 것입니다.

프로그래밍 분야에서의 많은 작업은 데이터를 디스크에 저장하거나, 네트워크를 통해 전송하거나 API를 호출하기 위해 데이터를 제출하는 등의 일과 관련된다. 이 과정에서 데이터는 전송되는 동안에 적절한 형식으로 인코딩되고 다시 디코딩될 필요가 있다.

Swift의 커스텀 타입을 직렬화하기 더보기

Vision을 이용한 얼굴 인식 – 고급편

Vision을 이용해서 이미지 속에서 얼굴을 인식하는 방법을 살펴본 바 있는데, 당시 글에서 VNDetectFaceLandmarksRequest 를 사용했었다. 사실 얼굴이 들어있는 영역만 찾기 위해서는 VNDetectFaceRectsRequest 를 사용하는 것으로도 충분한데, face landmarks를 찾는다는 것은 얼굴의 주요 요소들 – 눈, 코, 입, 윤곽선 등-을 찾아낸다는 의미이다. 

이 글에서는 VNDetectFaceLandmarksRequest를 사용해서 실제로 얼굴의 각 요소를 추적하는 예제를 소개해 보겠다. 

Vision을 이용한 얼굴 인식 – 고급편 더보기

이니셜라이저 – Swift

Swift의 클래스와 구조체, enum 객체들은 사용하기 전에 반드시 초기화되어야 한다. 그러면 초기화(initialization)이란 무엇인가? 객체의 생성 자체를 초기화과정에 포함시키는 관점과 그렇지 않은 관점이 있지만, 여기서는 “객체를 만들어서 사용가능한 상태로 준비하는 일”이라고 보자. let foo = Foo() 와 같이 특정한 타입의 인스턴스를 생성하는 구문을 실행했을 때 저 아래(?)에서 벌어지는 과정은 다음과 같다.

이니셜라이저 – Swift 더보기

Swift4의 키패스 표현

키패스는 어떤 객체의 프로퍼티 혹은 프로퍼티의 프로퍼티 체인에 대해서 그 이름을 통해서 값을 찾아나가는 표현을 말한다. Objective-C에서 키패스는 키패스 경로를 나타내는 문자열을 사용해서 특정 객체의 속성을 액세스하기 때문에 컴파일 타임이 아닌 런타임에 액세스해야 할 프로퍼티를 결정하는 동적 기능으로 키밸류코딩과 키밸류 옵저빙에 사용된다. Swift2까지는 Swift 내에 키패스에 대한 기능이 별도로 마련되지 않았고, NSObject의 value(forKey:)setValue(_:forKey:)를 사용하면서 문자열을 그대로 사용했다.

문자열을 통해서 키패스를 사용하는 것은 편리할 수는 있으나, 컴파일 타임에서 오타에 의해 존재하지 않는 키패스를 참조하는 것을 체크할 방법이 없어서 디버깅이 곤란한 부분이 있었다. 이 부분을 개선하기 위해 Swift3에서 #keyPath() 문법 표현이 추가되었는데, 이 문법은 코딩 시점에는 컴파일러의 도움을 받아 올바른 키패스를 확인할 수 있고, #keyPath() 표현을 통해 해당 키패스값을 문자열로 안전하게 변환할 수 있었다.

하지만 키패스를 문자열로 치환하는 이와 같은 방법은 Swift의 디자인 관점에서는 몇 가지 한계를 갖는다. 키패스 자체는 프로퍼티를 찾아가는 경로만을 정의하므로 타입 정보를 잃고 그 결과가 Any가 되어버린다든지, 파싱이 느리고 NSObject 기반의 클래스에서만 사용할 수 있었다. Swift4에서는 이러한 단점을 보완하고 클래스외의 모든 Swift 타입에서 키패스를 통해서 프로퍼티를 참조할 수 있는 범용적인 키패스 문법(과 키패스를 위한 코어 타입)이 추가되었다.

Swift4의 키패스 문법

Swift4의 키패스 문법은 단순히 백슬래시(\)로 시작하는 키패스 값을 말한다. Objective-C와 달리 self 대신에 타입 이름을 쓰거나, 타입이 분명한 경우, 타입 이름을 생략하고 바로 . 으로 시작하는 키패스를 사용할 수 있다. 다음은 Swift Evolution의 새로운 키패스 제안서에 실린 예제이다.

class Person {
  var name: String
  var friends: [Person] = []
  var bestFriend: Person? = nil
  init(name: String) {
    self.name = name
  }
}

var han = Person(name: "Han Solo")
var luke = Person(name: "Luke Skywalker")
luke.friends.append(han)

// 키패스 객체를 생성한다. 
let firstFriendsNameKeyPath = \Person.friends[0].name
// 생성한 키패스를 사용해서 프로퍼티를 액세스한다.
let firstFriend = luke[keyPath: firstFriendsNameKeyPath] // "Han Solo"

// 항상 . 으로 시작해야 한다. 이는 배열의 요소 참조시에도 마찬가지이다.
luke.friends[keyPath: \.[0].name]
luke.friends[keyPath: \[Person].[0].name] 

// 옵셔널 프로퍼티는 ?를 붙여서 액세스해야 한다.
let bestFriendsNameKeyPath = \Person.bestFriend?.name
let bestFriendsName = luke[Keypath: bestFriendsNameKeyPath] // nil

키 패스 타입

Swift4의 키패스는 KeyPath라는 타입에 의해서 관리된다. 이 타입은 하위 속성을 참조하기 위해서 다른 키패스를 이어 붙이는 것이 가능하고, 또한 루트 타입과 키패스가 가리키는 속성의 타입을 그 인자로 가질 수 있다. 이 말은 위 예제에서와 같은 표현으로 키패스를 생성하는 경우, luke의 타입인 Personname 속성의 타입인 String 에 대한 정보가 키패스 내부에 내제된다는 것이다. 따라서 위 예제에서 bestFriend 변수의 타입은 String이 라는 점을 컴파일러는 알 수 있다.