为什么Swift中甚至需要方便关键字?

时间:2015-06-17 15:39:49

标签: swift initialization

由于Swift支持方法和初始化程序重载,您可以将多个init放在一起并使用您认为方便的任何一个:

class Person {
    var name:String

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

    init() {
        self.name = "John"
    }
}

那么为什么convenience关键字会存在呢?是什么让以下更好?

class Person {
    var name:String

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

    convenience init() {
        self.init(name: "John")
    }
}

6 个答案:

答案 0 :(得分:208)

现有答案仅说明convenience故事的一半。故事的另一半,即现有答案所涵盖的一半,回答了Desmond在评论中发布的问题:

  

为什么Swift会强迫我将convenience放在我的初始化程序前面,因为我需要从中调用self.init

我在this answer中稍微触及了它,其中详细介绍了Swift初始化程序规则中的几个,但主要关注的是required字。但是,这个答案仍在解决与这个问题和这个答案相关的问题。我们必须了解Swift初始化器继承如何工作。

由于Swift不允许未初始化的变量,因此无法保证从您继承的类继承所有(或任何)初始值设定项。如果我们将任何未初始化的实例变量子类化并添加到子类中,我们就会停止继承初始化器。在我们添加自己的初始化器之前,编译器会对我们大喊大叫。

要清楚,未初始化的实例变量是没有给出默认值的任何实例变量(请记住,选项和隐式解包的选项会自动采用默认值nil)。

所以在这种情况下:

class Foo {
    var a: Int
}

a是未初始化的实例变量。除非我们给a一个默认值,否则不会编译:

class Foo {
    var a: Int = 0
}

或在初始化方法中初始化a

class Foo {
    var a: Int

    init(a: Int) {
        self.a = a
    }
}

现在,让我们看看如果我们继承Foo会发生什么,是吗?

class Bar: Foo {
    var b: Int

    init(a: Int, b: Int) {
        self.b = b
        super.init(a: a)
    }
}

右?我们添加了一个变量,我们添加了一个初始化器来将值设置为b,因此它将进行编译。根据您来自哪种语言,您可能希望Bar继承Foo的初始值设定项init(a: Int)。但事实并非如此。怎么可能呢? Foo的{​​{1}}如何知道如何为init(a: Int)添加的b变量分配值?它没有。因此,我们无法使用无法初始化所有值的初始化程序初始化Bar实例。

这与Bar有什么关系?

好吧,让我们看一下the rules on initializer inheritance

  

规则1

     

如果您的子类没有定义任何指定的初始值设定项,它会自动继承其所有超类指定的初始值设定项。

     

规则2

     

如果您的子类提供了所有超类指定初始化器的实现 - 通过按照规则1继承它们,或者通过提供自定义实现作为其定义的一部分 - 那么它会自动继承所有超类便捷初始化器。 / p>

注意规则2,它提到了便利初始化器。

那么convenience关键字所做的事情是向我们指明哪些初始值设定项可以由添加实例变量而没有默认值的子类继承

我们来看这个例子convenience class:

Base

请注意,我们这里有三个class Base { let a: Int let b: Int init(a: Int, b: Int) { self.a = a self.b = b } convenience init() { self.init(a: 0, b: 0) } convenience init(a: Int) { self.init(a: a, b: 0) } convenience init(b: Int) { self.init(a: 0, b: b) } } 初始值设定项。这意味着我们有三个可以继承的初始化器。我们有一个指定的初始化程序(指定的初始化程序只是任何不是便利初始化程序的初始化程序)。

我们可以用四种不同的方式实例化基类的实例:

enter image description here

所以,让我们创建一个子类。

convenience

我们继承自class NonInheritor: Base { let c: Int init(a: Int, b: Int, c: Int) { self.c = c super.init(a: a, b: b) } } 。我们添加了自己的实例变量,但我们没有给它一个默认值,所以我们必须添加自己的初始化器。我们添加了一个Base,但它与init(a: Int, b: Int, c: Int)类的指定初始值设定项的签名不匹配:Base。这意味着,我们不会从init(a: Int, b: Int)继承任何初始值设定项:

