Инициализатор расширения протокола

Я хотел бы знать, что эквивалентно протоколу для инициализатора в простом классе, который содержит только функциональные возможности инициализации и предназначен только для расширения в конкретном классе.

Так что, наверное, проще всего показать код - я ищу расширение протокола, эквивалентное следующему:

import UIKit

class Thing {
    var color:UIColor
    init(color:UIColor) {
        self.color = color
    }
}
class NamedThing:Thing {
    var name:String
    init(name:String,color:UIColor) {
        self.name = name
        super.init(color:color)
    }
}
var namedThing = NamedThing(name: "thing", color: UIColor.blueColor())

Я ожидал, что код будет выглядеть примерно так:

protocol Thing {
    var color:UIColor {get set}
}
extension Thing {
    init(color:UIColor) {
        self.color = color
    }
}
class NamedThing:Thing {
    var name:String
    var color:UIColor
    init(name:String,color:UIColor) {
        self.name = name
        self.init(color:color)
    }
}

Я видел решения, предлагаемые в других вопросах StackOverflow (например, Как определить инициализаторы в расширении протокола?), но я не уверен, что они работают, обратитесь к этой проблеме дополнительных параметров в инициализаторе класса.

Ответ 1

Вы должны предоставить действительную цепочку инициализации для создания экземпляра класса и который ограничивает ваши параметры инициализаторов в протоколах.

Поскольку ваш протокол не может быть охвачен всеми членами класса, который его использует, любой инициализатор, который вы объявляете в своем протоколе, должен будет делегировать инициализацию "неизвестных" членов класса другому инициализатору, предоставленному классом сам по себе.

Я скорректировал ваш пример, чтобы проиллюстрировать это, используя базовый init() в качестве инициализатора делегирования для протокола.

Как вы можете видеть, это требует, чтобы ваш класс реализовал начальные значения для всех членов при вызове init(). В этом случае я сделал это, указав значения по умолчанию в каждой декларации участника. И, поскольку для некоторых членов не всегда существует фактическое начальное значение, я изменил их на опции автоматического разворота.

И чтобы сделать более интересным, ваш класс не может делегировать инициализацию инициализатору, предоставленному протоколом, если только это не происходит через инициализатор удобства.

Интересно, стоят ли все эти ограничения? Я подозреваю, что вы пытаетесь использовать протокол, потому что вам нужна последовательность обычных переменных, которые будут последовательно инициализироваться между классами, реализующими протокол. Возможно, использование класса делегатов обеспечит менее сложное решение, чем протоколы (просто мысль).

protocol Thing:AnyObject
{
    var color:UIColor! { get set }
    init()
}

extension Thing 
{    
    init(color:UIColor)
    {  
       self.init()
       self.color = color
    }
}

class NamedThing:Thing 
{
    var name:String!   = nil
    var color:UIColor! = nil

    required init() {}

    convenience init(name:String,color:UIColor) 
    {
        self.init(color:color)
        self.name = name
    }
}

Ответ 2

protocol Thing {
    var color: UIColor {get set}
}

Удивительно, никаких проблем.

extension Thing {
    init(color: UIColor) {
        self.color = color
    }
}

Нет. Это никогда не сработает. Это нарушает слишком много правил. Первым и самым важным является то, что это не обязательно устанавливает все свойства. Рассмотрим ваш NamedThing. Что такое name в этом случае? Что произойдет, если сеттер color выберет другие свойства, которые еще не были установлены? Компилятор пока не видит все возможные варианты реализации, поэтому не имеет понятия, является ли color просто ivar или чем-то более сложным. Нет, это не сработает.

Реальная проблема - это "абстрактный класс, который может быть расширен в конкретном классе". Забудьте классы. Забудьте наследование. Swift - это все композиции и протоколы, а не наследование.

Итак, подумайте о примере, который вы описываете в комментариях (хотя в Cocoa нет "абстрактных классов" ). Предположим, что настройка цвета на самом деле представляет собой много кода, который вы не хотите дублировать. Это не проблема. Вам просто нужна функция.

import UIKit

protocol Thing {
    var color: UIColor {get set}
}

private extension Thing {
    static func colorForColor(color: UIColor) -> UIColor {
        // We don't really use the color directly. We have some complicated code that we don't want to repeat
        return color
    }
}

final class NamedThing: Thing {
    var name: String
    var color: UIColor

    init(name: String, color: UIColor) {
        self.name = name
        self.color = NamedThing.colorForColor(color)
    }
}

Поскольку точка вашего расширения предназначена для обработки частичной инициализации, просто дайте ему вычислить нужную вам часть. Не пытайтесь сделать его инициализатором в расширении, так как тогда он должен будет нести ответственность за инициализацию всего, и это очень сложно сделать правильно, когда вы смешиваете его с наследованием.

Ответ 3

Вот что я имел в виду для "класса делегата".

Это метод, который я использую для добавления хранимых переменных в класс с использованием протоколов.

class ManagedColors
{
   var color:UIColor
   // other related variables that need a common initialisation
   // ...
   init(color:UIColor)
   {
      self.color = color
      // common initialisations for the other variables
   }
}

protocol ManagedColorClass
{
    var managedColors:ManagedColors { get }
}

extension ManagedColorClass 
{    
    // makes properties of the delegate class accessible as if they belonged to the
    // class that uses the protocol
    var color:UIColor { 
                        get { return managedColors.color } 
                        set { managedColors.color = newValue }
                      }    
}


// NamedThing objects will be able to use .color as if it had been
// declared as a variable of the class
//
// if you add more properties to ManagedColors (and the ManagedColorHost protocol)
// all your classes will inherit the properties as if you had inherited from them through a superclass
// 
// This is an indirect way to achive multiple inheritance, or add additional STORED variables with
// a protocol
//
class NamedThing:ManagedColorClass 
{
    var name:String
    var managedColors:ManagedColors 

    init(name:String,color:UIColor) 
    {
        managedColors = ManagedColors(color:color)
        self.name = name
    }
}

let red = NamedThing(name:"red", color:UIColor.redColor())
print(" \(red.name)  \(red.color)")

Ответ 4

Я пришел к выводу, что вопрос является нескромным, поскольку расширение протокола не может динамически определять свойства (если оно не предоставляет значения по умолчанию для свойств или объявляет их как неявно развернутые). Чтобы разрешить это более ориентированным на протокол образом, для этого требуется другой подход, который по-прежнему включает в себя объявление и инициализацию всех переменных в конкретном классе, например:

import UIKit
protocol Colorable {
    var color: UIColor {get set}
}
protocol Nameable {
    var name: String {get set}
}
class ColoredNamedThing: Colorable, Nameable {
    var name: String
    var color: UIColor

    init(name: String, color: UIColor) {
        self.name = name
        self.color = color
    }
}

var coloredNamedThing = ColoredNamedThing(name: "Name", color: UIColor.redColor())

Спасибо @alain-t за ответ, который я собираюсь принять, поскольку он наиболее близко находит решение моего вопроса, несмотря на то, что он включает неявно развернутые свойства.

Спасибо @rob-napier также за ваш вклад.