Swift - Расширения протокола - Значения свойств по умолчанию

avatar
Axort
11 августа 2016 в 00:41
47656
4
70

Допустим, у меня есть следующий протокол:

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 дополнительных структур, которые должны соответствовать этому протоколу... Я не хочу " скопировать/вставить соответствие (идентификатор переменной: Int, имя переменной: строка)

Поэтому я создаю расширение протокола:

extension Identifiable {
    var id: Int {
        return 0
    }

    var name: String {
        return "default"
    }
}

С помощью этого расширения теперь я могу создать структуру, соответствующую протоколу идентификации, без необходимости реализации обоих свойств:

struct C: Identifiable {

}

Теперь проблема в том, что я не могу установить значение свойства id или свойства name:

var c: C = C()
c.id = 12 // Cannot assign to property: 'id' is a get-only property

Это происходит из-за того, что в протоколе идентификации идентификатор и имя доступны только для получения. Теперь, если я изменю свойства id и name на {get set}, я получу следующую ошибку:

Тип 'C' не соответствует протоколу 'Идентифицируемый'

Эта ошибка возникает из-за того, что я не реализовал сеттер в расширении протокола... Поэтому я меняю расширение протокола:

extension Identifiable {
    var id: Int {
        get {
            return 0
        }

        set {

        }
    }

    var name: String {
        get {
            return "default"
        }

        set {

        }
    }
}

Теперь ошибка исчезает, но если я устанавливаю новое значение id или name, оно получает значение по умолчанию (геттер). Конечно, сеттер пуст.

Мой вопрос: Какой фрагмент кода мне нужно поместить в сеттер? Потому что, если я добавлю self.id = newValue, произойдет сбой (рекурсивный).

Заранее спасибо.

Источник

Ответы (4)

avatar
Luca Angeletti
11 августа 2016 в 01:12
100

Кажется, вы хотите добавить 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 нет множественного наследования, это будет единственный класс A, от которого можно наследовать.

Компоненты (протокольно-ориентированное программирование)

Этот метод довольно популярен в разработке игр

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
Axort
11 августа 2016 в 15:34
0

Привет! Спасибо за Ваш ответ. Мне очень нравится эта компонентная техника. С этого момента я пытаюсь принять протокольно-ориентированное программирование, поэтому я искал что-то подобное.

Luca Angeletti
11 августа 2016 в 15:35
1

@Axort: пожалуйста. Я также предлагаю вам посмотреть это видео о протокольно-ориентированном программировании с WWDC 2015 года. Очень интересно.

Above The Gods
2 февраля 2017 в 04:19
10

Почему у вас есть протокол HasIdentifiedComponent? Почему бы просто не сделать protocol Identifiable: var identifiableComponent: IdentifiableComponent { get set } Затем extension Identifiable { var id: Int { get { return identifiableComponent.id } set { identifiableComponent.id = newValue } } etc... }

Bohdan Savych
24 мая 2017 в 10:40
1

Не могли бы вы объяснить, почему вы создаете 2 протокола (HasIdentifiableComponent и Identifying) в примере с компонентами?

Luca Angeletti
24 мая 2017 в 16:31
2

@BohdanSavych Просто потому, что я хотел отделить понятие HasIdentifiableComponent от понятия Identifiable. Если вам не нравится такое разделение, не стесняйтесь группировать все в один протокол.

ekreloff
16 января 2018 в 19:25
1

Я получаю сообщение об ошибке «Невозможно присвоить свойству: вызов функции возвращает неизменное значение» в Swift 4 при попытке установить любое свойство. Аналогичная ошибка при попытке установить эти свойства из соответствующего класса, говоря, что self неизменяемо. Должны ли они быть протоколами только для класса?

Robin Macharg
12 марта 2018 в 12:36
0

Если вы готовы задействовать среду выполнения Objective C, вы также можете воспользоваться Associated Objects в качестве резервного хранилища для свойств расширения. См. nshipster.com/swift-objc-runtime и marcosantadev.com/stored-properties-swift-extensions для примеров и обсуждения.

cohen72
9 декабря 2018 в 07:53
0

@LucaAngeletti, как бы вы справились с этим, когда протокол определяет ассоциированный тип, а также имеет сохраненное свойство этого типа.

avatar
DominicMDev
15 мая 2019 в 21:08
3

Связанные объекты 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

avatar
Harsh
11 августа 2016 в 01:14
0

Вот почему вы не смогли установить свойства.

Свойство становится вычисляемым свойством, что означает, что оно не имеет вспомогательной переменной, такой как _x, как это было бы в ObjC. В приведенном ниже коде решения видно, что xTimesTwo ничего не сохраняет, а просто вычисляет результат из x.

.

См. официальные документы по вычисляемым свойствам.

Требуемые функции также могут быть наблюдателями свойств.

Сеттеры/геттеры отличаются от тех, что были в Objective-C.

Что вам нужно:

var x:Int

var xTimesTwo:Int {
    set {
       x = newValue / 2
    }
    get {
        return x * 2
    }
}

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

Axort
11 августа 2016 в 15:40
0

Привет! Спасибо за Ваш ответ. Да, как вы сказали, проблема в том, что вы не можете объявлять vars в расширении (расширения могут не содержать сохраненных свойств), поэтому мне было интересно, есть ли способ (маленький шанс) с вычисляемыми свойствами: P

avatar
Nick
11 августа 2016 в 01:07
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)")
Axort
11 августа 2016 в 15:35
2

Привет! Спасибо за Ваш ответ. Я искал решение с протоколо-ориентированным программированием вместо объектно-ориентированного программирования.