Swift-构造过程(Initialization)
Posted 人散风中
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift-构造过程(Initialization)相关的知识,希望对你有一定的参考价值。
前言
对于一个高中毕业后就除了毕业论文就再没认真写过东西的人来说,想坚持写点东西真的不太容易。最大的“阻碍”莫过于现在网上有各种各样大神花式分享的几乎从入门到精通的所有内容,当我们去拜读那些文章的时候,会从心底赞美这些人的无私和对专业知识的精通,同时难免体会到自己的无力,尤其是看看自己准备写,或者要写的东西的时候,就会彻彻底底的体会到两个字:low逼。虽然有时候想想别人的毕竟是别人的,自己学到的才是自己的,但是偶尔还是会受到一些刺激。昨天看到简书的一个哥们写的 浅谈iOS架构,下面有个人的评论虽然中肯,但是那种高高在上的感觉实在让人不爽。因为自己打算写一个大点的Swift语言开发的Demo,所以试图看看有没有什么好的项目架构建议,毕竟好习惯是慢慢培养的,以前开发的时候大都是随意的百度些如何创建ios项目结构或者参考已有的项目去创建一些固定的文件夹,添加对应的文件。待的一直是创业公司,并没有什么架构师,所以开发过的项目总体看起来都很乱。一个好的项目架构往往会让我们的项目代码看起来赏心悦目。昨天还真的收获到一篇非常非常不错的系列文章:iOS架构谈,其实现在我还没看完,不过作者是真大神。
成长总会经历咿呀学步的阶段,知识的学习也是一样,尤其是作为普通的不能在普通的普通人,没有天赋,没有特长,甚至连坚持的毅力也乏乏,但是,又怎么能放弃呢?
今天也不知道哪来的这么多感慨,也许知道只是懒得去向为啥会有这感慨。开始正文,Swift的构造过程,这个应该是 The Swift Programming Language 中文版 这本教材中最长的一个章节了,不过我提前看了一遍,也并没有特别难的知识点,先来个小提纲:
- 存储属性的初始赋值
- 自定义构造过程
- 默认构造器
- 值类型的构造器代理
- 类的继承和构造过程
- 可失败构造器
- 必要构造器
- 通过闭包和函数来设置属性的默认值
构造过程是使用类、结构体或枚举类型的一个实例的准备过程。在新实例可用前必须执行这个过程。通过定义构造器(Initializers
)来实现构造过程。与Objective-C
中的构造器不同的是Swift构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。累的实例也可以通过定义析构器(deinitializers
)在实例释放之前执行特定的清除工作。下一小节将会讲析构过程。
分条详述
存储属性的初始赋值
类和结构体在创建实例时,必须为所有存储熟悉你设置合适的初始值。存储类型的值不能处于一种未知的状态。有两种赋值方式,一种是默认赋值,一种是在默认构造器中赋值。需要注意的是当为存储属性设置默认值或者在构造器中为其赋值时,它们的值是被直接赋值的,不会触发属性观察者(
willSet
、didSet
、setter
):// 在构造器中赋值 struct Scrore var level: String init() level = "SS" var score = Scrore() // 此时执行构造过程,为level赋值 score.level // "SS" score.level = "C" // 重新赋值 // 默认属性值 struct Car var wheels = 4 var car = Car() car.wheels // 4
当一个属性总是使用相同的属性值,建议使用默认属性值,会使代码更加简洁易读。
自定义构造过程
可以通过输入参数和可选属性来自定义构造过程,也可以在构造过程中修改常量属性。
构造参数
举个例子,输入一个绝对温度或者华氏温度,将其转化为摄氏度,举这个例子一方面是教材就这个例子,另外,小小Demo我竟然有三个单词不认识,不能忍:
// 单词学习:Celsius: 摄氏度 temperature: 温度 fahrenheit:华氏温度 struct Celsius var temperatureInCelsius: Double // 构造器一:fromFahrenheit 是参数外部名, fahrenheit 是参数内部名 init(fromFahrenheit fahrenheit: Double) temperatureInCelsius = (fahrenheit - 32) / 1.8 // 构造器二:下划线 _ 表示忽略参数外部名,前面有介绍,kelvin 是参数内部名 init(_ kelvin: Double) temperatureInCelsius = kelvin - 273.15 // 构造器三: init(mutiableNumber: Double) temperatureInCelsius = mutiableNumber * 2 // 实例测试 // 构造器一,只显示外部参数名 var originCelsius = Celsius(fromFahrenheit: 300) originCelsius.temperatureInCelsius // 148.8888888888889 // 构造器二,调用时不显示外部参数名,因为被忽略了 originCelsius = Celsius(300) originCelsius.temperatureInCelsius // 26.85000000000002 // 构造器三,默认只有一个参数名,既是参数外部名,又是参数内部名 originCelsius = Celsius(mutiableNumber: 300) originCelsius.temperatureInCelsius // 600
参数的内部名称和外部名称
参照上面的代码构造器一、二、三,显然,参数名在构造器中非常重要,因为构造器都是
init()
,参数名则是对构造器的注释。如果在定义构造器时没有提供参数的外部名,Swift会为每个构造器的参数生成一个跟内部名字相同的外部名,如上面代码中的构造器三,这就相当于在每个构造参数之前加了个哈希符号。使用下划线表示忽略构造函数的外部名,实例的时候也不会显示,如构造器二。可选属性类型
当类或结构体中包含一个逻辑上允许取值为空的存储属性时,必须要将其设置为可选类型
optional type
。可选类型的属性将其自动初始化为空nil
,表示这个属性实故意在初始化时设置为空的。否则必须在初始化时为其赋值。比如一个问题,必须先问,才能有回答,所以回答的属性在类型实例化的时候可以为空,再次提醒一下,如果存储属性不是可选类型,那么类型初始化结束时必须给该属性赋值,否则会编译错误:// 允许为空的属性必须是可选类型 struct AskAndAnswer var question: String var answer: String? init(putYourQurestion question: String) self.question = question func ask() // 执行提出问题的方法 print(question) var test = AskAndAnswer(putYourQurestion: "How old are you?") test.ask() test.answer = "ni cai"
在构造过程中,常量既可以默认赋值,也可以在构造方法中赋值。一旦常量属性被设置,它将永远不能修改。对于类的实例来说,它的常量属性只能在定义它的类中赋默认值或者构造过程中赋值,不能在子类中修改。
默认构造器
如果类或者结构体的所有属性都有默认值,同事没有自定义的构造器,那么Swift会为这些结构体或类提供默认的、隐藏构造器。
类的默认构造器是唯一的,但是结构体可以有逐一的成员构造器,即实例化时为每一个属性赋值,下面实例代码中对两种方式都做了说明:
// 类,属性已经全部有默认值,实例化时只有下面这种方式 class GoodsInfo var weight: Double? // 可选类型,默认值为nil let price = 10 var name = "meat" let singleGoods = GoodsInfo() // 默认构造器 // 结构体,属性都有值,实例时有两种方式 struct Size var height = 0.0 var width = 0.0 var length: Double? var singleSize = Size() // 默认构造器 var nextSiz = Size(height: 20, width: 40, length: 50) // 逐一成员构造器 // 结构体,属性部分或者全部都没值,只有之中实例化方式 struct RectSize var height = 0.0 var width = 0.0 var length: Double var rect = RectSize(height: 11, width: 11, length: 11) // 如果所有存储属性没有提供默认值,就只有这种实例方式
值类型的构造器代理
构造器可以通过调用其他构造器来完成实例的部分构造过程,这一过程称为构造器代理,它能减少多个构造器之间的代码重复。
构造器代理的实现规则和形式在值类型(枚举
enumeration
、结构体structure
)和类类型(类class
)中有所不同。因为值类型没有继承的功能,所以构造器代理只为自身提供,过程相对简单。类则不同,它可以继承自其他类,这意味着类有责任保证其所有继承的存储属性在构造时也能正确的初始化,下一个小知识点将会讲到 类的继承和构造过程 这个知识点。对于值类型,可以使用
self.init
在自定义的构造器中引用这个值类型的其他构造器。并且只能在构造器内部调用self.init
,并且调用的时候并没有代码补全提示,所以要手动输入。如果为一个值类型定义了一个定制的构造器,那么将不能使用默认的构造器。说简单点,如果值类型中出现
init
这个方法,那么值类型实例化时就必须要用带有init
方法中的一个。加入既想定制构造器,又想使用默认的构造器,那么就用 扩展(extension
) 吧。这个知识点还在后面,重要程度,嗯,五颗星吧。下面的实例代码很好的解释了值类型的构造器代理和自定义构造器,注意看注释: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() // 矩形宽高 // 构造器一:这个和默认构造器的功能是一样的,但是,注意!这个不是默认构造器,这个不是默认构造器!!不信你把这行代码注释掉,然后 var singleRect = Rect() 这么实例化试试? init() // 构造器二:功能上和结果体的逐一成员构造器一样 init(rectOrigin origin: Point,rectSize size: Size) self.origin = origin self.size = size // 构造器三:秀一下如何使用构造器代理 init(rectCenter center: Point, rectSize size: Size) let originX = center.x - size.width / 2 let originY = center.y + size.height / 2 // 使用构造器代理,这里代码没有补全提示 self.init(rectOrigin: Point(x: originX, y: originY), rectSize: size) // 实例化 var singleRect1 = Rect() var singleRect2 = Rect(rectCenter: Point(x: 3, y: 4), rectSize: Size(width: 30, height: 30)) var singleRect3 = Rect(rectOrigin: Point(x: 12, y: 15), rectSize: Size(width: 24, height: 36))
类的继承和构造过程
指定构造器和便利构造器
类里面所有的存储属性,包括所有继承自父类的属性,都必须在构造过程中设置初始值。Swift提供两种类型的构造器来确保所有的类实例中存储属性都获得初始值,它们分别是指定构造器和便利构造器。上文中说到的类的默认构造器,所有的存储属性在构造完成前必须全部都有初始值。
指定构造器是类中最主要的构造器,每个类中都必须拥有至少一个指定构造器,其实默认的的构造器也属于指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。
便利构造器是类中比较次要的、辅助性的构造器。应当只在必要的情况下使用便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。
类的指定构造器写法跟值类型的简单构造器一样,便利构造器也采用相同的写法,但需要在
init
关键字之前放置convenience
关键字:// 指定构造器 init(parameters) statements // 便利构造器 convenience init(parameters) statements
类的构造器代理规则
为了简化指定构造器和便利构造器之间的调用关系,Swift采用以下三条规则来限制构造器之间的代理调用:
- 规则1:指定构造器必须调用其父类的指定构造器(如果存在调用行为);
- 规则2:便利构造器必须调用同一类中定义的其他构造器(只要使用便利构造器);
- 规则3:便利构造器必须最终以调用一个指定构造器结束。即必须有
super.init(...)
方法。
简而言之,就是指定构造器必须总是向上代理,便利构造器必须总是横向代理。下面图例中展示了一种针对四个类的类层级结构,它演示了指定构造器如何在类层级中充当管道的作用,在类的构造器链上简化了类之间的相互关系,也表达出了便利构造器智能横向代理的意思:
两段式构造过程
P.S.先提前说吧,我并不确定类的构造过程是不是真的需要这么细,至少现在这个小节我已经很认真的读两遍了,原谅我还不是很能理解两段式的构造过程到底是怎样的具体过程。希望在以后的开发中能对这小节的内容有深刻的体会。
Swift中类的构造过程包含两个阶段。第一个阶段,每个存储型的属性通过引入它们的类的构造器来设置初始值。每当一个存储属性的初始值被确定后,第二阶段开始,他给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。
两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外的构造器意外的赋予了不同的值。(P.S.教材中对于两段式讲了非常多的内容,唯一的缺憾就是没有代码示例,而我也并没有理解太好,本来想按照自己现有的理解写一段,但是总感觉不对,所以就先空着吧,理解了再回来补)
Swift编译器将执行4中有效的安全检查,以确保两段式构造过程能顺利完成:
- 安全检查1:指定构造器必须保证它所在的类引入的所有属性都必须先初始化完成,之后才能将其它的构造任务向上代理给父类中的构造器。
- 安全检查2:指定构造器必须先向上代理调用父类的构造器,然后再为继承的属性设置新值。如果没有这么做,指定构造器赋予的新值将被父类中的构造器覆盖。
- 安全检查3:便利构造器必须先代理调用同一类中的其他构造器,然后再为任意的属性赋新值。如果没这么做,便利构造器赋予的新值将会被同一类中的其他指定构造器所覆盖。
- 安全检查4:构造器在第一阶段完成之前,不能调用任何实例方法、不能读取任何实例属性的值,
self
的值也不能被引用。
类实例在第一阶段结束以前并不是完全有效,仅能访问属性和调用方法,一旦完成第一阶段,该实例才会声明为有效实例。以下是两段式构造过程中基于上面安全检查的构造流程展示。
首先是阶段1:
- 某个指定构造器或便利构造器被调用;
- 完成新实例内存的分配,但此时内存还没有被初始化;
- 指定构造器确保其所在的类引入的所有存储性属性都已经赋初值。存储类型所属的内存完成初始化;
- 指定构造器将调用父类的构造器,完成父类属性的初始化;
- 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;
- 当到达构造器链最顶部,且已确保所有的实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化,此时阶段1完成。
阶段2:
- 从顶部构造链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问
self
、修改它的属性并调用实例方法等等。 - 最终,任意构造器链中的便利构造器可以有机会定制实例和使用
self
。
看明白了么?教材中图解了这两个阶段。下图是构造过程阶段1,一旦父类中所有的属性都有了初始值,实例的内存被认为是完全初始化,而阶段1已完成。
阶段1完成后,父类中的指定构造器现在有机会进一步来定制实例(尽管它没有这必要)。一旦父类中指定的构造器完成调用,子类的定制构造器可以执行更多的定制操作(同样,它也没有这种必要)。最终,一旦子类的指定构造器完成调用,最开始被调用的便利构造器可以执行更多的定制操作。
构造器的继承与重写
跟 Objective-C 的子类不同,Swift中的子类不会默认继承父类的构造器。Swift这种机制可以防止一个父类的简单构造器被一个更专业的子类继承时,却错误的用父类来创建子类的实例。
当重写父类的指定构造器、属性、方法或者下标脚本等特性时,都要在前面加上关键字
override
,只要含有override
修饰符就回去检查父类是否有相匹配的重写指定构造器和验证重写构造器参数。下面的例子中定义了一个基础类叫
Vehicle
,基础类中声明一个存储属性numberOfWheels
、计算属性descroption
:class Vehicle var numberOfWheels = 0 // 存储属性 var description: String // 计算属性 return "number of wheels = \\(numberOfWheels)" // 默认构造器创建实例 let vehicle = Vehicle()
Vehicle
类并没有自定义构造器,前面说过,它会生成一个默认构造器,而默认构造器在类中是指定构造器。创建一个继承
Vehicle
的子类Bicycle
,这里用到了继承:class Bicycle: Vehicle override init() // 重写 super.init() numberOfWheels = 2
Bicycle
的构造器init()
一开始调用super.init()
方法,这个方法的作用是调用Bicycle
的父类Vehicle
。这样可以确保Bicycle
在修改属性前它所继承的属性numberOfWheels
能被Vehicle
类初始化。子类可以在初始化时修改继承到的变量属性,但是不能修改继承过来的常量属性。自动构造器的继承
子类不会默认继承父类的构造器,但是如果满足下面规则,父类构造器是可以被自动继承的:
规则1:如果子类没有任何指定构造器,它将自动继承父类的指定构造器。
规则2:如果子类提供了所有父类指定构造器的实现 – 不论是通过规则1继承来的,还是通过自定义实现的 – 它将自动继承所有父类的便利构造器。
即使在子类中添加了更多的便利构造器,这两条规则仍然适用。看下面的实例代码,注意注释文字:
class Vehicle var numberOfWheels = 0 // 存储属性 var isFly = false // 是否可以飞 var description: String // 计算属性 return "number of wheels = \\(numberOfWheels)" // 指定的构造器1 init(vehicleIsFly isFly: Bool) self.isFly = isFly print("\\(isFly ? "The vehicle can fly" : "can not fly")") // 指定构造器2 init(vehicleCount count: Int, vehicleWheels wheels: Int) self.numberOfWheels = wheels print("totleWheels = \\(count * wheels)") // 便利构造器 convenience init(vehicleIsSpeed speed: Double) self.init(vehicleIsFly: true) self.isFly = true // 创建子类1 class Bicycle: Vehicle // 满足规则1,子类中没有定义任何指定构造器,定义便利构造器不影响规则1 convenience init(BicyclePrice price: Double) self.init(vehicleIsFly: true) print("price = \\(price)") /*********** 如果加上下面这三行代码,重写部分指定方法,会有各种错误 ***********/ /* override init(vehicleCount count: Int, vehicleWheels wheels: Int) super.init(vehicleCount: count, vehicleWheels: wheels) print("subclass override") */ // 子类1的实例 let bicycle1 = Bicycle(vehicleCount: 3, vehicleWheels: 2) // 继承到的指定构造器1 let bicycle2 = Bicycle(vehicleIsFly: true) // 继承到的指定构造器2 let bicycle3 = Bicycle(vehicleIsSpeed: 100) // 继承到的便利构造器 // 自身的便利构造器 let bicycle4 = Bicycle(BicyclePrice: 1000)
指定构造器和便利构造器实例
其实,上面的实例代码就是一个比较完整的实例了。
可失败的构造器
必要构造器
通过闭包和函数来设置属性的默认值
结束语
这一节看的还真是痛苦啊,断断续续写了5天还没有写完,写的还真是啰嗦,加上自己始终不明不白,这感觉真不咋地。就这么着吧,打个点,以后积累了一定了知识后再回来补,如果还能记得的话……
以上是关于Swift-构造过程(Initialization)的主要内容,如果未能解决你的问题,请参考以下文章
Lazy Initialization with Swift
绕过子类中的Initialization:Swift和SpriteKit