Upgrade to Pro — share decks privately, control downloads, hide ads and more …

SwiftのKeyPathを使いこなす

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Ryu Ryu
May 15, 2023

 SwiftのKeyPathを使いこなす

Avatar for Ryu

Ryu

May 15, 2023
Tweet

More Decks by Ryu

Other Decks in Technology

Transcript

  1. 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のメソッドで使えるように
  2. AttributedStringを便利に extension String { func attribute<Value>( range: String, keyPath: WritableKeyPath<AttributedSubstring,

    Value>, 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の型に応じて変化する。
  3. AttributedStringを便利に • After let text = "⼤⼩" let attributedString =

    AttributedString(text) if let range = attributedString.range(of: "⼤") { attributedString[range].font = .boldSystemFont(ofSize: 30) } • before ViewBuilder内にかけるようになった
  4. AttributedStringを便利に extension AttributedString { func attribute<Value>( range: String, keyPath: WritableKeyPath<AttributedSubstring,

    Value>, 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キーワードをつけなければいけなくなってしまい、メソッドチェーンができない。
  5. AttributedStringを便利に extension String { func attribute<Value>( ranges: [String], keyPath: WritableKeyPath<AttributedSubstring,

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

    Value>, value: Value ) -> AttributedString { ranges.reduce(self) { attributedString, element in attributedString.attribute(range: element, keyPath: keyPath, value: value) } } }
  7. 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の例。存在しないプロパティにアクセスすることができる。
  8. KeyPath Member Lookup @dynamicMemberLookup struct Animal<T> { private let animal:

    T init(animal: T) { self.animal = animal } subscript<U>(dynamicMember keyPath: KeyPath<T, U>) -> U { return animal[keyPath: keyPath] } } 以下はKeyPath Member Lookupの例
  9. 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での導⼊事例はスキップ)