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
    }
}

Вы можете изменить другие свойства в установщике / получателе, для чего они и предназначены

Другие вопросы по тегам