0Pricing
Swift Academy · レッスン

書き込み可能なKeyPathと参照KeyPath

KeyPathを通して値を読み書きします。

「書き込み可能なKeyPathと参照KeyPath」はCoddyKit上の無料Swift Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSwift Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Swift Academyコースには全4レッスンが含まれています。

読み取り専用キーパスと書き込み可能キーパス

通常のKeyPathでは値を読み取ることしかできません。キーパスを通じて値を書き込むには、書き込み可能なバリアントが必要です。Swiftには、値型用と参照型用の2種類があります。

このレッスンでは、構造体用のWritableKeyPathとクラス用のReferenceWritableKeyPath、そして[keyPath:]サブスクリプトが設定可能になる仕組みについて学びます。

struct Person {
    var name: String
    var age: Int
}

let writable: WritableKeyPath<Person, Int> = \Person.age
print(type(of: writable))

値型のWritableKeyPath

WritableKeyPathは、値型の変更可能な格納プロパティを対象にします。これを通じて値を設定するには、構造体のプロパティを変更すると値全体が変更されるため、インスタンス自体がvarでなければなりません。

代入の左辺で[keyPath:]サブスクリプトを使って書き込みます。

struct Person {
    var name: String
    var age: Int
}

var alice = Person(name: "Alice", age: 30)
let agePath = \Person.age
alice[keyPath: agePath] = 31
print(alice.age)

インスタンスがvarでなければならない理由

値型では、キーパスを通じた書き込みによってコピーがその場で変更されます。インスタンスがletの場合は変更する対象がないため、代入はコンパイルできません。

これは通常のプロパティ変更と同じルールです。構造体が変数に格納されている場合にだけ、その格納プロパティを設定できます。

struct Point {
    var x: Int
    var y: Int
}

var p = Point(x: 1, y: 2)
p[keyPath: \Point.x] = 10
p[keyPath: \Point.y] = 20
print(p)

汎用セッターヘルパー

書き込み可能キーパスには変更するプロパティの情報が含まれているため、任意の値型の任意のプロパティを更新する汎用関数を作成できます。インスタンスをinoutで渡すことで、変更を呼び出し元に書き戻します。

これは、小さな関数型更新ユーティリティの基盤になります。

func update<Root, Value>(_ root: inout Root, _ path: WritableKeyPath<Root, Value>, to value: Value) {
    root[keyPath: path] = value
}

struct Person { var name: String; var age: Int }
var bob = Person(name: "Bob", age: 40)
update(&bob, \.age, to: 41)
print(bob.age)

クラスのReferenceWritableKeyPath

クラスの場合、SwiftはReferenceWritableKeyPathを提供します。クラスのインスタンスは参照型なので、インスタンスがlet定数に保持されていても、そこを通じて書き込めます。変わるのは参照ではなく、参照先のオブジェクトだけです。

これが、値型の書き込み可能キーパスとの主な違いです。

class Counter {
    var value: Int = 0
}

let counter = Counter()
let path: ReferenceWritableKeyPath<Counter, Int> = \Counter.value
counter[keyPath: path] = 5
print(counter.value)

letの参照と変更可能なオブジェクト

次のクラスインスタンスはletであることに注目してください。それでもキーパスを通じてプロパティを設定できます。構造体ではコンパイルエラーになります。

これは、クラスのプロパティを変更するために参照自体を変更可能にする必要はなく、プロパティ自体が変更可能であればよいためです。

class Profile {
    var nickname: String = ""
}

let profile = Profile()
let nickPath = \Profile.nickname
profile[keyPath: nickPath] = "Ace"
print(profile.nickname)

キーパスの型階層

書き込み可能キーパスの型は階層を形成しています。ReferenceWritableKeyPathはWritableKeyPathのサブタイプであり、WritableKeyPathはKeyPathのサブタイプです。そのため、より具体的なキーパスを、より一般的なキーパスが必要な場所で使用できます。

つまり、読み取りにKeyPathしか必要としない関数に、ReferenceWritableKeyPathを渡すことができます。

class Box { var size: Int = 1 }

func readSize(_ box: Box, _ path: KeyPath<Box, Int>) -> Int {
    box[keyPath: path]
}

let writable: ReferenceWritableKeyPath<Box, Int> = \Box.size
print(readSize(Box(), writable))

