Swift学习笔记十三

时间:2022-09-17 19:24:18

初始化

初始化是一个在类、结构体或枚举的实例对象创建之前,对它进行预处理的过程,包括给那个对象的每一个存储式属性设定初始值,以及进行一些其他的准备操作。

通过定义初始化器(initializer)来实现初始化过程,它就像一种在创建该类型实例对象时会自动调用的方法。不同于OC的初始化,Swift中并不需要返回值。

类类型的实例对象还可以定义析构器(deinitializer),它在实例对象被销毁之前执行一些自定义的清理工作。

类和结构体必须在初始化时给所有的存储式属性设定合适的初始化值,在实例对象创建后,存储式属性不允许处于不确定的状态。可以通过初始化给存储式属性设定初始值,也可以直接在定义它们的时候就给定默认值。(初始化中设定属性的值会直接改变它的值,而不会触发任何该属性的观察者)

初始化器

当创建某个类型的实例对象时,会自动调用初始化器,它的最简形式,就像是一个用init关键字的没有任何参数的实例方法:

struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
println("The default temperature is \(f.temperature)° Fahrenheit")
// prints "The default temperature is 32.0° Fahrenheit”

这里定义了一个没有参数的初始化方法init,它没类实例对象创建的时候,将存储属性的值设定为一个定值。

如前所述,存储属性的初始值可以在初始化方法中设定,也可以作为定义的一部分直接给出,特别地,如果每次初始化的时候都是给属性赋的同一个值,那么最好将这种初始化方式改为直接在属性定义的时候就给出默认值,这样做的好处是,在定义的时候就给出默认值更为简洁明了,语义上更为连贯,并且有时候Swift可以直接根据默认值推断出属性的数据类型,默认值还有助于更好地利用默认初始化器以及初始化器继承,稍后会介绍。

因此上面的例子可以简写为:

struct Fahrenheit {
var temperature = 32.0
}

自定义初始化

你可以自定义初始化过程,比如设定输入参数、设定可选属性类型、给常量属性赋值等。

初始化参数

在定义初始化器的时候你就可以给出初始化参数来确定自定义初始化过程中的值的类型和名称。初始化参数的语法和作用与函数/方法的参数完全一样,比如:

struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
}
let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius is 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0

请注意例子中自定义初始化方法的参数名,这里定义了两个不同的自定义初始化方法,它们的参数都指定了外部和内部名称。

初始化方法和普通的函数或者方法有一点区别,就是在调用的时候并没有写它的名称,或者说它们的名字都是init,因此当自定义多个初始化方法的时候,参数名称就变得尤为重要,这是在调用的时候用来确定究竟选用哪个初始化方法的依据,因此,上边的例子中两个初始化方法给参数指定了不同的外部参数名称,在调用的时候就能知道究竟是调用哪个初始化方法了,特别地,当你没有给参数指定外部名称的时候,Swift会自动给初始化方法的参数指定外部名称,它和其内部名称一样,就好像你在每个参数前加上了(#)符号。因此,如果初始化方法指定了参数外部名称(自动或手动),在调用初始化方法的时候,参数的外部名称是必须要写明的,省略外部名称会触发编译错误。

如果你不想给某个初始化方法的参数指定外部名称,那么用下划线来代替:

struct Celsius {
var temperatureInCelsius: Double
init(fromFahrenheit fahrenheit: Double) {
temperatureInCelsius = (fahrenheit - 32.0) / 1.8
}
init(fromKelvin kelvin: Double) {
temperatureInCelsius = kelvin - 273.15
}
init(_ celsius: Double) {
temperatureInCelsius = celsius
}
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0

可选属性类型

如果你希望有一个存储属性可以是“没有值”的--比如可能在初始化时没法确定它的值,或者是它在以后的某个时候可能是“没有值”的,那么你可以把这个属性声明为可选类型(optional type)。

可选类型的属性默认会被赋值为nil,标明它现在是“没有值”的,比如:

class SurveyQuestion {
var text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
println(text)
}
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese.”

这里,只有当问题提出之后,才能知道如何回答,所以response被设定为了可选String。

给常量属性特定值

在初始化过程中的任何时候,你都可以设定或者改变常量属性的值,只要初始化过程结束,你就不能再改变常量属性的值了。

对类对象而言,常量属性只能在定义类的初始化过程中改变,即使在子类中也不能改变。

默认初始化器

对于那些给所有属性设定了默认值并且一个初始化方法都没有提供的结构或者基类,Swift都自动提供了一个默认初始化方法,这个方法只是简单地创建了一个新实例对象,然后把所有的属性设定为他们的默认值。比如:

class ShoppingListItem {
var name: String?
var quantity =
var purchased = false
}
var item = ShoppingListItem()

结构体类型的逐成员初始化

如果没有定义任何初始化方法,那么结构体类型自动获得一个初始化方法,不同于上面介绍的默认初始化方法,结构体的这个初始化方法是逐成员的,即使有的属性并没有默认值。这种初始化方法是初始化新实例对象所有属性的简写形式,比如:

struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

值类型的初始化代理

初始化方法可以调用其他的初始化方法来作为自己初始化的一部分,这叫做初始化代理,它提高了代码的复用性,避免了很多初始化代码的重复。

至于初始化代理如何工作以及被允许以何种形式,对于值类型和类类型而言是不同的。

值类型(结构体和枚举)是不能继承的,因此它们的初始化代理非常简单,因为它们只能够代理给另一个它们自己提供的初始化方法。然而类可以从其他类继承,这意味着类在初始化的时候还必须确保它们继承来的存储属性也都能够被赋予合适的值。

对于值类型,在自定义初始化方法时可以用self.init来引用其他同类型的初始化方法。只能在初始化方法中使用self.init。

注意:如果你给值类型自定义了初始化方法,那么你就不能再访问默认的初始化方法了(结构体就不能访问逐成员初始化方法),这个约束避免了开发者不小心使用了默认初始化方法而绕过了复杂但是必要的自定义初始化方法。如果你希望你的数据类型同时被默认的初始化方法和你自定义的初始化方法初始化,你应该把你的自定义初始化方法写在扩展里边而不是在原始的类型声明里。

struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
} struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / )
let originY = center.y - (size.height / )
self.init(origin: Point(x: originX, y: originY), size: size) //注意这里
}
}

这里Rect的第一个初始化方法init()其实和如果没有提供任何初始化方法时系统给出的默认初始化方法是一样的,它就是简单生成一个新的实例对象,属性值都设为默认值。第二个初始化方法和逐参数初始化方法一样,第三个初始化方法是自定义的,它调用了已有的初始化方法作为其初始化的一部分。

类继承与初始化

所有类的属性--包括他从超类继承来的所有属性,都必须在初始化的时候赋予合适的值。

Swift给类定义了两种初始化方法来确保所有的属性都被赋予了合适的值,指派初始化(Designated Initializer)和便捷初始化(Convenience Initializer)。

指派初始化是类的主要初始化方式,它把类本身定义的属性全部初始化,然后调用超类的初始化方法来依次完成继承链上超类的初始化。类一般没有很多指派初始化方法,最通常的情况是只拥有一个。每个类必须拥有一个指派初始化方法,有些情况下,通过继承超类的指派初始化方法就可以满足这个要求。

便捷初始化是类的次要的一种初始化方式,通过将某些参数设定默认值的方式调用同一个类的指派初始化方法,就可以定义一个便捷初始化方法,你也可以为了创建一个有特殊用途的实例对象而定义便捷初始化方法。便捷初始化方法不是必需的。

指派初始化方法的语法和值类型的简单初始化语法一样:init(){}

便捷初始化方法的语法只需要在前面加上convenience关键字即可:convenience init(){}

为了简化指派初始化方法和便捷初始化方法之间的关系,Swift给初始化方法之间的代理制定了以下三条规则:

》指派初始化方法必须调用它的直接超类的指派初始化方法

》便捷初始化方法必须调用同一个类的其他初始化方法

》便捷初始化方法必须最终调用一个指派初始化方法

这三条规则可以简化为:指派初始化方法必须向上代理,便捷初始化方法必须横向代理。

Swift中类的初始化是一个分两阶段的过程,第一个阶段每个存储属性被声明它的类赋予初始值,当所有存储属性的值都被设定初始状态之后,第二阶段开始了,每个类都可以在实例对象被创建完成之前进一步自定义它的存储属性的值。

这个两步走的初始化过程既保证了初始化的安全,又给予继承链上所有类足够的灵活性。它阻止了在属性初始化之前对它们的访问,也阻止了从其他初始化方法中错误地将属性值改变了。

Swift编译器在两步走初始化过程中执行了四次安全检查以确保初始化正确完成:

》指派初始化方法必须在向上(超类的指派初始化方法)代理之前确保所有该类声明的属性被正确初始化

  如前所述,一个对象的内存分配只有在所有属性的初始状态都确定了之后才能完成,因此,指派初始化方法必须确保这条规则

》指派初始化方法必须在给继承属性赋值之前向上代理。

  如果不这样,初始化方法赋给继承属性的新值会被超类的初始化方法覆盖掉

》便捷初始化方法必须在给任何属性(包括当前类声明的属性)赋值之前横向代理

  如果不这样,它给属性赋的新值会被指派初始化方法覆盖掉(便捷初始化必须最终调用指派初始化方法)

》任何初始化方法在初始化的第一个步骤完成之前都不允许调用实例方法、访问实例的任何属性值或者引用self

  在第一阶段完成之前,类实例并不是完全合法的。

基于上面四次安全检查,两步走初始化阶段是按如下流程完成的:

------------------------

【第一阶段】

》类的一个指派或者便捷初始化方法被调用

》类的新实例对象的内存被分配,但该内存并没有被初始化

》一个指派初始化方法确保该类所有的存储属性都被正确的初始化了,这些存储属性的内存现在被初始化了

》指派初始化方法将控制权转交给超类的指派初始化方法,来对超类的存数属性执行类似处理

》这个过程一直在继承链上持续到基类为止

》一旦到达继承链顶端的类,这个基类可以确保所有的存储属性都有正确的初始化值了,实例对象被分配的内存被认为是完全初始化了,第一阶段结束

【第二阶段】

》从继承链顶端开始往回,链上的每一个指派初始化方法都有机会对实例对象进一步自定义,初始化方法现在可以访问self属性,改变属性值,调用类型方法等等

》最后,继承链中的任何便捷初始化方法都有机会通过self对实例对象进行进一步自定义

------------------------

初始化方法的继承和重载

和OC不一样,Swift中的子类并不会默认从超类中继承初始化方法。

如果你希望在子类中使用和超类一样的一个或多个初始化方法,你可以在子类中自定义初始化方法将这些初始化方法集中实现。

如果你在子类中自定义的指派初始化方法和超类中的指派初始化方法是匹配的,那么你就是重载了这个初始化方法,因此,你必须在子类的初始化方法之前写上override关键字,即使你是重载的系统提供的默认初始化方法。

就像重载属性、方法和下标一样,override关键字会促使Swift检查超类是否有相匹配的指派初始化方法以及重载时参数是否正确。

在重载超类指派初始化方法时,你必须写override关键字,即使你是用便捷初始化方法重载的也一样。相反,如果你在子类中定义的初始化方法和超类中便捷初始化方法匹配,你不需要写override关键字,因为根据前面的规则,你不能在子类中直接调用父类的便捷初始化方法,因此严格来说,子类并不是提供了父类初始化方法的重载版本。

比如,定义一个基类,他有一个存储属性,在定义的时候给定了默认值,一个只读计算属性:

class Vehicle {
var numberOfWheels =
var description: String {
return "\(numberOfWheels) wheel(s)"
}
}

这个基类给所有存储属性提供了默认值,并且没有提供自定义初始化方法,因此自动获得了默认初始化方法,这个方法就是简单地创建一个Vehicle实例对象,将其属性设置为默认值:

let vehicle = Vehicle()
println("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)

再定义一个类继承它,并重载初始化方法(尽管该初始化方法是系统给定的):

class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels =
}
}

注意,在子类的指派初始化方法中,是在给继承属性赋值之前向上代理的,这遵循了前面提到的规则。

如前所述,默认情况下,子类并不会继承超类的初始化方法,但是,当某些条件满足的时候,超类的初始化方法会自动地被子类继承,在实际开发中,这意味着你在很多情况下都不用重载超类的初始化方法,只要是安全的,就可以直接从超类继承初始化方法。

假设你为子类的所有新属性都提供了默认值,如下两个规则会自动生效:

》如果你的子类没有提供任何指派初始化方法,它会自动继承所有它超类的指派初始化方法

》如果你的子类提供了所有它超类的指派初始化方法的实现--要么通过上面规则1继承,或者自己提供另外的实现--那么它自动继承所有它超类的便捷初始化方法。

即使你的子类提供了另外的便捷初始化方法,这些规则也会生效。即使你是将超类的指派初始化方法在子类中实现为便捷初始化方法,这同样满足规则2的条件。

实际运用指派初始化和便捷初始化

下面的示例定义了具备继承关系的三个类:Food, RecipeIngredient, 和 ShoppingListItem,并演示了它们的初始化方法是如何互相作用的。

基类是Food,有一个存储属性和两个初始化方法:

class Food {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[Unnamed]")
}
}

类不像结构体,不会自动获得逐成员的初始化方法,因此它自己提供了接受一个参数的初始化方法,可以给唯一的存储属性设定值。因此,第一个初始化方法就是指派初始化方法,因为它确保了类的新实例对象的所有存储属性都被正确初始化了。Food是基类,因此它的指派初始化不用向上代理。它的第二个初始化方法就是便捷初始化方法了,它调用了本类中的其他初始化方法并且最终调用了指派初始化方法,即横向代理,它不接受参数,而给存储属性设定一个固定值。

RecipeIngredient继承自Food类,它定义了自己的新存储属性quantity,当然,它也集成了Food的存储属性name,它还定义了两个初始化方法:

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: )
}
}

RecipeIngredient自定义的第一个初始化方法明显是指派初始化方法,它确保了自己所有存储属性都被正确初始化,并且是向上代理的。同时,它还自定义了一个便捷初始化方法,这个方法虽然本身是便捷初始化方法,但是它与超类的指派初始化方法是匹配的,应该认为是将超类的指派初始化方法重载为了便捷初始化方法,因此必须加上override关键字。

根据上面的规则二,如果子类提供了所有它超类的指派初始化方法的实现(即使是用便捷初始化方法实现),它自动继承了超类的所有便捷初始化方法。

在这个例子中,RecipeIngredient的超类是Food,Food只有一个便捷初始化方法init(),根据规则二,它被RecipeIngredient继承了,RecipeIngredient中被继承的init()实际工作情况与它在Food类中是一样的,唯一一点不同的是它在横向代理的时候不是调用的Food的指派初始化方法,而是RecipeIngredient中的对应指派初始化方法了。

可以有如下三种方式来创建子类的实例对象:

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: )

继承链上的第三级是继承自RecipeIngredient的ShoppingListItem类,它定义了一个自己的新存储属性以及一个只读计算属性:

class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ✔" : " ✘"
return output
}
}

ShoppingListItem并没有提供初始化方法来初始化存储属性purchased,因为作为定义的一部分,它已经被初始化为一个定值了。

ShoppingListItem为它自己的所有存储属性提供了初始值,并且它自己没有提供任何初始化方法,根据上面的规则一,它自动继承了它超类的所有指派和便捷初始化方法。

你可以用它继承的三个初始化方法创建新的实例对象:

var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name: "Bacon"),
ShoppingListItem(name: "Eggs", quantity: ),
]
breakfastList[].name = "Orange juice"
breakfastList[].purchased = true
for item in breakfastList {
println(item.description)
}
// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘

允许失败的初始化

有时候会需要定义一个初始化方法允许失败的类、结构或枚举。这个失败可能是很多原因导致的,比如非法的初始化参数值、缺少必要的外部资源等等。

为了应对这种可能导致初始化失败的情况,可以定义一个或多个允许失败的初始化方法。它的语法是在init关键字后面加上一个问号。

注意:你不能同时定义两个拥有相同参数名称和类型的一个允许失败一个不允许失败的初始化方法。

允许失败的初始化方法返回一个可选类型(optional type),在允许失败的初始化内,如果条件触发了初始化失败,那么你就在那里返回nil。

注意:严格来说,初始化方法并不会返回任何值,它只是确保当初始化结束的时候self已经被完全和正确地初始化了,所以尽管你是用return nil来触发初始化失败,但是当初始化成功的时候你不需要用return关键字。

如下的例子定义了一个Animal结构体,它定义了一个允许失败的初始化方法:

struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
let someCreature = Animal(species: "Giraffe")
// someCreature is of type Animal?, not Animal if let giraffe = someCreature {
println("An animal was initialized with a species of \(giraffe.species)")
}
// prints "An animal was initialized with a species of Giraffe”
let anonymousCreature = Animal(species: "")
// anonymousCreature is of type Animal?, not Animal if anonymousCreature == nil {
println("The anonymous creature could not be initialized")
}
// prints "The anonymous creature could not be initialized”

注意:这里在允许失败的初始化方法中,检查species是否为空和检查它是否为nil是不一样的,检查是否为nil是在它为可选类型(optional string type)时才用的。

枚举类型的可失败初始化方法

你可以用可失败的初始化方法来根据一个或多个参数来选择合适的枚举成员,如果参数不能匹配一个合适的枚举成员,那么就触发初始化方法失败。比如:

enum TemperatureUnit {
case Kelvin, Celsius, Fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil
}
}
}

具有原始值(raw value)的枚举会自动获得允许失败的初始化方法:init?(rawValue:),它接受一个名为rawValue的对应原始值类型的参数,如果发现有一个匹配项,就返回那个枚举成员,否则就触发初始化失败。比如上面的例子可以改为拥有原始值的枚举:

enum TemperatureUnit: Character {
case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
} let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
println("This is a defined temperature unit, so initialization succeeded.")
}
// prints "This is a defined temperature unit, so initialization succeeded." let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
println("This is not a defined temperature unit, so initialization failed.")
}
// prints "This is not a defined temperature unit, so initialization failed.”

类的可失败初始化方法

值类型(结构体、枚举)的可失败初始化方法可以在其内部的任何地方触发失败,比如上面的Animal例子中,在存储属性species还没有被初始化的时候,就可以返回nil以触发初始化失败了,然而,对于类来说,只能在所有该类定义的存储属性都被初始化并且初始化代理都完成之后,才能触发初始化失败。

下边的例子展示了在类的可失败初始化方法中如何显示地展开可选项值以满足这个条件:

class Product {
let name: String!
init?(name: String) {
self.name = name
if name.isEmpty { return nil }
}
}

这个Product类和前面定义的Animal结构体很像,Product有一个存储属性name不能为空,为了强化这个需求,Product使用了可失败的初始化方法来确保在初始化成功时name不是为空的。

Product是个类,而不是结构体,因此,在触发初始化失败之前,所有它定义的存储属性都必须拥有初始化值。

在这个例子中,Product的name被定义为隐式展开的可选String类型。因为它是可选类型,意味着在被初始化赋值之前它拥有默认值nil,这也就意味着Product所定义的所有存储属性都具备初始值了,因此,在可失败初始化方法中,可以在给name赋值之前就判断其是否为空并触发初始化失败。

因为name是常量属性,因此一旦初始化成功,你就可以确定它一定是一个非nil值,尽管它被定义为隐式展开可选类型,你始终可以获取它隐式展开的值,而无需检查:

if let bowTie = Product(name: "bow tie") {
// no need to check if bowTie.name == nil
println("The product's name is \(bowTie.name)")
}
// prints "The product's name is bow tie”

初始化失败的传递

类、结构体或枚举的可失败初始化都可以代理给这个类、结构或枚举的另一个可失败初始化方法。类似地,子类的可失败初始化可以代理超类的可失败初始化方法。不管哪种情况,如果你代理的另一个可失败初始化方法触发了失败,那么整个初始化直接失败,不会再执行任何其他剩下的初始化代码。

下面的例子定义了一个继承自Product的子类CarItem,它定义了一个存储属性quantity并确保它的值不小于1:

class CartItem: Product {
let quantity: Int!
init?(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
if quantity < { return nil }
}
}

这里的quantity属性被定义为隐式展开可选类型,因此在被赋值之前它具备默认值nil,CarItem在给自己定义的存储属性赋值之后,向上代理了超类的初始化方法,这满足了类的可失败初始化方法规则。如果超类触发了初始化失败,CarItem的初始化会立即触发失败,不会再执行剩下的代码,如果超类初始化成功,CarItem的初始化方法确保它的quantity不小于1:

if let twoSocks = CartItem(name: "sock", quantity: ) {
println("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// prints "Item: sock, quantity: 2” if let zeroShirts = CartItem(name: "shirt", quantity: ) {
println("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
println("Unable to initialize zero shirts")
}
// prints "Unable to initialize zero shirts” if let oneUnnamed = CartItem(name: "", quantity: ) {
println("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
println("Unable to initialize one unnamed product")
}
// prints "Unable to initialize one unnamed product”

重载可失败初始化方法

在子类中可以重载超类中的可失败初始化方法,并且,你可以在子类中用不可失败初始化方法来重载超类中的可失败初始化方法。这让你可以定义一个初始化不允许失败的类,即使她的超类的初始化方法是可失败的。反之则不行,不能用不可失败初始化方法代理超类中可失败初始化方法。

如果你在子类中用不可失败初始化方法重载超类中的可失败初始化方法,子类的初始化方法就不能向上代理超类的初始化方法,一个不可失败初始化方法永远不能代理一个可失败初始化方法。

一下例子定义了一个类Document,它可以用一个要么非空字符串要么nil的name属性来初始化:

class Document {
var name: String?
// this initializer creates a document with a nil name value
init() {}
// this initializer creates a document with a non-empty name value
init?(name: String) {
self.name = name
if name.isEmpty { return nil }
}
}

接下来定义一个继承自Document的子类AutomaticallyNamedDocument,它把Document定义的两个指派初始化方法都重载了:

class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "[Untitled]"
}
override init(name: String) {
super.init() //注意这里没有代理超类中的可失败初始化方法
if name.isEmpty {
self.name = "[Untitled]"
} else {
self.name = name
}
}
}

AutomaticallyNamedDocument并没有可失败初始化方法,相反,它总是创建一个新实例对象,并确保name不会为空。

“init!”式可失败初始化

你可以通过在【init?】的方式定义可失败的初始化方法,它创建一个对应类型的可选(optional)类型,同时,你还可以通过【init!】来定义可失败的初始化方法,它创建一个对应类型的隐式展开的可选项类型。

你可以从init?代理init!,反之亦然,你可以用init?重载init!,反之亦然,你可以从init代理init!,尽管这样做在init!导致初始化失败时会触发断言。

必须的初始化(Required Initializers)

在类的初始化方法前面加上required关键字来标明该类的每个子类都必须实现这个初始化方法,并且在子类的这个方法前面也必须加上required关键字来标明在继承链上更远的子类也必须实现这个初始化方法,当重载一个required初始化方法时,你不需要在前面加上override关键字。如果你能够通过继承来的初始化方法满足条件,你不需要显示地实现required的初始化方法。

用闭包或者函数给属性设定默认值

如果一个存储属性需要一些自定义或者其他处理,你可以用一个闭包或者全局函数来为该属性提供自定义的默认值。当拥有那个属性的实例对象被初始化,这个闭包或者函数就被调用了,它的返回值就被作为默认值赋给了属性。这些闭包或函数其实首先创建了一个该属性类型的临时值,然后将该值定制为需要的初始状态,最后将这个临时值返回,作为初始值赋给属性。大概形式如下:

class SomeClass {
let someProperty: SomeType = {
// create a default value for someProperty inside this closure
// someValue must be of the same type as SomeType
return someValue
}()
}

这里闭包的后面紧跟了一组空括号,它通知Swift立即执行这个闭包。如果没有这对括号,那么是把闭包本身赋值给了属性,而非它的返回值。

注意:当你用闭包初始化一个属性时,记住在闭包执行时,实例对象还没有被初始化,因此在闭包中不能访问任何其他属性,即使它们有默认值也不行,你也不能访问隐式属性self或者调用实例方法。

struct Checkerboard {
let boardColors: [Bool] = {
var temporaryBoard = [Bool]()
var isBlack = false
for i in ... {
for j in ... {
temporaryBoard.append(isBlack)
isBlack = !isBlack
}
isBlack = !isBlack
}
return temporaryBoard
}()
}

这里定义的结构体有一个常量存储属性boardColors,它是一个Bool型数组,在初始化的时候,它被初始化为包含100个元素,他们是交替存储的true和false。

Swift学习笔记十三的更多相关文章

  1. Swift学习笔记十三:继承

    一个类能够继承(inherit)还有一个类的方法(methods),属性(property)和其他特性 一.基本的语法 class Human{ var name :String init(){ na ...

  2. 【swift学习笔记】二&period;页面转跳数据回传

    上一篇我们介绍了页面转跳:[swift学习笔记]一.页面转跳的条件判断和传值 这一篇说一下如何把数据回传回父页面,如下图所示,这个例子很简单,只是把传过去的数据加上了"回传"两个字 ...

  3. python3&period;4学习笔记&lpar;十三&rpar; 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息,抓取*网新闻内容

    python3.4学习笔记(十三) 网络爬虫实例代码,使用pyspider抓取多牛投资吧里面的文章信息PySpider:一个国人编写的强大的网络爬虫系统并带有强大的WebUI,采用Python语言编写 ...

  4. java之jvm学习笔记十三&lpar;jvm基本结构&rpar;

    java之jvm学习笔记十三(jvm基本结构) 这一节,主要来学习jvm的基本结构,也就是概述.说是概述,内容很多,而且概念量也很大,不过关于概念方面,你不用担心,我完全有信心,让概念在你的脑子里变成 ...

  5. Swift学习笔记(一)搭配环境以及代码运行成功

    原文:Swift学习笔记(一)搭配环境以及代码运行成功 1.Swift是啥? 百度去!度娘告诉你它是苹果最新推出的编程语言,比c,c++,objc要高效简单.能够开发ios,mac相关的app哦!是苹 ...

  6. Go语言学习笔记十三: Map集合

    Go语言学习笔记十三: Map集合 Map在每种语言中基本都有,Java中是属于集合类Map,其包括HashMap, TreeMap等.而Python语言直接就属于一种类型,写法上比Java还简单. ...

  7. swift学习笔记5——其它部分(自动引用计数、错误处理、泛型&period;&period;&period;)

    之前学习swift时的个人笔记,根据github:the-swift-programming-language-in-chinese学习.总结,将重要的内容提取,加以理解后整理为学习笔记,方便以后查询 ...

  8. swift学习笔记4——扩展、协议

    之前学习swift时的个人笔记,根据github:the-swift-programming-language-in-chinese学习.总结,将重要的内容提取,加以理解后整理为学习笔记,方便以后查询 ...

  9. swift学习笔记3——类、结构体、枚举

    之前学习swift时的个人笔记,根据github:the-swift-programming-language-in-chinese学习.总结,将重要的内容提取,加以理解后整理为学习笔记,方便以后查询 ...

随机推荐

  1. 【三石jQuery视频教程】02&period;创建 FontAwesome 复选框和单选框

    视频地址:http://v.qq.com/page/m/8/c/m0150izlt8c.html 大家好,欢迎来到[三石jQuery视频教程],我是您的老朋友 - 三生石上. 今天,我们要通过基本的H ...

  2. &lbrack;Golang&rsqb;实习最后一天小纪念+并发爬虫小练习

    今天是我在公司实习的最后一天,一个月的时间真的是太短暂了,我非常享受在公司工作的这一个月,在这里Leader和同事们对我的帮助极大地促进了我技术水平的进步和自信心的提升,我发自内心地感谢白山云科技给我 ...

  3. js带箭头左右翻动控制

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  4. Java 反射 调用私有域和方法(setAccessible)

    Java 反射 调用私有域和方法(setAccessible) @author ixenos AccessibleObject类 Method.Field和Constructor类共同继承了Acces ...

  5. ArcGIS 网络分析&lbrack;0&rsqb; 介绍与博文目录【更新中】

    网络分析是个热点,理论上是属于计算机图形学和数据结构的,GIS以此为基础做出应用. 以下列举本人在学习中遇到的网络分析问题与经验总结. 1. 软件平台及数据准备 平台:Windows 10 操作系统, ...

  6. Linux正则表达式练习

    练习一 1.生成30位的随机口令 [root@centos7 ~]#cat /dev/urandom | tr -dc "[:alnum:]" | head -c30 RJL5qc ...

  7. salesforce零基础学习(九十)项目中的零碎知识点小总结(三)

    本次的内容其实大部分人都遇到过,也知道解决方案.但是因为没有牢记于心,导致问题再次出现还是花费了一点时间去排查了原因.在此记录下来,好记性不如烂笔头,争取下次发现类似的现象可以直接就知道原因.废话少说 ...

  8. EOJ2018&period;10 月赛&lpar;A 数学&plus;思维题&rpar;

    传送门:Problem A https://www.cnblogs.com/violet-acmer/p/9739115.html 题意: 能否通过横着排或竖着排将 1x p 的小姐姐填满 n x m ...

  9. MATLAB图片折腾1

    MATLAB 把文件夹里图片转成mat文件 pt='/Users/haoyuguo/Desktop/sync1/'; ext='*.jpg'; dis=dir([pt ext]); nms={dis. ...

  10. pm2 常用命令

    pm2 是一个带有负载均衡功能的Node应用的进程管理器.当你要把你的独立代码利用全部的服务器上的所有CPU,并保证进程永远都活着,0秒的重载, PM2是完美的.它非常适合IaaS结构,但不要把它用于 ...