Подтвердить что ты не робот

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, он сработает (рекурсивный).

Спасибо заранее.

4b9b3361

Ответ 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