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
}
Как вы можете видеть, мне пришлось "соответствовать" идентификационному протоколу в структуре 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
Это происходит потому, что в Идентифицируемом протоколе id и name являются только gettable. Теперь, если я изменил свойства id и name на {get set}, я получаю следующую ошибку:
Тип 'C' не соответствует протоколу 'Identifiable'
Эта ошибка возникает из-за того, что я не реализовал сеттер в расширении протокола... Поэтому я меняю расширение протокола:
extension Identifiable {
var id: Int {
get {
return 0
}
set {
}
}
var name: String {
get {
return "default"
}
set {
}
}
}
Теперь ошибка исчезает, но если я устанавливаю новое значение id или name, оно получает значение по умолчанию (getter). Конечно, сеттер пуст.
Мой вопрос:
Какую часть кода мне нужно установить внутри сеттера?. Если я добавлю self.id = newValue, он сработает (рекурсивный).
Спасибо заранее.
Ответы
Ответ 1
Кажется, вы хотите добавить stored property
к типу через расширение протокола. Однако это невозможно, потому что с расширениями вы не можете добавить сохраненное свойство.
Я могу показать вам пару альтернатив.
Подклассы (объектно-ориентированное программирование)
Самый простой способ (как вы, наверное, уже представляете) - использовать классы вместо структур.
class IdentifiableBase {
var id = 0
var name = "default"
}
class A: IdentifiableBase { }
let a = A()
a.name = "test"
print(a.name) // test
Минусы: в этом случае ваш класс A должен наследоваться от 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
Ответ 2
Протоколы и расширения протокола очень мощные, но они, как правило, наиболее полезны для свойств и функций только для чтения.
за то, что вы пытаетесь выполнить (хранимые свойства со значением по умолчанию), классы и наследование могут быть более элегантным решением
что-то вроде:
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)")
Ответ 3
Вот почему вы не смогли установить свойства.
Свойство становится вычисленным свойством, которое означает, что у него нет переменной поддержки, такой как _x, как это было бы в ObjC. В приведенном ниже коде решения вы можете видеть, что xTimesTwo ничего не хранит, а просто вычисляет результат из x.
См. Официальные документы о вычисленных свойствах.
Функциональность, которую вы хотите, также может быть Property Observer.
Setters/getters отличаются от Objective-C.
Что вам нужно:
var x:Int
var xTimesTwo:Int {
set {
x = newValue / 2
}
get {
return x * 2
}
}
Вы можете изменить другие свойства в сеттерах/геттерах, что они предназначены для
Ответ 4
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