ネストした値型を更新する

書き込み可能キーパスは、ネストした構造体を通じて連結できます。深くネストしたプロパティを設定すると、連鎖する各外側の値が書き換えられ、すべてが1回の代入で完了します。

ここでは、ネストした住所の中にある都市を変更します。

struct Address { var city: String }
struct Person { var name: String; var address: Address }

var carol = Person(name: "Carol", address: Address(city: "Oslo"))
carol[keyPath: \Person.address.city] = "Bergen"
print(carol.address.city)

複数の更新を適用する

書き込み可能キーパスは値として扱えるため、キーパスと値の組として更新の一覧を保存し、ループで適用できます。これは小さなパッチシステムです。

ここでは、設定を表す構造体に2つの変更を適用します。

struct Settings { var volume: Int; var brightness: Int }

var settings = Settings(volume: 5, brightness: 5)
func apply(_ s: inout Settings, _ path: WritableKeyPath<Settings, Int>, _ v: Int) {
    s[keyPath: path] = v
}
apply(&settings, \.volume, 8)
apply(&settings, \.brightness, 3)
print(settings)

バインディング形式の構成

実際の開発でよくある用途は、新しく作成したオブジェクトのプロパティを設定してからそのオブジェクトを返すconfigureヘルパーです。これにより、流れるような記述スタイルが可能になります。クラスではletの結果に対しても機能します。

ここでは、参照型の書き込み可能キーパスを通じてラベルのテキストを設定します。

class Label { var text: String = "" }

func configure<T: AnyObject, V>(_ object: T, _ path: ReferenceWritableKeyPath<T, V>, _ value: V) -> T {
    object[keyPath: path] = value
    return object
}

let label = configure(Label(), \.text, "Hello")
print(label.text)

適切なキーパス型を選ぶ

読み取りだけの場合はKeyPathを使います。varに格納された値型のプロパティを設定する場合はWritableKeyPathを使い、letを通じてクラスのプロパティを設定する場合はReferenceWritableKeyPathを使います。

用途に合う最も具体的な型を選ぶことで、APIが読み取りを行うのか変更を行うのかを正確に示せます。

struct S { var n: Int }
class C { var n: Int = 0 }

let read: KeyPath<S, Int> = \S.n
let writeStruct: WritableKeyPath<S, Int> = \S.n
let writeClass: ReferenceWritableKeyPath<C, Int> = \C.n
print(type(of: read), type(of: writeStruct), type(of: writeClass))

確認:書き込み可能キーパス

インスタンスがlet定数であってもクラスのプロパティを設定できるキーパス型を思い出してください。

まとめ:書き込み可能キーパスと参照型キーパス

キーパスを通じて書き込む方法を学びました。

  • WritableKeyPathは値型の格納プロパティを設定しますが、インスタンスにはvarが必要です。
  • ReferenceWritableKeyPathはクラスのプロパティを設定し、letを通じても機能します。
  • [keyPath:]サブスクリプトは代入の左辺に置いて書き込みに使います。
  • これらの型は階層を形成しているため、書き込み可能キーパスを読み取り専用キーパスの代わりに使用できます。
  • 汎用のinoutセッターやconfigureヘルパーは、これらの仕組みを基盤にしています。

次は、キーパスを使って動的データへのドット構文によるアクセスを提供する@dynamicMemberLookupについて学びます。

struct Person { var name: String; var age: Int }

var dave = Person(name: "Dave", age: 27)
dave[keyPath: \Person.age] = 28
print("Updated age: \(dave.age)")

よくある質問

「書き込み可能なKeyPathと参照KeyPath」レッスンは無料ですか?

はい。「書き込み可能なKeyPathと参照KeyPath」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Swift Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Swift Academyコースには全4レッスンが含まれています。

「書き込み可能なKeyPathと参照KeyPath」で何を学びますか?

KeyPathを通して値を読み書きします。 ブラウザで直接実行するハンズオンコードでSwift Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Swift Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSwift Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「書き込み可能なKeyPathと参照KeyPath」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSwift Academyレッスンでコードを書いて実行できますか?

はい。すべてのSwift Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. KeyPathの基礎
  2. コレクションでKeyPathを使う
  3. 書き込み可能なKeyPathと参照KeyPath
  4. @dynamicMemberLookup
← Swift Academyに戻る