@dynamicMemberLookup — аннотация Swift, позволяющая переопределять доступ к свойствам объекта на этапе выполнения через subscript. Исторически этот механизм был введён для более прозрачной интеграции с динамическими языками, такими как Python, а также нацелен на более гибкую маршрутизацию доступа к данным в proxy-объектах, динамических моделях и DSL.
Проблема: Стандартный Swift требует объявлять свойства явно и проверяет их существование на этапе компиляции. Но иногда необходимо обращаться к свойствам по их имени, которого не существует в compile-time, например, при работе с JSON, API, proxy-объектами, скриптовыми обёртками.
Решение: Использовать @dynamicMemberLookup и реализовать subscript(dynamicMember:) для перехвата попыток доступа к несуществующим свойствам.
Пример кода:
@dynamicMemberLookup struct JSON { private var data: [String: Any] subscript(dynamicMember member: String) -> Any? { data[member] } } let user = JSON(data: ["name": "Anna", "age": 23]) print(user.name as? String) // Anna
Ключевые особенности:
Можно ли реализовать dynamicMemberLookup для классов, или только для struct?
Да, можно применять для классов, struct и enum (начиная с Swift 5+). Важно только реализовать соответствующий subscript.
Что если обратиться к несуществующему свойству через dynamicMemberLookup?
Возвращается значение из subscript, ответственность за обработку отсутствия значения лежит на вашей реализации.
Например, в примере выше, если обратиться к user.secret, будет возвращено nil.
Можно ли делать динамический доступ с помощью ключей другого типа, например Int?
Да! Можно объявить subscript(dynamicMember:) с другими argument labels и комбинировать с обычными subscript-ами.
@dynamicMemberLookup struct ArrayProxy { private let array: [Int] subscript(dynamicMember member: String) -> Int? { if member == "first" { return array.first } if member == "last" { return array.last } return nil } subscript(index: Int) -> Int? { array[safe: index] } }
Разработчик использует @dynamicMemberLookup для обычной модели пользователя (User), чтобы обращаться к любым полям через строки. Код становится непрозрачным, IDE потеряла подсказки.
Плюсы:
Минусы:
@dynamicMemberLookup применён для работы с произвольным JSON-объектом, чьи поля заранее неизвестны. Механизм позволяет элегантно и безопасно (через Any?/optional binding) работать с неструктурированными данными.
Плюсы:
Минусы: