【问题标题】:Protocol Extension Initializer协议扩展初始化器
【发布时间】:2016-01-14 23:01:11
【问题描述】:

我想知道一个简单类中的初始化器的等效协议是什么,它只包含初始化功能并且只打算在具体类中扩展。

所以可能最简单的方法是显示代码 - 我正在寻找与以下内容等效的协议扩展:

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 问题中看到了建议的解决方案(例如 How to define initializers in a protocol extension?),但我不确定它们是否有效,也不确定它们是否专门解决了类初始化程序中附加参数的问题。

【问题讨论】:

    标签: swift initialization swift2 protocols


    【解决方案1】:

    您必须提供有效的 init 链来创建类的实例,这会限制您对协议中的初始化程序的选择。

    由于您的协议不能确定覆盖使用它的类的所有成员,因此您在协议中声明的任何初始化程序都需要将类的“未知”成员的初始化委托给该类提供的另一个初始化程序自己。

    我调整了您的示例以使用基本的 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
        }
    }
    

    【讨论】:

    • 符合 AnyObject 我们多余的、隐式展开的名称和颜色是危险的并且不需要(改为设置默认值)
    • 感谢您富有洞察力的 cmets,您似乎遇到了与我类似的障碍。我同意你关于是否值得麻烦的评论。我想我正在寻找以面向协议的方法实现上述问题的最佳实践。需要为 vars 提供默认值或使其隐式展开对我来说似乎不太方便,也不是最佳实践,这让我想知道这是否像使用协议一样不是解决此问题的正确方法。 @alain-t 你能用代码示例填写你对“委托类”的评论吗?
    • @CraigGrummitt swift中的所有变量(和/或常量)在使用之前必须有一些值(对于引用类型,在Swift中表示为类,值是引用,对于值类型它是价值自我)。不存在直接等效于 null 的是 Swift。 var i = Optional() 的默认值为 nil。 var j = ImplicitlyUnwrappedOptional() 给你相同的结果(都是枚举'type')....(继续下面)
    • @CraigGrummitt (continue) .... 但是如果你在某个地方使用它,编译器不会抱怨,而且你的代码很可能会崩溃。也就是说,为什么 var name:String! = nil 不是最好的解决方案。在您的示例中,最好的方法是使用非可选值。 var name: String = "" 应该可以工作,对我来说这似乎很方便:-) 以及最佳实践。
    【解决方案2】:
    protocol Thing {
        var color: UIColor {get set}
    }
    

    太棒了,没问题。

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

    没有。那永远行不通。这打破了太多的规则。第一个也是最重要的是,这并不一定会设置所有属性。考虑您的NamedThing。在这种情况下,name 是什么?如果color 设置器获取其他尚未设置的属性会发生什么?编译器还不能看到所有可能的实现,所以它不知道color 是否只是一个 ivar 或者更复杂的东西。不,这行不通。

    真正的问题是“可以在具体类中扩展的抽象类”。忘记上课。忘记继承。 Swift 完全是关于组合和协议,而不是继承。

    让我们考虑一下您在 cmets 中描述的示例(尽管在 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)
        }
    }
    

    既然你的扩展的目的是处理部分初始化,让它计算你需要的部分。不要试图让它成为扩展中的初始化器,因为那样它就必须负责初始化所有东西,而当你将它与继承混合时,这很难正确地做到。

    【讨论】:

    • 感谢您的回答 Rob。为了简单起见,我将我的代码简化为一个简单的示例,没有说明为什么它需要成为一个类,但在现实生活中的示例中,它是一个需要实现协议的类,因为它将继承一个 UIKit 类.样板代码旨在通过处理颜色设置的协议扩展来说明。
    • 顺便说一句,我刚刚通过您的评论“Swift 中没有抽象类”意识到我们可能对“抽象类”有不同的理解,如果我混淆了,请道歉对术语的错误理解的问题。我已将问题编辑得更清楚。
    • 这是什么 UIKit 类,它总是必须被子类化? (我们同意抽象类是什么;我只是想不出你在谈论的类。UIGestureRecognizer?)我强烈怀疑你面临的实际问题更容易以另一种方式解决。这是过度简化问题的经典问题。
    • 这只是一个思想实验,但我子类化的类是 UIView。感谢您更新您的代码,一些想法/cmets: 1. 我想遵循您建议的策略 NamedThing 不需要是最终的。 2. 我猜想在 Thing 扩展中设置静态方法与在颜色上设置计算属性几乎相同,并且可以在那里执行任何额外的计算。 3. 如果我们使用计算属性,最简单的解决方案可能就是省略协议的任何初始化,它们太有问题了。
    • 至于 final,IMO 你应该总是将事情标记为 final,除非你计划子类化它有特定的原因。它只是解决了很多潜在的头痛。 Swift 真的很讨厌继承。 ObjC 不喜欢 继承,但它主要通过忽略大多数极端情况并希望您正确编写代码来处理它。 Swift 更多时候要求代码是正确的,所以 ObjC 忽略的极端情况又回来咬你。
    【解决方案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)")
    

    【讨论】:

    • 在得知您的协议名称中包含“类”一词后,我按照您的示例进行操作,感谢您的输入。以另一种方式提出您的建议,您正在使用工厂类来构建我原来简单的“事物”类中的任何属性,对吗?我想我用它作为这个问题的解决方案的问题是,在它的基础上它再次使用了一个类。但这是一个有趣的例子,谢谢。
    • 它确实使用了一个类,但这里的好处是您可以在继承层次结构之外使用它,就像使用协议一样。我用它来实现“多重继承”,这就是为什么我用 Class 来命名我的协议。对于单个变量,这看起来有点矫枉过正,但是当您想将数据和行为添加到具有自己层次结构的多个类时,它开始变得更有意义。对于您的特定需求,我同意这可能不是最好的方法。
    【解决方案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 的贡献。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-09-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多