先日 Swift5 がリリースされました! ABI 安定(Application Binary Interface)、 Result 型の追加など色々ありましたが、その中で identity keypath という
KeyPath の発表がありました。
また3月に開催された try!Swift2019 でも KeyPath についてのセッションがあって気になっていたところでした。
今回はその KeyPath についていろいろ調べて見ました。
TL;DR
- Swift3で #KeyPath が追加
- KVC 、 KVO などのキー指定をコンパイル時にチェック可能になった
- Swift4で KeyPath が追加
- Swift3 #KeyPath 問題点の解決
- 違う要素を持つ Struct を抽象化できる
- KeyPath には種類がある
#KeyPath
- Swift3で利用可能
- KVC 、 KVO などのキー指定をコンパイル時にチェックできる(typo を防げる)
- コンパイル後、文字リテラルに置換される
そのため、 Swift3以降でも Swift2以前と同じく文字列でキーの指定が可能
class Animal: NSObject {
@objc dynamic var name: String
init(name: String) {
self.name = name
}
}
let animal = Animal(name: "Dog")
// Swift3 #KeyPath での書き方
animal.setValue("Cat", forKey: #keyPath(Animal.name))
animal.value(forKey: #keyPath(Animal.name))
let keyPath = #keyPath(Animal.name)
print(type(of: keyPath)) // String
// 以前の書き方(Swift3以降でもこの書き方はできる)
animal.setValue("Horse", forKey: "name")
animal.value(forKey: "name")
問題点
KeyPath
KeyPath Composition
Hashable
KeyPath の種類
KeyPath は以下の階層になっています。
AnyKeyPath
↓
PartialKeyPath<Root>
↓
KeyPath<Root, Value>
↓
WritableKeyPath<Root, Value>
↓
ReferenceWritableKeyPath<Root, Value>
AnyKeyPath
- Read Only
- Root,Value はコンパイル時に特定されない
- 実行時に具体的な型に解決
struct Address {
let country: String
}
struct User {
let username: String
let age: Int
let address: Address
}
var user = User(username: "Mike", age: 21, address: Address(country: "Japan"))
let keyPath: AnyKeyPath = \User.username // Optional<Any>
user[keyPath: keyPath] = "太郎" // Read Only なのでコンパイルエラー
AnyKeyPath
PartialKeyPath<Root>
KeyPath<Root, Value>
- Read Only
- Root,Value もコンパイルで型が特定
KeyPath
WritableKeyPath<Root, Value>
ReferenceWritableKeyPath<Root, Value>
KeyPath の便利な使い方
ソート機能
通常は Sort 関数で以下のように書くことが多いと思います。
var user = User(username: "Mike", age: 21, address: Address(country: "Japan"))
var user2 = User(username: "Charlie", age: 17, address: Address(country: "Korea"))
var user3 = User(username: "Tommy", age: 24, address: Address(country: "Canada"))
var people = [user, user2, user3]
people.sort{ $0.username > $1.username }
//people.sort{ $0.address.country > $1.address.country }
KeyPath を使用すると以下のようにかけます。
extension Sequence {
func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {
return sorted { first, second in
return first[keyPath: keyPath] > second[keyPath: keyPath]
}
}
}
let newPeople = people.sorted(by: \User.username)
//let newPeople = people.sorted(by: \User.address.country)
異なるモデルの UITableView 表示
タイトル、サブタイトル、画像表示など、 UITableView で比較的多く見られるパターンの時、
KeyPath を利用して汎用的な CellConfigurator を作成しておけば、異なるモデルでも対応できる。
struct CellConfigurator<Model> {
let titleKeyPath: KeyPath<Model, String>
let subtitleKeyPath: KeyPath<Model, String>
let imageKeyPath: KeyPath<Model, UIImage?>
func configure(_ cell: UITableViewCell, for model: Model) {
cell.textLabel?.text = model[keyPath: titleKeyPath]
cell.detailTextLabel?.text = model[keyPath: subtitleKeyPath]
cell.imageView?.image = model[keyPath: imageKeyPath]
}
}
let songCellConfigurator = CellConfigurator<Song>(
titleKeyPath: \.name,
subtitleKeyPath: \.artistName,
imageKeyPath: \.albumArtwork
)
let playlistCellConfigurator = CellConfigurator<Playlist>(
titleKeyPath: \.title,
subtitleKeyPath: \.authorName,
imageKeyPath: \.artwork
)
The power of key paths in Swift
Swift5 で追加された機能
identity keypath
Identity key path
まとめ
KeyPath を紹介して来ましたが、いかがだったでしょうか?
Swift3で
#KeyPath が、 Swift4で
KeyPath が実装され、よりシンプルかつ Swift らしいものになっています。
今まであまり使う機会なかったのですが、 KeyPath を使うことで、抽象的に値を利用することができ色々と使用できる場面はありそうです。今後積極的に使っていきたいと思います!
参考文献
ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。
興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる