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

Des*_*ume 122 initialization swift

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

class Person {
    var name:String

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

    init() {
        self.name = "John"
    }
}
Run Code Online (Sandbox Code Playgroud)

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

class Person {
    var name:String

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

    convenience init() {
        self.init(name: "John")
    }
}
Run Code Online (Sandbox Code Playgroud)

nhg*_*rif 221

现有的答案只讲述了一半的convenience故事.故事的另一半,即现有答案所涵盖的一半,回答了Desmond在评论中发布的问题:

为什么Swift会强迫我把它放在convenience我的初始化器前面,因为我需要self.init从它调用?`

我在这个答案中略微触及它,其中我详细介绍 Swift的几个初始化规则,但主要关注的是这个required词.但是,这个答案仍在解决与这个问题和这个答案相关的问题.我们必须了解Swift初始化器继承的工作原理.

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

为了清楚起见,未初始化的实例变量是任何未赋予默认值的实例变量(请记住,选项和隐式解包的选项会自动采用默认值nil).

所以在这种情况下:

class Foo {
    var a: Int
}
Run Code Online (Sandbox Code Playgroud)

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

class Foo {
    var a: Int = 0
}
Run Code Online (Sandbox Code Playgroud)

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

class Foo {
    var a: Int

    init(a: Int) {
        self.a = a
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,让我们看看如果我们进行子类化会发生什么Foo,是吗?

class Bar: Foo {
    var b: Int

    init(a: Int, b: Int) {
        self.b = b
        super.init(a: a)
    }
}
Run Code Online (Sandbox Code Playgroud)

对?我们添加了一个变量,我们添加了一个初始化器来设置一个值,b以便它编译.根据你来自哪种语言,你可能会期望它Bar继承了Foo初始化器,init(a: Int).但事实并非如此.怎么可能呢?怎样Fooinit(a: Int)知道如何分配一个值b变量Bar添加?它没有.因此,我们无法Bar使用无法初始化所有值的初始化程序初始化实例.

这与这有什么关系convenience

那么,让我们看一下初始化器继承的规则:

规则1

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

规则2

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

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

所以convenience关键字的作用是向我们指示哪些初始值设定项可以由添加没有默认值的实例变量的子类继承.

我们来看这个示例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)
    }
}
Run Code Online (Sandbox Code Playgroud)

注意我们convenience这里有三个初始化器.这意味着我们有三个可以继承的初始化器.我们有一个指定的初始化程序(指定的初始化程序只是任何不是便利初始化程序的初始化程序).

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

在此输入图像描述

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

class NonInheritor: Base {
    let c: Int

    init(a: Int, b: Int, c: Int) {
        self.c = c
        super.init(a: a, b: b)
    }
}
Run Code Online (Sandbox Code Playgroud)

我们继承自己Base.我们添加了自己的实例变量,但我们没有给它一个默认值,所以我们必须添加自己的初始化器.我们添加了一个,init(a: Int, b: Int, c: Int)但它与Base类的指定初始化程序的签名不匹配:init(a: Int, b: Int).这意味着,我们不会继承任何初始化器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)
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,除了我们直接在这个类中实现的两个初始化器之外,因为我们实现了初始化器匹配Base类的指定初始化器,所以我们继承了所有Base类的convenience初始化器:

在此输入图像描述

具有匹配签名的初始化程序被标记为的事实在convenience此处没有区别.它只意味着Inheritor只有一个指定的初始化程序.因此,如果我们继承Inheritor,我们只需要实现一个指定的初始化器,然后我们继承了Inheritor方便初始化器,这反过来意味着我们已经实现了所有Base的指定初始化器并且可以继承它的convenience初始化器.

  • 实际回答问题并遵循文档的唯一答案.如果我是OP,我会接受它. (13认同)
  • 你应该写一本书;) (11认同)
  • @nhgrif 为什么需要重新实现*所有*超类的指定初始化器来解锁便利初始化器?如果仅重新实现许多超类指定初始值设定项之一,那么编译器难道不能仅解锁使用它的便利初始值设定项吗? (2认同)
  • @IanWarburton 我不知道这个特定“为什么”的答案。您在评论的第二部分中的逻辑对我来说似乎很合理,但文档清楚地说明它是如何工作的,并且举出一个您在 Playground 中询问的示例来确认该行为与记录的内容相匹配。 (2认同)
  • 根据我作为 Swift 社区一员的经历,与大多数其他编译语言相比,语言编译相当复杂,这个社区中的许多人失去了对程序员 API 和编译器 API 的了解。有些决定确实值得怀疑,“便利”关键字就是其中之一。我个人认为在我的代码中没有任何有用的见解。就像枚举的“indirect”关键字一样,“@escaping”闭包和其他一些关键字。 (2认同)

Nat*_*ann 9

主要是清晰度.从你的第二个例子,

init(name: String) {
    self.name = name
}
Run Code Online (Sandbox Code Playgroud)

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

var gender: Gender?
Run Code Online (Sandbox Code Playgroud)

性别是一个枚举

enum Gender {
  case Male, Female
}
Run Code Online (Sandbox Code Playgroud)

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

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

convenience init(femaleWithName: String) {
   self.init(name: name)
   gender = .Female
}
Run Code Online (Sandbox Code Playgroud)

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

  • 因此,对于编译器来说,即使没有`convenience`关键字,我也会尝试使用多个初始值设定器,但Swift仍然会对它进行窃听.那不是我期待Apple的那种简单=) (2认同)
  • 这个答案什么也没回答。您说的是“澄清度”,但没有解释它如何使事情变得更清晰。 (2认同)

u54*_*54r 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}
Run Code Online (Sandbox Code Playgroud)

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

  • 删除了“便捷”关键字后,Swift会不会获得足够的信息来以完全相同的方式运行? (2认同)