swift简单总结(二十五)—— 构造过程

版本记录

版本号 时间
V1.0 2017.07.30

前言

我是swift2.0的时候开始接触的,记得那时候还不是很稳定,公司的项目也都是用oc做的,并不对swift很重视,我自己学了一段时间,到现在swift3.0+已经出来了,自己平时也不写,忘记的也差不多了,正好项目这段时间已经上线了,不是很忙,我就可以每天总结一点了,希望对自己对大家有所帮助。在总结的时候我会对比oc进行说明,有代码的我会给出相关比对代码。
1. swift简单总结(一)—— 数据简单值和类型转换
2. swift简单总结(二)—— 简单值和控制流
3. swift简单总结(三)—— 循环控制和函数
4. swift简单总结(四)—— 函数和类
5. swift简单总结(五)—— 枚举和结构体
6. swift简单总结(六)—— 协议扩展与泛型
7. swift简单总结(七)—— 数据类型
8. swift简单总结(八)—— 别名、布尔值与元组
9. swift简单总结(九)—— 可选值和断言
10. swift简单总结(十)—— 运算符
11. swift简单总结(十一)—— 字符串和字符
12. swift简单总结(十二)—— 集合类型之数组
13. swift简单总结(十三)—— 集合类型之字典
14. swift简单总结(十四)—— 控制流
15. swift简单总结(十五)—— 控制转移语句
16. swift简单总结(十六)—— 函数
17. swift简单总结(十七)—— 闭包(Closures)
18. swift简单总结(十八)—— 枚举
19. swift简单总结(十九)—— 类和结构体
20. swift简单总结(二十)—— 属性
21. swift简单总结(二十一)—— 方法
22. swift简单总结(二十二)—— 下标脚本
23. swift简单总结(二十三)—— 继承
24. swift简单总结(二十四)—— 构造过程

构造过程

上一篇讲构造过程没有讲完,下面我们继续看下面两个方面。

  • 类的继承和构造过程
  • 通过闭包和函数来设置属性的默认值

类的继承和构造过程

类里面的所有存储型属性,包括所有继承自父类的属性,都必须在构造过程中设置初始值。swift提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,分别是指定构造器和便利构造器。

1. 指定构造器和便利构造器

指定构造器是类中最主要的构造器,一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。

每一个类都必须拥有至少一个指定构造器,在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。

便利构造器时类中比较次要的、辅助型的构造器,你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值,你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。

一般只在必要的时候才为类提供便利构造器。

2. 构造器链

为简化指定构造器和便利构造器之间的调用关系,swift采用以下三条规则来限制构造器之间的代理调用。

  • 指定构造器必须调用其直接父类的指定构造器
  • 便利构造器必须调用同一类中定义的其他构造器
  • 便利构造器必须最终以调用一个指定构造器结束

或者可以这么记录

  • 指定构造器必须总是向上代理
  • 便利构造器必须总是横向代理

具体如下所所示:

图示

这里可以看到:

  • 父类中包含一个指定构造器和两个便利构造器,其中一个变遍历构造器就调用了另外一个便利构造器,而后者又调用了唯一的指定构造器。
  • 子类中包含两个指定构造器和一个便利构造器,便利构造器必须调用两个指定构造器中的任意一个。
  • 上图中展示的构造器都可以用来完整创建对应类的实例,这些规则只在实现类的定义时有影响。

在看一下下面的四个类的例子,演示指定构造器时如何在类层级中充当“管道”的作用,在类的构造器链上简化了类之间的相互关系。

四类构造器链

3. 两段式构造过程

swift中类的构造过程包含连个阶段:第一阶段,每个存储型属性通过引入它们的类的构造器来设置初始值,当每一个存储型属性值确定后;第二个阶段,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。

注意swift的两段式构造过程和OC中的构造过程类似,主要区别在与阶段1,OC给每一个属性赋值为0或空值,swift的构造流程则更加灵活,它允许你设置定制的初始值,并自如应对某些属性不能以0nil作为合法默认值的情况。

swift编译器将执行4中有效的安全检查,确保两段式构造过程能顺利完成。

  • 安全检查1:指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其他构造任务向上代理给父类中的构造器。一个对象的内存只有在其所有存储型确定之后才能完成初始化,为满足这一点,指定构造器必须保证它所在类引入的属性在它往上代理之前完成初始化。
  • 安全检查2:指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值,如果不这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
  • 安全检查3:便利构造器必须先代理调用同一类中的其他构造器,然后再为任意属性赋新值,如果不这么做,便利构造器的新值将被同一类中其他指定构造器所覆盖。
  • 安全检查4:构造器在第一阶段完成之后,不能调用任何实例方法,不能读取任何示例属性的值,self的值不能被引用。

下面是两段式构造过程中基于上述安全检查的构造流程展示。

阶段1:

  • 某个指定构造器或便利构造器被调用
  • 完成新实例内存分配,但此时内存还没被初始化
  • 指定构造器确保其所在类引入的所有存储型属性都已赋初值,存储型属性所属的内存完成初始化
  • 指定构造器将调用父类的构造器,完成父类属性的初始化
  • 这个调用父类构造器的过程沿着构造器链一直往上执行,直到达到构造器最顶部
  • 当到达了最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化完成,1阶段完成。

阶段2

  • 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例,构造器此时可以使用self,修改它的属性并调用实例方法等。
  • 最终,任意构造器链中的便利构造器可以有机会定制实例和使用self

下面图展示的子类和父类之间构造的阶段1

阶段1

构造过程从对子类中一个便利构造器的调用开始,这个便利构造器此时没法修改任何属性,它把构造任务代理给同一个类中的指定构造器,指定构造器将确保所有子类的属性都有值,然后它将调用父类的指定构造器,并沿着构造器链一直往上完成父类的构建过程。一旦父类中所有属性都有了初始值,实例的内存被认为是完全初始化,阶段1完成。

下面看一下阶段2

阶段2

4. 构造器的继承和重载

OC不同的是,swift的子类不会默认继承父类的构造器,swift的这种机制,可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。

假如你希望自定义的子类中能实现一个或多个跟父类相同的构造器,也许是为了完成一些定制的构造过程,你可以在你定制的子类提供和重载与父类相同的构造器,如果你重载的构造器是一个指定构造器,你可以在子类里重载它的实现,并在自定义版本的构造器中调用父类版本的构造器,如果你重载的构造器是一个便利构造器,你的重载过程必须通过调用同一类中提供的其他指定构造器来实现。

注意: 与方法、属性和下标不同,在重载构造器时你没必要使用关键字override

5. 自动构造器的继承

子类不会默认继承父类的构造器,但是如果特定条件可以满足,父类构造器可以被自动继承。

假如要为子类中引入的任意新属性提供默认值,请遵守以下2个规则。

规则1

如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。

规则2

如果子类提供了所有父类指定构造器的实现,不管是通过规则1继承过来的,还是通过自定义实现的,它将自动继承所有父类的便利构造器。

即使你在子类中添加了更多的便利构造器,这两条规则仍然适用,

注意:子类可以通过部分满足规则2的方式,使用子类便利构造器来实现父类的指定构造器。

6. 指定构造器和便利构造器语法

类的指定构造器写法和值类型简单构造器一样,如下所示:

init(parameter){
    statements
}

便利构造器也是一样写法,但是需要在init关键字之前放置convenience关键字。

convenience init(parameter){
    statements
}

7. 指定构造器和便利构造器实战

下面将会代码展示指定构造器、便利构造器和自动构造器的继承。

先提供基类Food,是一个简单的用来封装食物名字的类,看代码。

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

下面看一下,Food类的构造器链:

Food构造器链

类没有提供一个逐一成员构造器,所以Food类提供了一个接受单一参数name的指定构造器,这个构造器可以使用一个特定的名字创建实例。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        let nameMeat = Food(name: "Bacon")
        print(nameMeat.name)
    }
}

下面看输出结果

Bacon

Food类构造器init(name : String)被定义为一个指定构造器,能确保新Food实例中存储型属性都被初始化,Food类没有父类,所以init(name : String)构造器不需要调用super.init()来完成构造。

Food类也提供了一个没有参数的便利构造器init(),这个init()构造器为新食物提供了一个默认的占位名字,通过代理调用同一个类中定义的指定构造器init(name : String)并给参数name传值"Unnamed"来实现。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        let nameMeat = Food()
        print(nameMeat.name)
    }
}

下面看输出结果

Unnamed

接着定义第二个类,继承自Food,拥有Int类型的数量属性quantity还有就是从Food继承过来的name属性。并且也定义了两个构造器来创建实例。

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

下面展示的是RecipIngredient的构造器链。

构造器链

