Инициализатор расширения протокола
Я хотел бы знать, что эквивалентно протоколу для инициализатора в простом классе, который содержит только функциональные возможности инициализации и предназначен только для расширения в конкретном классе.
Так что, наверное, проще всего показать код - я ищу расширение протокола, эквивалентное следующему:
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 также за ваш вклад.