在快速、明确的示例中,init 与 init 之间的区别是什么

我很难理解两者之间的区别,或 convenience init的目的。

85997 次浏览

方便初始化器用于当你有一些具有很多属性的类,这使得总是用所有的变量来初始化它有点“痛苦”,所以你使用方便初始化器只是传递一些变量来初始化对象,剩下的用一个默认值来赋值。有一个非常好的视频在雷温德利希网站,不确定它是否免费,因为我有一个付费帐户。 这里是一个例子,你可以看到,而不是初始化我的对象与所有这些变量我只是给它一个标题。

struct Scene {
var minutes = 0
}


class Movie {
var title: String
var author: String
var date: Int
var scenes: [Scene]


init(title: String, author: String, date: Int) {
self.title = title
self.author = author
self.date = date
scenes = [Scene]()
}


convenience init(title:String) {
self.init(title:title, author: "Unknown", date:2016)
}


func addPage(page: Scene) {
scenes.append(page)
}
}




var myMovie = Movie(title: "my title") // Using convenicence initializer
var otherMovie = Movie(title: "My Title", author: "My Author", date: 12) // Using a long normal initializer

标准 init:

指定的初始值设定项是类的主初始值设定项 指定的初始值设定项会完全初始化 并调用适当的超类初始值设定项来继续 初始化过程沿着超类链向上。

返回文章页面

方便的初始值设定项是次要的,它支持 可以定义一个方便的初始值设定项来调用指定的 与方便的初始值设定项来自同一类的 将指定初始化程序的参数设置为默认值 还定义了一个方便的初始值设定项来创建该类的实例 用于特定的用例或输入值类型。

快速文档计算

简而言之,这意味着您可以使用方便的初始值设定项来使调用指定的初始值设定项更快、更“方便”。因此,方便的初始化程序需要使用 self.init而不是您可能在指定初始化程序的覆盖中看到的 super.init

伪代码示例:

init(param1, param2, param3, ... , paramN) {
// code
}


// can call this initializer and only enter one parameter,
// set the rest as defaults
convenience init(myParamN) {
self.init(defaultParam1, defaultParam2, defaultParam3, ... , myParamN)
}

我在创建自定义视图时经常使用这些方法,比如具有主要缺省值的长初始化器。医生解释得比我好,去看看!

下面是一个简单的例子,取自 苹果开发者门户网站

基本上指定的初始化器是 init(name: String),它确保所有存储的属性都被初始化。

init()方便初始化器不带任何参数,通过使用指定的初始化器将 name存储属性的值自动设置为 [Unnamed]

class Food {
let name: String


// MARK: - designated initializer
init(name: String) {
self.name = name
}


// MARK: - convenience initializer
convenience init() {
self.init(name: "[Unnamed]")
}
}


// MARK: - Examples
let food = Food(name: "Cheese") // name will be "Cheese"
let food = Food()               // name will be "[Unnamed]"

在处理具有至少少几个存储属性的大型类时,它非常有用。我建议在 苹果开发者门户网站。中阅读更多关于可选项和继承的内容

注: 阅读全文

指定的初始化器是类的主初始化器。指定的初始化器完全初始化该类引入的所有属性,并调用适当的超类初始化器将初始化过程继续到超类链。

方便的初始值设定项是次要的,它支持类的初始值设定项。您可以定义一个方便初始值设定项,以便从与方便初始值设定项相同的类别中调用指定的初始值设定项,并将指定初始值设定项的某些参数设定为预设值。

类的指定初始值设定项的编写方式与值类型的简单初始值设定项的编写方式相同:

init(parameters) {
statements
}

方便的初始值设定项以相同的样式编写,但是方便修饰符放在 init 关键字之前,用一个空格分隔:

convenience init(parameters) {
statements
}

一个实际的例子如下:

class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}
let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon”

Food 类中的 init (name: String)初始化器作为指定的初始化器提供,因为它确保新 Food 实例的所有存储属性都被完全初始化。Food 类没有超类,因此 init (name: String)初始化器不需要调用 super.init ()来完成初始化。

”Food 类还提供了一个方便的初始化程序 init () ,它没有参数。Init ()初始化无名氏通过向 Food 类的 init (name: String)委派名称值为[ Unname ]来为新食物提供默认值:

“let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]”

层次结构中的第二个类是 Food 的一个子类,名为 RecipeIngredent。“食谱配料”类对烹饪配方中的一种配料进行建模。它引入了一个名为 Quantity 的 Int 属性(除了从 Food 继承的 name 属性之外) ,并定义了两个用于创建 RecipeIngredent 实例的初始化器:

class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}

RecipeIngredent 类有一个指定的初始值设定项 init (name: String,Quantity: Int) ,它可用于填充新 RecipeIngredent 实例的所有属性。此初始值设定项首先将传递的数量参数分配给數量属性,该属性是 RecipeIngrement 引入的唯一新属性。这样做之后,初始化器将委托给 Food 类的 init (name: String)初始化器。

页数: 536摘自: 苹果公司“迅速编程语言(Swift 4)”iBooks. https://itunes.apple.com/pk/book/the-swift-programming-language-swift-4-0-3/id881256329?mt=11

因此,当您不需要为类指定每个属性时,它会派上用场。例如,如果我想创建所有以 HP 值100开始的冒险,我将使用下面的 方便 init并添加一个名称。这会大大减少代码。

class Adventure {


// Instance Properties


var name: String
var hp: Int
let maxHealth: Int = 100


// Optionals


var specialMove: String?


init(name: String, hp: Int) {


self.name = name
self.hp = hp
}


convenience init(name: String){
self.init(name: name, hp: 100)
}
}

在哪里方便的初始值设置优于设置默认参数值

对我来说,如果除了简单地为类属性设置默认值之外还有更多的事情要做,那么 convenience initializers就很有用。

具有指定 init ()的类实现

否则,我只需在 init定义中设置默认值,例如:

class Animal {


var race: String // enum might be better but I am using string for simplicity
var name: String
var legCount: Int


init(race: String = "Dog", name: String, legCount: Int = 4) {
self.race = race
self.name = name
self.legCount = legCount // will be 4 by default
}
}

使用方便的 init ()扩展类

然而,除了简单地设置默认值之外,可能还有更多的事情要做,这就是 convenience initializers派上用场的地方:

extension Animal {
convenience init(race: String, name: String) {
var legs: Int


if race == "Dog" {
legs = 4
} else if race == "Spider" {
legs = 8
} else {
fatalError("Race \(race) needs to be implemented!!")
}


// will initialize legCount automatically with correct number of legs if race is implemented
self.init(race: race, name: name, legCount: legs)
}
}

用法示例

// default init with all default values used
let myFirstDog = Animal(name: "Bello")


// convenience init for Spider as race
let mySpider = Animal(race: "Spider", name: "Itzy")


// default init with all parameters set by user
let myOctopus = Animal(race: "Octopus", name: "Octocat", legCount: 16)


// convenience init with Fatal error: Race AlienSpecies needs to be implemented!!
let myFault = Animal(race: "AlienSpecies", name: "HelloEarth")

方便的初始化器可以在 课程延长中定义,但标准的初始化器不能。

所有的答案听起来都不错,但是,让我们用一个简单的例子来理解它

