如何在协议扩展中定义初始化器?

71
protocol Car {
     var wheels : Int { get set}

     init(wheels: Int)

}

extension Car {
    init(wheels: Int) {
        self.wheels = wheels
    }
}

当我写 self.wheels = wheels 时,出现了错误

Error: variable 'self' passed by reference before being initialized

如何在协议扩展中定义初始化器?


2
如果您正在使用简单的结构,则可以在协议中省略初始化程序,因为它将由编译器合成! - heyfrank
4个回答

89

如您所见,在这种情况下,这是行不通的,因为在编译时,必须确保在使用结构体/枚举/类之前初始化所有属性。

您可以将另一个初始化程序作为要求,以便编译器知道所有属性都已初始化:

protocol Car {
    var wheels : Int { get set }
    // make another initializer
    // (which you probably don't want to provide a default implementation)
    // a protocol requirement. Care about recursive initializer calls :)
    init()
    init(wheels: Int)

}

extension Car {
    // now you can provide a default implementation
    init(wheels: Int) {
        self.init()
        self.wheels = wheels
    }
}

// example usage

// mark as final
final class HoverCar: Car {
    var wheels = 0
    init() {}
}

let drivableHoverCar = HoverCar(wheels: 4)
drivableHoverCar.wheels // 4

自从Xcode 7.3 beta 1版本以来,它可以按预期与structs一起使用,但对于类而言则不是这样,因为如果它们不是final,那么协议中的init(wheels: Int)将成为必需的初始化程序,并且它可以被覆盖,因此不能通过扩展添加。解决方法(正如编译器所建议的):使class变为final
另一个解决方法(详细说明;没有final class):
要处理类而不使它们变为final,您还可以在协议中删除init(wheels: Int)的要求。它似乎与以前没有什么不同,但请考虑以下代码:
protocol Car {
    var wheels : Int { get set }
    init()
    // there is no   init(wheels: Int)
}

extension Car {
    init(wheels: Int) {
        self.init()
        print("Extension")
        self.wheels = wheels
    }
}

class HoverCar: Car {
    var wheels = 0
    required init() {}
    init(wheels: Int) {
        print("HoverCar")
        self.wheels = wheels
    }
}

// prints "HoverCar"
let drivableHoverCar = HoverCar(wheels: 4)

func makeNewCarFromCar<T: Car>(car: T) -> T {
    return T(wheels: car.wheels)
}

// prints "Extension"
makeNewCarFromCar(drivableHoverCar)

如果你从一个通用的上下文中创建一个Car,并且在调用init时只知道它是Car类型,则即使在HoverCar中定义了一个初始化程序,在扩展初始化程序也会被调用。这仅发生在协议中没有init(wheels: Int)要求的情况下。

如果添加它,您将面临以前使用final关键字声明该class的问题,但现在它会打印两次"HoverCar"。无论哪种方式,第二个问题可能永远不会发生,因此它可能是更好的解决方案。

附注:如果我犯了一些错误(代码、语言、语法等),欢迎您指正 :)


你能否添加类的声明以进一步说明你的解决方案? - Craig Grummitt
@CraigGrummitt 你指的是哪个类? Car 是一个协议(protocol),其声明已经完成。 - Qbyte
1
感谢展示这个类。我看到这个解决方案的主要问题是,你需要在变量声明或所需的init()中声明一个默认值来设置wheels,或者你需要将其声明为隐式解包可选项。这是我们针对该问题拥有的最好解决方案,但我想知道是否这本身就像是我们不应该在协议扩展中实现初始化器一样! :) - Craig Grummitt
1
@CraigGrummitt 我完全同意你的观点。强制要求设置车轮默认值的默认初始化要求有些不好。然而,Swift 3正在进行部分初始化器的讨论,这可能会解决这个问题。 - Qbyte
1
@Qbyte drivableHoverCar.wheels 打印的是 4,不是 6。 - slider
显示剩余5条评论

7

我的理解是这不可能实现,因为协议扩展无法知道符合该协议的类或结构体具有哪些属性 - 因此无法保证它们被正确初始化。

如果有方法可以绕过这个问题,我非常感兴趣! :)


6

@Qbyte是正确的。

另外,你可以查看我的Configurable

在那里我有一个Initable协议

public protocol Initable {
    // To make init in protocol extension work
    init()
}

public extension Initable {
    public init(@noescape block: Self -> Void) {
        self.init()
        block(self)
    }
}

为了符合它,接下来需要进行以下操作。
extension Robot: Initable { }

我有两种方法,一种是使用final关键字,另一种则是实现init方法。

final class Robot {
    var name: String?
    var cute = false
}

class Robot {
    var name: String?
    var cute = false

    required init() {

    }
}

1
很好。终于有一个“我的”链接,显示了必要的部分。 - Drew

2

虽然可能不一样,但在我的情况下,我使用了一个静态函数来返回该类的对象,而不是使用init。

protocol Serializable {
   static func object(fromJSON json:JSON) -> AnyObject?
}

class User {
    let name:String

    init(name:String) {
        self.name = name
    }
}

extension User:Serializable {

    static func object(fromJSON json:JSON) -> AnyObject? {
        guard let name = json["name"] else {
            return nil
        }

        return User(name:name)
     }

}

然后,为了创建对象,我会执行以下操作:
let user = User.object(fromJSON:json) as? User

我知道这不是最好的解决方案,但这是我能找到的最佳解决方案,以避免将商业模型与数据层耦合。

注意:我很懒,所以我直接在注释中编写了所有内容,如果有什么问题,请让我知道。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接