首页 > 代码库 > Swift学习之路五(构造过程Initialization)
Swift学习之路五(构造过程Initialization)
写在前面
构造过程(Initialization),Swift中的构造器和Objective-C中的构造函数还是有很大不同的。即使Swift中类的构造器和值类型 (枚举和结构体)的构造器也有所不同。 下面我们慢慢看到底有那些不同。
1. 构造器,构造过程包括为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务;
与 Objective-C 中的构造器不同,Swift 的构造器无需返回值;
构造器以init命名。
注意:当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观测器(property observers)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | struct Dog { var name:String //定义一个无参数的构造器 init(){ name="狗狗" } } var aDog=Dog() //如果实力生命为let类型,则属性name不可以更改,因为结构体是值类型 aDog.name="毛毛" |
2.自定义构造器
你不希望为构造器的某个参数提供外部名字,你可以使用下划线_来显示描述它的外部名,以此覆盖上面所说的默认行为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | struct Person { var name:String="" var age:Int=0 //拥有一个参数的构造函数,外部参数名ofName init(ofName nameP:String){ name=nameP } //ageP即作为内部参数,又作为外部参数 init(ageP :Int){ age=ageP } //拥有两个参数的构造函数 init(ofName nameP:String , andAge ageP:Int){ name=nameP age=ageP } } var person1=Person(ofName: "Lves") var person2=Person(ageP: 23) var person3=Person(ofName: "乐Coding", andAge: 1) |
3. 如果你定制的类型中包含一个可以取值为空的存储类型,你可以把她定义为可选类型optional type,可选类型将自动初始化为空nil
4. 构造过程中常量属性的修改
对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class Programmer { let skill:String var name:String //可选性类型 var content:String? init (name:String ,skill:String){ self.name=name self.skill=skill } func getName()->String{ return name } } let pro=Programmer(name: "Lves",skill:"swift") var name=pro.getName() pro.skill |
Swift 将为所有属性已提供默认值的且自身没有定义任何构造器的结构体或基类,提供一个默认的构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。
如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器
5.构造器代理
构造器代理:构造器通过调用其它构造器来完成实例的部分构造过程,目的是为了减少代码重复;
构造器代理的实现规则和形式在值类型和类类型中有所不同,先说一下值类型(枚举和结构体)的
1)对于值类型,你可以使用self.init在自定义的构造器中引用其它的属于相同值类型的构造器。并且你只能在构造器内部调用self.init。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | struct Size { var w=0.0,h=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(cent:Point ,size:Size){ let originX=cent.x-size.w/2 let originY=cent.y-size.h/2 //调用默认构造函数 self.init(origin:Point(x: originX, y: originY),size:size) } } |
6. 类的继承和构造过程 (翻译自 ios8 programming language,感谢 http://numbbbbb.gitbooks.io/)
类里边包括从父类继承来的存储性属性,都要在构造过程中初始化。swift为类提供了两种:指定构造器和便利构造器。
引用:
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。
每一个类都必须拥有至少一个指定构造器。在某些情况下,许多类通过继承了父类中的指定构造器而满足了这个条件。具体内容请参考后续章节自动构造器的继承。
便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。
你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清晰明了。
规则 1
指定构造器必须调用其直接父类的的指定构造器。
规则 2
便利构造器必须调用同一类中定义的其它构造器。
规则 3
便利构造器必须最终以调用一个指定构造器结束。
一个更方便记忆的方法是:
指定构造器必须总是向上代理
便利构造器必须总是横向代理
7. 两段式构造过程 (翻译自 ios8 programming language)
Swift 中类的构造过程包含两个阶段。第一个阶段,每个存储型属性通过引入它们的类的构造器来设置初始值。当每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。
Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程能顺利完成:
安全检查 1
指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化。
安全检查 2
指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
安全检查 3
便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
安全检查 4
构造器在第一阶段构造完成之前,不能调用任何实例方法、不能读取任何实例属性的值,self的值不能被引用。
以下是两段式构造过程中基于上述安全检查的构造流程展示:
阶段 1
某个指定构造器或便利构造器被调用;
完成新实例内存的分配,但此时内存还没有被初始化;
指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化;
指定构造器将调用父类的构造器,完成父类属性的初始化;
这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;
当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段1完成。
阶段 2
从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。
最终,任意构造器链中的便利构造器可以有机会定制实例和使用self。
感觉构造器的执行过程有点像递归构造(小编个人猜测)
8.构造器的继承和重载:Swift 中的子类不会默认继承父类的构造器
如果你重载的构造器是一个指定构造器,你可以在子类里重载它的实现,并在自定义版本的构造器中调用父类版本的构造器。
如果你重载的构造器是一个便利构造器,你的重载过程必须通过调用同一类中提供的其它指定构造器来实现。
与方法、属性和下标不同,在重载构造器时你没有必要使用关键字override;
假设要为子类中引入的任意新属性提供默认值,请遵守以下2个规则:
规则 1
如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
规则 2
如果子类提供了所有父类指定构造器的实现–不管是通过规则1继承过来的,还是通过自定义实现的–它将自动继承所有父类的便利构造器。
即使你在子类中添加了更多的便利构造器,这两条规则仍然适用。
语法:
指定构造器的写法和结构体的构造器一样
1 2 | init(p){ } |
便利构造器也采用相同样式的写法,但需要在init关键字之前放置convenience关键字,并使用空格将它们俩分开:
1 2 3 | convenience init(parameters) { statements } |
定义一个基类,人
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Human{ var name:String //构造函数,指定构造器 init(name:String){ self.name=name } //便利构造器 convenience init(){ //调用本类的指定构造器 self.init(name:"noname") } } let aMan=Human(name: "LvesLi") |
init(name:String) 被当做指定构造器是因为它确保所有新Human实例中的存储性属性都能被初始化;
Human类没有父类,所以init(name: String)构造器不需要调用super.init()来完成构造
定义第二个类是Human的子类Student。Student拥有自己的属性Grade:Int
在Student的指定构造器中,先初始化自己的新属性,然后代理给父类的指定构造器.这个过程满足两段式构造过程中的安全检查1
便利构造器让实力创建更方便快捷,它调用了本类的指定构造器。便利构造器只是简单的将任务代理给了同一类里提供的指定构造器。
因为这个便利构造器重写父类的指定构造器init(name: String),必须在前面使用使用override标识。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | class Student :Human { var grade:Int //指定构造器 init(name:String,grade:Int){ self.grade=grade super.init(name: name) } //便利构造器 override convenience init(name: String) { self.init(name:name,grade:1) } } //创建实例,三个构造器都可以 let st1=Student() let st2=Student(name: "Lves") let st3=Student(name: "Lves", grade: 4) |
定义第三个类 UniversityStudent(大学生),多了一个属性heterosexual bool,是否有女/男朋友
下面的类没有定义构造器是因为他的新属性有默认值,将自动继承所有父类中的指定构造器和便利构造器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class UniversityStudent: Student { var heterosexual:Bool=false var desciption:String{ return "\(name) is \(grade) grade,and if has a heterosexual:\(heterosexual) " } } let st4=UniversityStudent() let st5=UniversityStudent(name: "Lves") let st6=UniversityStudent(name: "Lves", grade: 4) st6.desciption //Lves is 4 grade,and if has a heterosexual:false |
9. 通过闭包和函数来设置属性的默认值
如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
这种类型的闭包或函数一般会创建一个跟属性类型相同的临时变量,然后修改它的值以满足预期的初始状态,最后将这个临时变量的值作为属性的默认值进行返回。
下面列举了闭包如何提供默认值的代码概要:
1 2 3 4 5 6 7 8 9 10 11 12 13 | class SomeClass { let someProperty: SomeType = { // 在这个闭包中给 someProperty 创建一个默认值 // someValue 必须和 SomeType 类型相同 return someValue }() } |
注意:
1.闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 需要立刻执行此闭包。如果你忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
2.如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能够在闭包里访问其它的属性,就算这个属性有默认值也不允许。同样,你也不能使用隐式的self属性,或者调用其它的实例方法。
未完待续.......继续阅读请到我们的博客:http://www.lvesli.com 。有更多iOS开发的资源,你也可以订阅我们的官方微信账号:乐Coding.或者扫描下方
Swift学习之路五(构造过程Initialization)