0Pricing
Swift Academy · Урок

Записываемые и ссылочные KeyPaths

Читайте и записывайте данные через ключевые пути.

«Записываемые и ссылочные KeyPaths» — бесплатный урок Swift Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Swift Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Swift Academy содержит 4 уроков всего.

Ключевые пути только для чтения и доступные для записи

Обычный KeyPath позволяет только читать значение. Чтобы записывать значение через ключевой путь, нужен доступный для записи вариант. В Swift есть два таких варианта: один для типов-значений, другой для ссылочных типов.

В этом уроке рассматриваются 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)

Почему экземпляр должен быть переменной

Для типов-значений запись через ключевой путь изменяет копию на месте. Если экземпляр объявлен как 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, но его свойство всё равно можно установить через ключевой путь. Со структурой это привело бы к ошибке компиляции.

Это происходит потому, что изменение свойства класса не требует изменяемой ссылки — изменяемым должно быть только само свойство.

class Profile {
    var nickname: String = ""
}

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

Иерархия типов ключевых путей

Типы доступных для записи ключевых путей образуют иерархию. ReferenceWritableKeyPath является подтипом WritableKeyPath, а тот, в свою очередь, — подтипом KeyPath. Поэтому более специализированный ключевой путь можно использовать везде, где ожидается более общий.

Это означает, что ReferenceWritableKeyPath можно передать функции, которой нужно только читать через KeyPath.

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))

Обновление вложенных типов-значений

Доступные для записи ключевые пути можно объединять для вложенных структур. Установка глубоко вложенного свойства переписывает каждое содержащее его значение по всей цепочке — всё выполняется одним присваиванием.

Здесь мы изменяем город во вложенном адресе.

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)

Применение нескольких изменений

Поскольку доступные для записи ключевые пути являются значениями, можно хранить список изменений в виде пар ключевого пути и значения, а затем применить его в цикле. Это небольшая система исправлений.

Здесь мы применяем два изменения к структуре настроек.

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.

Здесь мы устанавливаем текст у label через доступный для записи ключевой путь для ссылочного типа.

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, если нужно только читать, WritableKeyPath — чтобы устанавливать свойство типа-значения, хранящегося в var, и ReferenceWritableKeyPath — чтобы устанавливать свойство класса даже через let.

Выбор наиболее специализированного подходящего типа позволяет вашим программным интерфейсам честно отражать, читают они данные или изменяют их.

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)")

Часто задаваемые вопросы

Урок «Записываемые и ссылочные KeyPaths» бесплатный?

Да — полный текст урока «Записываемые и ссылочные KeyPaths» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Swift Academy, подпишись на CoddyKit PRO. Курс Swift Academy содержит 4 уроков всего.

Чему я научусь в уроке «Записываемые и ссылочные KeyPaths»?

Читайте и записывайте данные через ключевые пути. Ты практикуешь Swift Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Swift Academy?

Предыдущий опыт не требуется. Swift Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Записываемые и ссылочные KeyPaths»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Swift Academy?

Да. Каждый урок Swift Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Основы KeyPath
  2. Использование KeyPaths в коллекциях
  3. Записываемые и ссылочные KeyPaths
  4. @dynamicMemberLookup
← Назад к Swift Academy