class X{
var temp1
init(a: Int){
self.temp1 = a
}

现在,我们知道一个类可以继承另一个类,所以

class Z: X{
var temp2
init(a: Int, b: Int){
self.temp2 = b
super.init(a: a)
}

现在,在这种情况下,在为类 Z 创建实例时,必须同时提供值‘ a’和‘ b’。

let z = Z(a: 1, b: 2)

但是,如果您只想传递 b 的值,并且希望 rest 采用其他值的默认值,那么在这种情况下,您需要将其他值初始化为默认值。怎么等?因为你只需要在课堂之前把它设置好。

//This is inside the class Z, so consider it inside class Z's declaration
convenience init(b: Int){
self.init(a: 0, b: b)
}
convenience init(){
self.init(a: 0, b: 0)
}

现在,您可以创建类 Z 的实例,并为变量提供一些、全部或全部值。

let z1 = Z(b: 2)
let z2 = Z()

convenience init使用值初始化类成为可选的。

enter image description here

enter image description here

如果您的 用例调用 同一类中另一个初始值设定项中的初始值设定项,这是有意义的。

试着在 游乐场中这样做

class Player {
let name: String
let level: Int


init(name: String, level: Int) {
self.name = name
self.level = level
}
    

init(name: String) {
self.init(name: name, level: 0) //<- Call the initializer above?


//Sorry you can't do that. How about adding a convenience keyword?
}
}


Player(name:"LoseALot")


用方便关键字

class Player {
let name: String
let level: Int


init(name: String, level: Int) {
self.name = name
self.level = level
}
    

//Add the convenience keyword
convenience init(name: String) {
self.init(name: name, level: 0) //Yes! I am now allowed to call my fellow initializer!
}
}

如果您希望通过扩展类型来为类型创建自己的自定义初始值设定项,那么这也很有用。

class Player {
let name: String
let level: Int


init(name: String, level: Int) {
self.name = name
self.level = level
}
}


extension Player {
convenience init(name: String) {
self.init(name: name, level: 0)
}
}

我想为@Chris Graf 的例子再加两分——-

类必须在声明或“ init”方法中初始化所有存储属性(可选项不需要初始化)。方便 inits 工作在默认 init 之上,也就是说,它在调用默认 init 之前或默认 init 之后添加额外的设置。

“便利”初始化程序的优点

1.使用 init (//ー init 2,只有两个属性)方法也可以实现由“便利 init”实现的相同功能,但是我们需要再次初始化所有属性,而不是使用默认的 init (//ー init 1,只有两个属性)。原因是“ init”方法不允许调用“ self. init”,即使参数的数量不同。因此,代码复制将在那里。

2.额外的设置也可以在默认初始值设置器中完成。但是,如果只想在某些场景中初始化存储属性,并在其他场景中进行额外的设置,则可能需要两个不同的初始化器。但是如果像上面所说的那样声明了两个不同的 inits,那么我们将在两个 inits 中再次结束编写相同的代码。因此,当两个 init 都需要时,使用“ Convenience”init,并使用带有附加设置的 init (Convenience init)和只有存储属性初始化的 init。

注意: 方便 init ー必须始终调用 default init。原因是通过调用 Convenience init,类实例不会被初始化。所以在我们调用“ self. init ()”之前,“ self”不能在“ convenice init”中被访问。

class Animal {


var race: String
var name: String
var legCount: Int


init(race: String = "Dog", name: String, legCount: Int = 4)
{//—init 1
`enter code here`with all properties
self.race = race
self.name = name
self.legCount = legCount // will be 4 by default
print("came to init 1")
}
    

init(race: String, name: String) {//—init 2 with only two properties)
var legs = 0


if race == "Dog" {
legs = 4
} else if race == "Spider" {
legs = 8
}
legCount = legs
self.race = race
self.name = name
print("came to init 2")
}
    

convenience init(race: String) {
print("came to convenience init")
var legs = 0
let name = "default"
if race == "Dog" {
legs = 4
} else if race == "Spider" {
legs = 8
}
else {
fatalError("Race \(race) needs to be implemented!!")
}
            

// will initialize legCount automatically with correct number of legs if race is implemented
self.init(race: race, name: name, legCount: legs)
}
}


// default init1 with all default values used
let myFirstDog = Animal(name: "Bello")


// default init1 with all parameters set by user
let myOctopus = Animal(race: "Octopus", name: "Octocat", legCount: 16)


// default init2 with 2 parameters
let myOctopus1 = Animal(race:"Octopus1" , name:"Octocat1")


// convenience init for Spider as race
let mySpider = Animal(race: "Spider")

简单地说,方便的初始化程序

  • 使用它们自己的唯一签名重载 init 函数
  • 为指定的 init 函数提供灵活性
  • 提供为类属性提供默认值的能力
  • 一个类中可以有很多,但是它们都需要在某个时候委托给指定的 init 函数

正如 快速5基本训练中所解释的。