Slide 1

Slide 1 text

SwiftのKeyPathを使いこなす

Slide 2

Slide 2 text

⾃⼰紹介 • りゅう • 法政⼤学電気電⼦⼯学科 3年 • NewsPicksでインターン • Swift歴2年ちょい @Ryu0118 @ryu_hu03 @Ryu0118 @ryu_hu03

Slide 3

Slide 3 text

KeyPathとは Þ 動的にプロパティにアクセスできるようにするもの Þ バックスラッシュを使ってるまあよくみるやつ AnyKeyPath PartialKeyPath KeyPath WritableKeyPath ReferenceWritableKeyPath 上から順に継承されていて、それ ぞれのクラスで特性が違う!

Slide 4

Slide 4 text

KeyPathの具体的な使い⽅

Slide 5

Slide 5 text

このような構造体を作成してインスタンスを⽣成する。

Slide 6

Slide 6 text

• AnyKeyPath • PartialKeyPath • KeyPath Genericsによる型の指定ができない Rootのみ型の指定ができる Root, Valueともに型の指定ができる 基本的にKeyPathを使う(AnyKeyPathやPartialKeyPathを使うケースが思い浮かばない)

Slide 7

Slide 7 text

• WritableKeyPath • ReferenceWritableKeyPath 動的にPropertyの値を書き換えることができる 参照型(class, actor)の場合は ReferenceWritableKeyPathを使う。

Slide 8

Slide 8 text

使うメリットなくない?

Slide 9

Slide 9 text

使⽤例① map, compactMap, flatMap • ある構造体の配列から特定のPropertyの配列を作 りたいときに便利 • SwiftLintでも⚠よりも🟢が推奨される。

Slide 10

Slide 10 text

使⽤例② KVO (Key Value Observing) • あるクラスの特定のPropertyの変更を検知するときにつかう。 • これはWebViewのページのロードの進み具合の変更を検知している。

Slide 11

Slide 11 text

KeyPathを使って便利な メソッドを⾃作する

Slide 12

Slide 12 text

mapを⾃作してみる mapの中でKeyPathを使うことで特定のPropertyを取り出している。

Slide 13

Slide 13 text

AttributedStringを便利に • AttributedStringとは、部分的に⽂字を⼤きくしたり⾊を変えたりできるもの • NSAttributedStringをより今⾵に便利にしたもの • UIKit, SwiftUIどちらにも対応 • iOS15+ このようにすることで”⼤”のみ⼤きくできるが、var body: some View などの@ViewBuilder内に書く とエラーがでてしまう。 let text = "⼤⼩" let attributedString = AttributedString(text) if let range = attributedString.range(of: "⼤") { attributedString[range].font = .boldSystemFont(ofSize: 30) } KeyPathを⽤いてStringのメソッドで使えるように

Slide 14

Slide 14 text

AttributedStringを便利に extension String { func attribute( range: String, keyPath: WritableKeyPath, value: Value ) -> AttributedString { var attributedString = AttributedString(self) if let range = attributedString.range(of: range) { attributedString[range][keyPath: keyPath] = value } return attributedString } } 引数にAttributedSubstringへのKeyPathを持つことで、AttriutedSubstringのプロパティを指定できる。 valueの型はKeyPathで指定したpropertyの型に応じて変化する。

Slide 15

Slide 15 text

AttributedStringを便利に • After let text = "⼤⼩" let attributedString = AttributedString(text) if let range = attributedString.range(of: "⼤") { attributedString[range].font = .boldSystemFont(ofSize: 30) } • before ViewBuilder内にかけるようになった

Slide 16

Slide 16 text

AttributedStringを便利に このままだと、このようにAttributeを追加できない!! => ということでAttributedStringにも同じようなメソッドを追加していきます。 attributeメソッドから帰ってくる型がAttributedStringなので、こんな感じでメソッドチェーンできない!

Slide 17

Slide 17 text

AttributedStringを便利に extension AttributedString { func attribute( range: String, keyPath: WritableKeyPath, value: Value ) -> AttributedString { var copy = self if let range = copy.range(of: range) { copy[range][keyPath: keyPath] = value } return copy } } AttributedStringは構造体なので、var copy = self でselfをコピーせずにselfを使ってしまう と、mutatingキーワードをつけなければいけなくなってしまい、メソッドチェーンができない。

Slide 18

Slide 18 text

AttributedStringを便利に メソッドチェーンできるようになった!!

Slide 19

Slide 19 text

AttributedStringを便利に しかし、1と2の⽂字を⼤きく、~とページの⽂字⾊を⾚に変更したい場合以下のようになってしまう。 このようにFontや⾊を複数の⽂字にかけたい場合、同じメソッドを複数書かなければならないため、 効率が悪い。

Slide 20

Slide 20 text

AttributedStringを便利に extension String { func attribute( ranges: [String], keyPath: WritableKeyPath, value: Value ) -> AttributedString { ranges.reduce(AttributedString(self)) { attributedString, element in attributedString.attribute(range: element, keyPath: keyPath, value: value) } } }

Slide 21

Slide 21 text

AttributedStringを便利に extension AttributedString { func attribute( ranges: [String], keyPath: WritableKeyPath, value: Value ) -> AttributedString { ranges.reduce(self) { attributedString, element in attributedString.attribute(range: element, keyPath: keyPath, value: value) } } }

Slide 22

Slide 22 text

AttributedStringを便利に これでめちゃめちゃ便利になった!! (サンプルコードスライドの最後に貼っておくので、よかったらご覧ください)

Slide 23

Slide 23 text

KeyPath Member Lookupに ついて

Slide 24

Slide 24 text

KeyPath Member Lookup • Swift 5.1で追加された機能 • Dynamic Member Lookupをコンパイラレベルで安全にしたもの let animal = Animal() print(animal.cat, animal.dog, animal.elephant) // 出⼒: Optional("まる") Optional("ぽち") nil @dynamicMemberLookup struct Animal { private let animalNames = ["cat": "まる", "dog": "ぽち"] subscript(dynamicMember key: String) -> String? { animalNames[key] } } 以下はDynamic Member Lookupの例。存在しないプロパティにアクセスすることができる。

Slide 25

Slide 25 text

KeyPath Member Lookup @dynamicMemberLookup struct Animal { private let animal: T init(animal: T) { self.animal = animal } subscript(dynamicMember keyPath: KeyPath) -> U { return animal[keyPath: keyPath] } } 以下はKeyPath Member Lookupの例

Slide 26

Slide 26 text

KeyPath Member Lookup KeyPath Member Lookupは存在しないプロパティにはアクセスできない。 Animalがあたかもnameプロパティを持っているかのような挙動をする。 struct Cat { let name: String } let cat = Cat(name: "まる") let animal = Animal(animal: cat) print(animal.name) // 出⼒: まる また、Dynamic Member Lookupと違いXcode上でサジェストが出るので便利 ⾃分はまだ使い道が思いつかないが、TCAではこの技術が導⼊されていて、これがないとTCAが成り⽴たな いような強⼒な機能 (時間がないので、TCAでの導⼊事例はスキップ)

Slide 27

Slide 27 text

まとめ KeyPathを使っていろいろなものを便利にすることができた! もっといろいろなものを便利にしていきたい。 サンプルコードは以下のURLからご参照ください! https://gist.github.com/Ryu0118/939a3341f5e40103fd213127fa762f46

Slide 28

Slide 28 text

参考⽂献 • https://dev.classmethod.jp/articles/swift_keypath1/ • https://developer.apple.com/documentation/foundation/attributedstring • https://developer.apple.com/documentation/swift/keypath ご清聴ありがとうございました!