The Power of Key paths in Swift


きっかけ


模型を作る時、key pathを知りました.
keyPathを変数として使えるかどうか考えている最中に、この文章を読んで整理してみました.
私がしたい変数の一つは.
struct Zoo {
    let animal: String
    let age: Int
}
もしあれば、Zooageのpropertyを変数として使いたいです.
もし、もし
let agePathOfZooStruct = Zoo.age
よろしければ.keymappingとも言える...これまでやってきたルールのないまとまりのないモデルの組み合わせは、ハードコーディングを1つずつ行うことなく変数に加えることができます...このような考えは私にKeypathを理解させた.
まだ解決策は見つかっていませんが、次の文章は面白いです.

KeyPath?



Key paths essentially let us reference any instance property as a separate value.
As such, they can be passed around, used in expressions, and enable a piece of code to get or set a property without actually knowing which exact property its working with.
KeyPathsを使用して、instanceプロパティを個別の値として参照します.
したがって、実際にどのプロパティが機能するか分からない場合でも、コードを渡して式に使用し、propertyをインポートまたは設定できます.
キーパスには3つの主要な変異体があります.

  • Keypath:propertyは読み取り専用アクセスを提供します.

  • WritableKeyPath:可変属性と読み書きアクセスを提供します.

  • ReferenceWritableKeyPath:クラスインスタンスなどの参照タイプとのみ使用できます.次に、可変属性にreadwrite accessを提供します.
  • There are some additional key path types( + AnyKeyPath, APartialKeyPath) as well, that are there to reduce internal code duplication and to help with type erasure, but we’ll focus on the main types in this article.
    (他のキーパスタイプは、内部コードの重複を減らし、タイプを削除するのに役立ちますが、主なタイプについて重点的に説明します.)

    Functional shorthands

    Articleというモデルがあります.
    struct Article {
        let id: UUID
        let source: URL
        let title: Stirng
        let body: String
    }
    Articleモデルからidやsourceのように1つの値だけを抽出したい場合は、mapを使用します.
    // articles = array of Article model
    let articleIDs = articles.map { $0.id }
    let articleSources = articles.map { $0.source }
    SWIFT 5.2以前にkeypathを使用するために、以下の拡張シーケンスが使用されていました.
    // Earlier Swift 5.2
    extension Sequence {
        func map<T>(_ keyPath: KeyPath<Element, T>) -> [T] {
            return map { $0[keyPath: keyPath] }
        }
    }
    let articleIDs = articles.map(\.id)
    let articleSources = articles.map(\.source)

    Sorting

    extension Sequence {
        func sorted<T: Comparable>(by keyPath: KeyPath<Element, T>) -> [Element] {
            return sorted { a, b in
                return a[keyPath: keyPath] < b[keyPath: keyPath]
            }
        }
    }
    ということで.
    articles.sorted(by: \.title)
    これで昇順に並びました.

    No instance required


    The true power of key paths comes from the fact that they let us reference a property without having to associate it with any specific instance.
    (キーパスの真の力は、特定のインスタンスに関連付けずにpropertyを参照できるという事実から来ています.)
    struct SongCellConfigurator {
        func configure(_ cell: UITableViewCell, for song: Song) {
            cell.textLabel?.text = song.name
            cell.detailTextLabel?.text = song.artistName
            cell.imageView?.image = song.albumArtwork
        }
    }
    上記のコードには問題はありませんが、他のモデルも同様の方法でレンダリングされる可能性があります(多くのテーブルビューユニットで表されるモデルにかかわらず、タイトル、サブタイトル、画像をレンダリングする傾向があります).そこで、プライマリパスの機能を使用して共有プロファイル実装を作成できるかどうかを見てみましょう.
    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]
        }
    }
    -> Usage
      let songCellConfigurator = CellConfigurator<Song>(
          titleKeyPath: \.name,
          subtitleKeyPath: \.artistName,
          imageKeyPath: \.albumArtwork
      )
      
      let playlistCellConfigurator = CellConfigurator<PlayList>(
          titleKeyPath: \.title,
          subtitleKeyPath: \.authorName,
          imageKeyPathL \.artwork
      )

    Converting to functions

    class ListViewController {
        private var items = [Items]() { didSet { render() } }
        
        func loadItems() {
            loader.load { [weak self] items in
                self?.items = items
            }
    }
    func setter<Object: AnyObject, Value>(
        for object: Object,
        keyPath: ReferenceWritableKeyPath<Object, Value>
    ) -> (Value) -> Void {
        return { [weak object] value in
            object?[keyPath: keyPath] = value
        }
    }
    Let't see if key paths again can help us make the above syntax a bit simpler, and if we also can get rid of that weak self dance we so often have to do(and with it - the risk of accidentally introducing retain cycle if we forget to capture a weak reference to self ).
    class ListViewController {
        private var items = [Item]() { didSet { render() } }
        
        func loadItems() {
            loader.load(then: setter(for: self, keyPath: \.items))
        }
    }
    この文章は次の文章を読んで記録します.
    https://www.swiftbysundell.com/articles/the-power-of-key-paths-in-swift/