Swift - Расширения протокола - Свойства по умолчанию
Допустим, у меня есть следующий протокол:
protocol Identifiable {
var id: Int {get}
var name: String {get}
}
И что у меня есть следующие структуры:
struct A: Identifiable {
var id: Int
var name: String
}
struct B: Identifiable {
var id: Int
var name: String
}
Как вы можете видеть, мне пришлось "соответствовать" протоколу Identifiable в структуре A и структуре B. Но представьте, если бы у меня было N структур, которые должны соответствовать этому протоколу... я не хочу "копировать / вставлять" 'соответствие (var id: Int, var name: String)
Поэтому я создаю расширение протокола:
extension Identifiable {
var id: Int {
return 0
}
var name: String {
return "default"
}
}
С этим расширением теперь я могу создать структуру, соответствующую протоколу Identifiable, без необходимости реализации обоих свойств:
struct C: Identifiable {
}
Теперь проблема в том, что я не могу установить значение для свойства id или свойства name:
var c: C = C()
c.id = 12 // Cannot assign to property: 'id' is a get-only property
Это происходит потому, что в протоколе Identifiable идентификатор и имя доступны только для получения. Теперь, если я изменяю свойства id и name на {get set}, я получаю следующую ошибку:
Тип "С" не соответствует протоколу "Идентифицируемый"
Эта ошибка происходит из-за того, что я не реализовал установщик в расширении протокола... Поэтому я изменяю расширение протокола:
extension Identifiable {
var id: Int {
get {
return 0
}
set {
}
}
var name: String {
get {
return "default"
}
set {
}
}
}
Теперь ошибка исчезает, но если я устанавливаю новое значение в id или name, он получает значение по умолчанию (getter). Конечно, сеттер пуст.
Мой вопрос:какой кусок кода я должен поместить в сеттер? Потому что, если я добавлю self.id = newValue, он падает (рекурсивно).
Заранее спасибо.
3 ответа
Кажется, вы хотите добавить stored property
к типу через расширение протокола. Однако это невозможно, поскольку с помощью расширений вы не можете добавить сохраненное свойство.
Я могу показать вам пару альтернатив.
Подклассы (объектно-ориентированное программирование)
Самый простой способ (как вы, наверное, уже представляете) - использовать классы вместо структур.
class IdentifiableBase {
var id = 0
var name = "default"
}
class A: IdentifiableBase { }
let a = A()
a.name = "test"
print(a.name) // test
Минусы: в этом случае ваш класс А должен наследоваться от
IdentifiableBase
и поскольку в Swift нет множественного наследования, это будет единственный класс, который сможет наследовать.
Компоненты (протоколно-ориентированное программирование)
Этот метод довольно популярен в разработке игр
struct IdentifiableComponent {
var id = 0
var name = "default"
}
protocol HasIdentifiableComponent {
var identifiableComponent: IdentifiableComponent { get set }
}
protocol Identifiable: HasIdentifiableComponent { }
extension Identifiable {
var id: Int {
get { return identifiableComponent.id }
set { identifiableComponent.id = newValue }
}
var name: String {
get { return identifiableComponent.name }
set { identifiableComponent.name = newValue }
}
}
Теперь вы можете сделать так, чтобы ваш тип соответствовал Identifiable
просто писать
struct A: Identifiable {
var identifiableComponent = IdentifiableComponent()
}
Тестовое задание
var a = A()
a.identifiableComponent.name = "test"
print(a.identifiableComponent.name) // test
Objective-C Связанные объекты
Вы можете использовать объекты Objective-C, чтобы добавить stored property
к class
или же protocol
,Обратите внимание, что связанные объекты работают только для объектов класса.
import ObjectiveC.runtime
protocol Identifiable: class {
var id: Int { get set }
var name: String { get set }
}
var IdentifiableIdKey = "kIdentifiableIdKey"
var IdentifiableNameKey = "kIdentifiableNameKey"
extension Identifiable {
var id: Int {
get {
return (objc_getAssociatedObject(self, &IdentifiableIdKey) as? Int) ?? 0
}
set {
objc_setAssociatedObject(self, &IdentifiableIdKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
var name: String {
get {
return (objc_getAssociatedObject(self, &IdentifiableNameKey) as? String) ?? "default"
}
set {
objc_setAssociatedObject(self, &IdentifiableNameKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
Теперь вы можете сделать свой class
соответствовать Identifiable
просто написав
class A: Identifiable {
}
Тестовое задание
var a = A()
print(a.id) // 0
print(a.name) // default
a.id = 5
a.name = "changed"
print(a.id) // 5
print(a.name) // changed
Протоколы и расширения протокола очень мощные, но они, как правило, наиболее полезны для свойств и функций только для чтения.
для того, что вы пытаетесь достичь (хранимые свойства со значением по умолчанию), классы и наследование могут быть более элегантным решением
что-то вроде:
class Identifiable {
var id: Int = 0
var name: String = "default"
}
class A:Identifiable {
}
class B:Identifiable {
}
let a = A()
print("\(a.id) \(a.name)")
a.id = 42
a.name = "foo"
print("\(a.id) \(a.name)")
Вот почему вы не смогли установить свойства.
Свойство становится вычисляемым свойством, что означает, что у него нет резервной переменной, такой как _x, как в ObjC. В приведенном ниже коде решения вы можете увидеть, что xTimesTwo ничего не хранит, а просто вычисляет результат из x.
Смотрите Официальные документы по вычислительным свойствам.
Функциональность, которую вы хотите, также может быть Property Observers.
Установщики / получатели отличаются, чем они были в Objective-C.
Что вам нужно это:
var x:Int
var xTimesTwo:Int {
set {
x = newValue / 2
}
get {
return x * 2
}
}
Вы можете изменить другие свойства в установщике / получателе, для чего они и предназначены