import Foundation
/*
构造过程是使用 类、结构体 或 枚举类型 一个实例的准备过程。
在新实例用前必须运行这个过程,详细操作包括 设置实例中每一个存储型属性的初始值 和 运行其他必须的设置 或 初始化工作
通过定义构造器( Initializers )来实现构造过程,这些构造器能够看做是用来创建特定类型新实例的特殊方法。
与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完毕正确的初始化
*/
/*********** 1 存储属性的初始赋值 ************/
/*
类和结构体在创建实例时,必须为全部存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态
能够在构造器中为存储型属性赋初值,也能够在定义属性时为其设置默认
注意:
当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发不论什么属 性观察者( property observers )
*/
// 构造器
//init() {
// // 运行构造的过程
//}
// 结构体定义了一个不带參数的构造器 init ,并在里面将存储型属性 name 的值初始化为 "kermit"
struct Student {
var name: String
init () {
name = "kermit"
}
}
// 默认属性值
struct Student1 {
var name = "kermit"
}
/*********** 2 自己定义构造过程 ************/
// 构造參数
struct Student2 {
var name: String
init (oldBrother brother: String) {
name = brother
}
init (oldSister sister: String) {
name = sister
}
}
let xiaoMing = Student2(oldBrother: "xiaoMing")
let xiaoFang = Student2(oldSister: "xiaoFang")
// 參数的内部名称和外部名称
/*
跟函数和方法參数同样,构造參数也存在一个在构造器内部使用的參数名字和一个在调用构造器时使用的外部參数名字。
然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中的參数名和类型来确定须要调用的构造器。
正由于參数如此重要,假设在定义构造器时没有提供參数的外部名字,Swift 会为每一个构造器的參数自己主动生成一个跟内部名字同样的外部名,就相当于在每一个构造參数之前加了一个哈希符号
*/
struct Color {
let red, green, blue: Double
init (red:Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
init (white: Double) {
red = white
green = white
blue = white
}
}
// 注意,假设不通过外部參数名字传值,是没法调用这个构造器的。仅仅要构造器定义了某个外部參数名,就必须使用它,忽略它将导致编译错误
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
// 不带外部名的构造器參数
struct Red {
let alpha: Double
init (_ newAlpha:Double) {
alpha = newAlpha
}
}
let red = Red(0.5)
// 可选属性类型
/*
假设定制的类型包括一个逻辑上同意取值为空的存储型属性
无论是由于它无法在初始化时赋值,还是由于它能够在之后某个时间点能够赋值为空
都须要将它定义为可选类型 optional type。
可选类型的属性将自己主动初始化为空 nil ,表示这个属性是有益在初始化时设置为空的。
*/
class Person {
var name: String
var high: String?
init(name: String) {
self.name = name
}
func getName() {
print(name)
}
}
let man = Person(name: "JK")
man.getName()
man.high = "168"
// 构造过程中常量属性的改动
// 能够在构造过程中的随意时间点改动常量属性的值,仅仅要在构造过程结束时是一个确定的值。一旦常量属性被赋值,它将永远不可更改
// 注意:对于类的实例来说,它的常量属性仅仅能在定义它的类的构造过程中改动;不能在子类中改动
class Person1 {
let name: String
var high: String?
init (name: String) {
self.name = name
}
func getName() {
print(name)
}
}
let man1 = Person(name: "MF")
man1.getName()
/*********** 3 默认构造器 ************/
// 假设结构体和类的全部属性都有默认值,同一时候没有自己定义的构造器,那么 Swift 会给这些结构体和类创建一个默认构造器
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
print(item) // Initialization构造过程.ShoppingListItem
// 结构体的逐一成员构造器
// 假设结构体对全部 存储型属性 提供了默认值且自身没有提供定制的构造器,它们能自己主动获得一个逐一成员构造器
// 逐一成员构造器是用来初始化 结构体 新实例里 成员属性 的快捷方法
// 在调用逐一成员构造器时,通过与成员属性名同样的參数名进行传值来完毕对成员属性的初始赋值
struct Size {
var width = 0.0, height = 0.0
}
// 由于这两个存储型属性都有默认值,结构体 Size 自己主动获得了一个逐一成员构造器
let boxSize = Size( 3.0, height: 5.0)
/*********** 4 值类型的构造器代理 ************/
/*
构造器能够通过调用其他构造器来完毕实例的部分构造过程。这一过程称为构造器代理,它能降低多个构造器间的代码反复
构造器代理的实现规则和形式在值类型和类类型中有所不同。值类型(结构体和枚举类型)不支持继承,所以构造器代理的过程相对简单,由于它们仅仅能代理给本身提供的其他构造器。
类则不同,它能够继承自其他类,这意味着类有责任保证其全部继承的存储型属性在构造时也能正确的初始化
对于值类型,能够使用 self.init 在自己定义的构造器中引用其他的属于同样值类型的构造器。而且仅仅能在构 造器内部调用 self.init
假设为某个值类型定义了一个定制的构造器,将无法訪问到默认构造器(假设是结构体,则无法訪问逐一对象构造器)。
这个限制能够防止为值类型定义了一个更复杂的,完毕了重要准备构造器之后,别人还是错误的使用了那个自己主动生成的构造器。
注意:
想通过默认构造器、逐一对象构造器以及你自己定制的构造器为值类型创建实例,建议将自己定制的构造器写到扩展( extension )中,而不是跟值类型定义混在一起
*/
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/2
let originY = center.y - size.height/2
self.init(origin: Point(x: originX, y: originY),size: size)
}
}
// 这个构造器是一个空函数,使用一对大括号{ }来描写叙述,它没有运行不论什么定制的构造过程
let basicRect = Rect()
// 在功能上跟结构体在没有自己定义构造器时获得的逐一成员构造器是一样的
let originRect = Rect(origin: Point(x: 2.0, y: 3.0), size: Size( 3.0, height: 3.0))
// 先通过 center 和 size 的值计算出 origin 的坐标。然后再调用(或代理给)init(origin:size:) 构造器将新的 origin 和 size 值赋值到相应的属性中
let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size( 2.0, height: 2.0))
/*********** 5 类的继承和构造过程 ************/
// 类里面的全部存储型属性--包括全部继承自父类的属性--都必须在构造过程中设置初始值。
// Swift 提供了两种类型的类构造器来确保全部类实例中存储型属性都能获得初始值,它们各自是 指定构造器 和 便利构造器
// 指定构造器和便利构造器
// 指定构造器是类中最基本的构造器。一个指定构造器将初始化类中提供的全部属性,并依据父类莲往上调用父类的构造器来实现父类的初始化
// 便利构造器是类中比較次要的,辅助型的构造器。能够定义便利构造器来调用同一类中指定的构造器,并为其參数提供默认值
/*
语法:
// 指定构造器
init(parameters) {
}
// 便利构造器 在 init 前加上 convenience 关键字
convenience init(parameters) {
}
*/
// 类的构造器代理规则
/*
规则1:
指定构造器必须调用器直接父类的指定构造器
规则2:
便利构造器必须调用同类中定义的其他构造器
规则3:
便利构造器必须始终以调用一个指定构造器结束
总结:
指定构造器必须是向上代理
便利构造器必须是横向代理
*/
// 两断式构造过程
/*
Swift 中类的构造过程包括两个阶段:
第一个阶段,每一个存储型属性通过引入它们的类的构造器来设置初始值。
当每一个存储型属性值被确定后,
第二阶段開始,它给每一个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。
两段式构造过程的使用让构造过程更安全,同一时候在整个类层级结构中给予了每一个类全然的灵活性。
两段式构造过程能够防止属性值在初始化之前被訪问;也能够防止属性被另外一个构造器意外地赋予不同的值。
注意:
Swift的两段式构造过程跟 Objective-C 中的构造过程相似。
最基本的差别在于阶段 1,Objective-C 给每一个 属性赋值 0 或空值(比方说 0 或 nil )。
Swift 的构造流程则更加灵活,它同意你设置定制的初始值,并自如应对某些属性不能以 0 或 nil 作为合法默认值的情况
Swift 编译器将运行 4 种有效的安全检查,以确保两段式构造过程能顺利完毕:
安全检查 1
指定构造器必须保证它所在类引入的全部属性都必须先初始化完毕,之后才干将其他构造任务向上代理给父类中的构造器
安全检查 2
指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。假设没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖
安全检查 3
便利构造器必须先代理调用同一类中的其他构造器,然后再为随意属性赋新值。假设没这么做,便利构造器赋予的新值将被同一类中其他指定构造器所覆盖
安全检查 4
构造器在第一阶段构造完毕之前,不能调用不论什么实例方法、不能读取不论什么实例属性的值, self 的值不能被引用
基于安全检查的构造流程展示:
阶段 1
• 某个指定构造器或便利构造器被调用;
• 完毕新实例内存的分配,但此时内存还没有被初始化;
• 指定构造器确保其所在类引入的全部存储型属性都已赋初值。存储型属性所属的内存完毕初始化;
• 指定构造器将调用父类的构造器,完毕父类属性的初始化;
• 这个调用父类构造器的过程沿着构造器链一直往上运行,直到到达构造器链的最顶部;
• 当到达了构造器链最顶部,且已确保全部实例包括的存储型属性都已经赋值,这个实例的内存被觉得已经完 全初始化。
此时阶段1完毕。
阶段 2
• 从顶部构造器链一直往下,每一个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时能够訪问 self 、改动它的属性并调用实例方法等等。
• 终于,随意构造器链中的便利构造器能够有机会定制实例和使用 self 。
*/
// 构造器的继承和重写
/*
跟 Objective-C 中的子类不同,Swift 中的子类不会默认继承父类的构造器。
(父类的构造器仅在确定和安全的情况下被继承)
Swift 的这样的机制能够防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例
假如希望自己定义的子类中能实现一个或多个跟父类同样的构造器,或许是为了完毕一些定制的构造过程,能够在定制的子类中提供和重写与父类同样的构造器(带上 override 修饰符)
*/
class NewPerson {
var name = "perosnName"
var description: String {
return "(name)"
}
}
let kevin = NewPerson()
print(kevin.description)
class student: NewPerson {
override init() {
// 调用父类。确保student在改动属性之前它所继承的属性能被父类初始化
super.init()
name = "studentName"
}
}
// 子类能够在初始化时改动继承变量属性,可是不能改动继承过来的常量属性
let kangkang = student()
print(kangkang.description)
// 自己主动构造器的继承
/*
子类不会默认继承父类的构造器。
可是假设特定条件能够满足,父类构造器是能够被自己主动继承的。
在实践中,这意味着对于很多常见场景不必重写父类的构造器,而且在尽可能安全的情况下以最小的代价来继承父类的构造器。
要为子类中引入的随意新属性提供默认值,请遵守下面2个规则:
规则 1
假设子类未定义不论什么指定构造器,它将自己主动继承全部父类的指定构造器。
规则 2
假设子类提供了全部父类指定构造器的实现--无论是通过规则1继承过来的,还是通过自己定义实现的--它将自己主动继承全部父类的便利构造器。
*/
// 指定构造器和便利构造器实例
// 类层次中的基类是 Food ,它是一个简单的用来封装食物名字的类
class Food {
var name: String
// 指定构造器
// Food 类没有父类,所以 init(name: String) 构造器不须要调用 super.init() 来完毕构造
init(name: String) {
self.name = name
}
// 便利构造器
convenience init() {
self.init(name:"[Unnamed]")
}
}
let nameMeat = Food(name: "Bacon") // namedMeat 的名字是 "Bacon”
// 通过代理调用同一类中定义的指定构造器 init(name: String) 并给參数 name 传值 [Unnamed] 来实现
let mysteryMeat = Food() // mysteryMeat 的名字是 [Unnamed]
// 类层级中的第二个类是 Food 的子类 RecipeIngredient 。 RecipeIngredient 类构建了食谱中的一味调味剂
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
// 满足两段式构造过程中的安全检查1
// 指定构造器必须保证它所在类引入的全部属性都必须先初始化完毕,之后才干将其他构造任务向上代理给父类中的构造器
self.quantity = quantity
super.init(name: name)
}
// 便利构造器的定义能够让创建实例更加方便和快捷,而且避免了使用反复的代码来创建多个 quantity 为 1 的实例
override convenience init(name: String) {
self.init(name: name, quantity: 1)
}
}
// Food 的便利构造器
let oneMysteryItem = RecipeIngredient()
// RecipeIngredient 的便利构造器
let oneBacon = RecipeIngredient(name: "Bacon")
// RecipeIngredient 的构造器
let sixEggs = RecipeIngredient(name: "eggs", quantity: 6)
// ShoppingListItem 引入了一个布 尔类型的属性 purchased ,它的默认值是 false
class NewShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "(quantity) x (name)"
output += purchased ?
"?
" : "!"
return output
}
}
var breakfastList = [
// Food 的便利构造器
NewShoppingListItem(),
// RecipeIngredient 的便利构造器
NewShoppingListItem(name: "Bacon"),
// RecipeIngredient 的构造器
NewShoppingListItem(name: "eggs", quantity: 6)
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
print(item.description)
}
/*
1 x Orange juice?
1 x Bacon!
6 x eggs!
*/
/*********** 6 可失败构造器 ************/
/*
假设一个类、结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是很实用的。
这里所指的“失败”是指,如给构造器传入无效的參数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等
语法为在 init 关键字后面加添问号 (init?)
注意:
可失败构造器的參数名和參数类型,不能与其他非可失败构造器的參数名,及其类型同样
可失败构造器,在构建对象的过程中,创建一个其自身类型为可选类型的对象。通过 return nil 语句,来表 明可失败构造器在何种情况下“失败”
严格来说,构造器都不支持返回值。由于构造器本身的作用,仅仅是为了能确保对象自身能被正确构建。
所以即使在表明 可失败构造器, 失败的这样的情况下 ,用到了 return nil 。
也不要在表明 可失败构造器 成功的这样的情况下 ,使用关键字 return
*/
struct Animal {
let species: String
// 这个可失败构造器,被用来检查传入的參数是否为一个空字符串,假设为空字符串,则该可失败构造器,构建对象失败,否则成功
init?(species: String) {
if species.isEmpty {
return nil
}
self.species = species
}
}
// someCreature 的类型是 Animal?
而不是 Animal
let someCreature = Animal(species: "Giraffe")
if let giraffe = someCreature {
print("An animal was initialized with a species of (giraffe.species)")
// An animal was initialized with a species of Giraffe
}
let nullAnimal = Animal(species: "")
if nullAnimal == nil {
print("The xxx could not be initialized")
}
// 枚举类型的可失败构造器
enum TemperatureUnit {
case Kelvin, Celsius, Fahreheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahreheit
default:
return nil
}
}
}
// 能够通过给该可失败构造器传递合适的參数来获取这三个枚举成员中相匹配的当中一个枚举成员。
当參数的值不能与随意一枚举成员相匹配时,该枚举类型的构建过程失败:
let f = TemperatureUnit(symbol: "F")
if f != nil {
print("initialization succeed")
}
let x = TemperatureUnit(symbol: "X")
if x == nil {
print("initialization failed")
}
// 带原始值的枚举类型的可失败构造器
enum NewTemperatureUnit: Character {
case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}
let ff = NewTemperatureUnit(rawValue:"F")
if f != nil {
print("initialization succeed")
}
let xx = NewTemperatureUnit(rawValue: "X")
if x == nil {
print("initialization failed")
}
// 类的可失败构造器
/*
值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有不论什么的限制
类的可失败构造器仅仅能在 全部的类属性被初始化后 和 全部类之间的构造器之间的代理调用 发生完后 触发失败行为
*/
// Product 类有一个不能为空字符串的 name 常量属性。
为了强制满足这个要求, Product 类使用了可失败构造器来确保这个属性的值在构造器成功时不为空
// Product 类的全部可失败构造器必须在自己失败前给 name 属性一个初始值
class Product {
let name: String! // 隐式解析可选字符串类型
// 由于它是一个可选类型,所以在构造过程里的赋值前, name 属性有个默认值 nil
init?
(name: String) {
self.name = name
if name.isEmpty {
return nil
}
}
}
// 构造失败的传递
/*
可失败构造器同意在同一类,结构体和枚举类中横向代理其他的可失败构造器
子类的可失败构造器也能向上代理基类的可失败构造器
无论是向上代理还是横向代理,假设你代理的可失败构造器,在构造过程中触发了构造失败的行为,
整个构造过程都将被马上终止,接下来不论什么的构造代码都将不会被运行。
*/
// 表示物品的数量至少为1
// 假设构造了一个 CartItem 对象,而且该对象的 name 属性不为空以及 quantity 属性为 1 或者很多其他,则构造成功
// 假设构造一个 CartItem 对象,其 quantity 的值 0 , 则 CartItem 的可失败构造器触发构造失败的行为
// 假设构造一个 CartItem 对象,但其 name 的值为空, 则基类 Product 的可失败构造器将触发构造失败的行为,整个 CartItem 的构造行为同样为失败
class CartItem:Product {
let quantity: Int!
init?(name: String, quantity: Int) {
self.quantity = quantity;
super.init(name: name)
// 假设由于 name 的值为空而导致基类的构造器在构造过程中失败。则整个 CartIem 类的构造过程都将失败,后面的子类的构造过程都将不会被运行。
// 假设基类构建成功,则继续运行子类的构造器代码。
if quantity < 1 {
return nil
}
}
}
// 重写一个可失败构造器
// 用子类的可失败构造器重写父类的可失败构造器,或者用子类的非可失败构造器重写一个父类的失败构造器
// 优点:即使父类的构造器为可失败构造器,但当子类的构造器在构造过程中不可能失败时,我们能够把它改动过来
// 注意:当用子类的非可失败构造器重写一个父类的可失败构造器时,子类的构造器将不再能向上代理父类的可失败构造器。
一个非可失败的构造器永远不能代理调用一个可失败的构造器
class Document {
var name: String?
// 该构造器构建了一个name属性值为nil的document对象
init() {}
// 该构造器构建了一个name属性值为非空字符串的document对象
init?
(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
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
}
}
}
// 可失败构造器 init!
/*
通常来说我们通过在 init 关键字后加入问号的方式( init? )来定义一个可失败构造器,
但也能够使用通过在 init 后面加入惊叹号的方式来定义一个可失败构造器 (init!) ,该可失败构造器将会构建一个特定类型的隐式解析可选类型的对象。
能够在 init?
构造器中代理调用 init! 构造器,反之亦然。也能够用 init?
重写 init! ,反之亦然。还能够用 init 代理调用 init! ,
但这会触发一个断言: init! 构造器是否会触发构造失败?
*/
/*********** 7 必要构造器 ************/
// 在类的构造器前加入 required 修饰符表明全部该类的子类都必须实现该构造器
class KT {
required init() {
// 在这里加入该必要构造器的实现代码
}
}
class Kt: KT {
required init() {
// 子类必要构造器的实现代码
}
}
/*********** 8 通过闭包和函数来设置属性的默认值 ************/
// 每当某个属性所属的新类型实例创建时,相应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
class newClass {
let someProperty: String = {
// 在这个闭包中给 someProperty 创建一个默认值
return "次地方的类型必须和 someProperty 类型一致"
}()
// 注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 须要立马运行此闭包。
// 假设忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性
}