enter image description here

那么,如果我们从Base继承,会发生什么,但我们继续实施了一个与Base指定的初始值设定项匹配的初始值设定项?

Base

现在,除了我们直接在这个类中实现的两个初始值设定项之外,因为我们实现了一个匹配class Inheritor: Base { let c: Int init(a: Int, b: Int, c: Int) { self.c = c super.init(a: a, b: b) } convenience override init(a: Int, b: Int) { self.init(a: a, b: b, c: 0) } } 类的指定初始值设定项的初始值设定项,我们可以继承所有Base类的Base初始化:

enter image description here

具有匹配签名的初始化程序标记为convenience的事实在此处没有区别。它只表示convenience只有一个指定的初始值设定项。因此,如果我们继承Inheritor,我们只需要实现一个指定的初始化程序,然后我们继承Inheritor的便利初始化程序,这反过来意味着我们已经实现了所有{ {1}}指定的初始值设定项,可以继承其Inheritor初始值设定项。

答案 1 :(得分:9)

大部分清晰度。从你的第二个例子,

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

是必需的或指定。它必须初始化所有常量和变量。便捷初始化程序是可选的,通常可用于简化初始化。例如,假设您的Person类具有可选的变量gender:

var gender: Gender?

性别是枚举

enum Gender {
  case Male, Female
}

你可以拥有像这样的便利初始化者

convenience init(maleWithName: String) {
   self.init(name: name)
   gender = .Male
}

convenience init(femaleWithName: String) {
   self.init(name: name)
   gender = .Female
}

便捷初始化程序必须调用指定的或必需的初始值设定项。如果您的类是子类,则必须在其初始化中调用super.init()

答案 2 :(得分:7)

嗯,首先我想到的是它在类继承中用于代码组织和可读性。继续你的Person课程,想一想像这样的场景

class Person{
    var name: String
    init(name: String){
        self.name = name
    }

    convenience init(){
        self.init(name: "Unknown")
    }
}


class Employee: Person{
    var salary: Double
    init(name:String, salary:Double){
        self.salary = salary
        super.init(name: name)
    }

    override convenience init(name: String) {
        self.init(name:name, salary: 0)
    }
}

let employee1 = Employee() // {{name "Unknown"} salary 0}
let john = Employee(name: "John") // {{name "John"} salary 0}
let jane = Employee(name: "Jane", salary: 700) // {{name "Jane"} salary 700}

使用便利初始化程序,我可以创建一个没有值的Employee()对象,因此单词convenience

答案 3 :(得分:1)

除了其他用户在此解释的观点之外,我还有一点了解。

我强烈感受到便利初始化程序和扩展程序之间的联系。至于我,当我想修改(在大多数情况下简化或简单)初始化现有类时,初始化器是最有用的。

例如,您使用的某些第三方类具有init,其中包含四个参数,但在您的应用程序中,最后两个具有相同的值。为了避免更多输入并使代码清晰,您可以定义只有两个参数的convenience init,并在其中使用last参数调用self.init并使用默认值。

答案 4 :(得分:1)

根据Swift 2.1 documentationconvenience初始化程序必须遵守一些特定规则:

  1. convenience初始化程序只能在同一个程序中调用初始化程序 课程,不在超级课程中(只有跨越,而不是上课)

  2. convenience初始化程序必须调用指定的初始值设定项 链中的某个地方

  3. convenience初始值设定项无法更改 ANY 属性 调用另一个初始化程序 - 而指定初始化程序 必须初始化当前类引入的属性 在调用另一个初始化程序之前。

  4. 通过使用convenience关键字,Swift编译器知道它必须检查这些条件 - 否则它不能。

答案 5 :(得分:1)

一个类可以有多个指定的初始值设定项。便捷初始化器是辅助初始化器,必须调用同一类的指定初始化器。