Сохраняет ли путь к свойству key path экземпляр класса, для которого он предназначен?

Сохраняет ли путь к свойству (key path) экземпляр класса, для которого он предназначен?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Нет. Key path хранит описание пути к свойству и не содержит сильной ссылки на конкретный экземпляр, для которого этот путь можно применить. Объект живёт только благодаря отдельным сильным ссылкам, включая ссылку, переданную при чтении или изменении свойства.

Исторический контекст

Key paths появились как типобезопасный способ представить доступ к свойству в виде значения: его можно сохранить, передать в функцию и применить к разным экземплярам. Это отделяет описание операции доступа от самого объекта и позволяет переиспользовать один путь.

Такой подход отличается от замыкания, которое может захватить экземпляр. Сам key path является метаданными о структуре типов, а не владельцем конкретного объекта.

Постановка проблемы

Представим долгоживущий кэш key paths. Ошибочно считать, что сохранение пути к свойству удерживает объект-владелец, поэтому разработчик может ожидать, что экземпляр не будет уничтожен.

В результате объект может быть освобождён после обнуления последней сильной ссылки, несмотря на то что key path продолжает существовать. Попытка применить путь к уже уничтоженному экземпляру невозможна не из-за key path: для применения всё равно требуется отдельная ссылка на объект.

Подробное решение

Key path вроде \\Profile.name описывает, как получить свойство name у экземпляра Profile. Он не содержит конкретный Profile, поэтому его копирование или хранение не увеличивает счётчик сильных ссылок этого типа.

final class Profile { var name: String init(name: String) { self.name = name } deinit { print("deinit") } } let namePath = \\Profile.name var profile: Profile? = Profile(name: "Ada") print(profile![keyPath: namePath]) profile = nil

В примере namePath продолжает существовать после profile = nil, но экземпляр Profile может быть уничтожен. Само обращение через key path временно использует переданный экземпляр; оно не превращает key path в владельца.

Это отличается от замыкания, которое явно или неявно захватило бы profile. Если замыкание хранит сильный захват экземпляра, оно продлевает его жизнь; если хранит слабый захват, объект может исчезнуть, а результат доступа стать недоступным.

Для ReferenceWritableKeyPath правило такое же: возможность изменить свойство не означает владение корневым объектом. Владелец должен отдельно гарантировать жизнь экземпляра на время операции чтения или записи.

Ситуация из практики

Компонент интерфейса получает key path к свойству модели и сохраняет его до следующего обновления. Рассматривались два варианта хранения:

  • хранить только key path — не удерживает модель и подходит, если модель управляется внешним владельцем;
  • хранить key path вместе с моделью — обеспечивает доступ к конкретному объекту, но добавляет сильное владение и может продлить его жизнь дольше ожидаемого.

Выбран вариант с раздельным управлением: компонент хранит key path, а модель передаётся ему через явно определённую зависимость с подходящей политикой владения. Это предотвращает ложное ожидание, что key path сам сохраняет объект, и позволяет контролировать граф ссылок.

Что кандидаты часто упускают

  1. Продлевает ли чтение свойства через key path жизнь объекта?

    На время операции должен существовать доступный экземпляр, но key path не становится его владельцем. После завершения операции объект может быть уничтожен, если других сильных ссылок нет; точный момент освобождения определяется правилами времени жизни ARC и оптимизациями компилятора.

  2. Образуется ли цикл, если объект хранит key path к собственному свойству?

    Нет, сам key path не содержит ссылку на экземпляр. Цикл возникнет только при наличии другого ребра владения, например если объект хранит замыкание, которое сильно захватывает этот же объект.

  3. Чем хранение key path отличается от хранения замыкания доступа к свойству?

    Key path хранит структурированное описание пути и не захватывает конкретный экземпляр. Замыкание может захватить объект или промежуточное состояние, поэтому способно продлить жизнь экземпляра и даже создать retain cycle. Выбор зависит от того, нужен ли универсальный путь или операция с конкретным контекстом и его владением.