RecipIngredient类有一个指定构造器init(name : String, quantity : Int),它用来产生新的RecipIngredient实例,随后,构造器将任务向上代理给父类Foodinit(name : String),这个过程满足两段式构造过程的安全检查1。

RecipIngredient类也定义了一个便利构造器init(name: String),这个便利构造器是将任务代理给了同一个类中的指定构造器。

注意RecipIngredient的便利构造器init(name: String)Food中指定构造器init(name: String)有相同的参数,因为这个便利构造器重写要父类的指定构造器init(name: String),必须加override

以下三种方法都可以实例化RecipIngredient

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        let oneInstance = RecipIngredient()
        print(oneInstance.quantity)
        print(oneInstance.name)
        
        let twoInstance = RecipIngredient(name: "egg")
        print(twoInstance.quantity)
        print(twoInstance.name)
        
        let threeInstance = RecipIngredient(name: "egg", quantity: 10)
        print(threeInstance.quantity)
        print(threeInstance.name)

    }
}

下面看输出结果

1
Unnamed
1
egg
10
egg

下面看最后一个类,继承自RecipIngredient,类中引入了一个布尔类型的属性purchased,还添加了一个计算型属性description,下面看代码。

class ShoppiingListItem: RecipIngredient {
    var purchased = false
    var description : String{
        var output = "\(quantity) x \(name)"
        output += purchased ? "买" : "不买"
        return output
    }
}

ShoppiingListItem为自己的所有属性都提供了默认值,并且自己没有定义任何构造器,ShoppiingListItem将自动继承所有父类中的指定构造器和便利构造器。

下面看一下三个类的构造器链。

构造器链

可以使用全部三个继承来的构造器创建ShoppiingListItem实例。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        var breakfastList = [
            ShoppiingListItem(),
            ShoppiingListItem(name: "bread"),
            ShoppiingListItem(name: "egg", quantity: 10)
        ]
        breakfastList[0].name = "orange juice"
        breakfastList[0].purchased = true
        
        for item in breakfastList {
            print(item.description)
        }
    }
}

下面看输出结果

1 x orange juice买
1 x bread不买
10 x egg不买

通过闭包和函数来设置属性的默认值

如果某个存储型属性的默认值需要特别定制,你就可以使用闭包或全局函数来为其属性提供定制的默认值,每当这个属性所属的新类型实例创建时,对应的闭包或者函数会被调用,而它们的返回值会当做默认值赋值给这个属性。

这种类型的闭包或函数一般会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后将这个临时变量的值作为属性的默认值进行返回。

下面看一下调用格式。

class SomeClass{
    let someProperty : SomeType = {
        //在这个闭包中给someProperty创建一个默认值
        //someValue必须和SomeType类型相同
        return someValue
    }()
}

注意

  • 闭包结尾的大括号后面接一对空的小括号,这是用来告诉swift需要立即执行此闭包,如果你忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
  • 如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其他部分都还没有初始化,这意味着你不能够在闭包里访问其他的属性,就算这个属性有默认值也不允许,同样,你也不能用隐式self属性,或者调用其他的实例方法。

看下面的结构体定义了西洋跳棋游戏的棋盘。

棋盘

下面看一下代码

struct Checkboard {
    let boardColors : [Bool] = {
        var temporaryBorad = [Bool]()
        var isBlack = false
        for i in 1...10{
            for j in 1...10 {
                temporaryBorad.append(isBlack)
                isBlack = !isBlack
            }
            isBlack = !isBlack
        }
        return temporaryBorad
    }()
    
    func squareIsBlackAtRow(row : Int, column : Int) -> Bool {
        return boardColors[(10 * row) + column]
    }
}

新的Checkboard实例创建时,对应的赋值闭包会执行,一系列颜色值会被计算出来作为默认值赋值给boardColors,闭包会返回临时数组temporaryBorad,这个返回值会被保存到boardColors中,并可以通过工具函数squareIsBlackAtRow返回。

下面我们就调用下看看。

class JJPracticeVC: UIViewController {

    override func viewDidLoad()
    {
        super.viewDidLoad()

        view.backgroundColor = UIColor.lightGray
        
        let checkBoard = Checkboard()
        print(checkBoard.squareIsBlackAtRow(row: 0, column: 1))
        print(checkBoard.squareIsBlackAtRow(row: 9, column: 9))
    }
}

下面看输出结果

true
false

后记

未完,待续~~~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容