Программирование
Протокол Identifiable в SwiftUI с примерами
Хотя этот протокол выглядит простым и имеет всего одно свойство, в нем легко допустить распространенную ошибку, которая может привести к неожиданному поведению вашего представления.
Протокол Identifiable в SwiftUI позволяет добавить уникальную идентификацию объекта. Протокол требует единственного свойства ID любого хэшируемого типа, что делает его гибким протоколом для любых экземпляров.
Несмотря на то, что это относительно простой протокол, несколько возможных крайних случаев могут привести к неожиданным ошибкам в вашем SwiftUI-коде. Поэтому важно понимать, как правильно использовать этот протокол.
Приведение объекта в соответствие с Identifiable
Создавая новые экземпляры (инстансы) в Swift, вы, скорее всего, не думаете о том, чтобы сразу добавить соответствие протоколу Identifiable. Чаще всего вы сталкиваетесь с ошибками сборки, связанными с SwiftUI, такими как:
Ссылка на инициализатор ‘init(_:content:)’ в ‘ForEach’ требует, чтобы ‘Article’ соответствовал ‘Identifiable’.
Эта ошибка сборки возникает, когда вы используете неидентифицируемую коллекцию объектов внутри элемента ForEach:
SwiftUI требует идентификации для каждого объекта, поскольку иначе он не смог бы определить, нужно ли перерисовывать представление после изменения коллекции. Другими словами, добавление идентификации влияет на поведение ваших представлений.
Мы можем продемонстрировать это, добавив соответствие протоколу Identifiable в структуру статьи:
struct Article { let title: String let url: URL } extension Article: Identifiable { var id: String { url.absoluteString } }
На первый взгляд, этот код кажется вполне нормальным, поскольку мы можем успешно скомпилировать и запустить наш проект. Однако, как только мы встречаем две статьи с одинаковым URL, результирующий вывод становится неожиданным:
Хотя обе статьи имеют уникальное название, URL остается неизменным. В представлении результатов дважды отображается один и тот же заголовок, поскольку оно считает, что обе статьи идентичны на основе идентификации. Это показывает, почему следует тщательно добавлять идентификаторы к объектам и проверять, является ли используемый идентификатор уникальным во всех случаях.
В этом примере мы можем правильно добавить поддержку идентификации, введя новое уникальное свойство ID поста:
struct Article { let postID: Int let title: String let url: URL } extension Article: Identifiable { var id: Int { postID } }
Полагание на реализацию по умолчанию для классов
Вы могли заметить, что от вас не требуется добавлять какое-либо свойство ID в классы, которым вы добавили соответствие Identifiable. Протокол Identifiable предоставляет реализацию по умолчанию для типов классов, которые гарантированно остаются уникальными только на время жизни объекта.
Типом идентификатора по умолчанию будет ObjectIdentifier, уникальный идентификатор для экземпляра класса или метатипа. Мы можем продемонстрировать это поведение, создав версию класса для нашей статьи:
class ArticleClass: Identifiable { let postID: Int let title: String let url: URL init(postID: Int, title: String, url: URL) { self.postID = postID self.title = title self.url = url } }
Добавлять новое свойство ID не требуется, а распечатка реализации по умолчанию показывает что-то вроде:
print("\(articleClass.id)") /// Prints: ObjectIdentifier(0x0000600000be0810)
Идентификатор равен указателю объекта, поэтому он гарантированно будет уникальным только в течение всего времени жизни объекта.
Примечание: важно понимать, что для статей в этом примере нужно использовать struct. Может показаться, что это решение — изменить все типы на классы, но это не так. Подробнее об этом вы можете узнать из моей статьи “Структуры и классы в Swift: различия”.
Заключение
Протокол Identifiable позволяет выполнять итерации по коллекции объектов внутри ForEach в SwiftUI. Хотя этот протокол выглядит простым и имеет всего одно свойство, в нем легко допустить распространенную ошибку, которая может привести к неожиданному поведению вашего представления. Классы могут полагаться на реализацию по умолчанию, но не стоит превращать все свои структуры в классы только для того, чтобы они соответствовали Identifiable.
Спасибо!