你真的了解构造过程吗(构造过程和析构过程)
注:本文为作者整理 , 尽量没有废话,都是干货 。希望看官们可以有所收获。
1、构造过程
构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务,在类中声明一个存储型变量 ,必须为其赋初始值 。可以通过构造器为其赋值
当我们创建一个类或者结构体的时候 系统默认给我们创建一个无参数的构造器 ,语法如下 :
init() {
//可以在这里给存储型变量赋初始值
}
class Student{
var name:String
init(){
name = "zhangsan "
}
}
这里我们在构造方法中为name
赋了初值 ,当然你也可以直接var name:String=“zhangsan”
定义的时候指定一个初始值
你可以通过输入参数和可选属性类型来定制构造过程,也可以在构造过程中修改常量属性
class Student1{
var name:String
let age:Int
init(name:String){
self.age = 20
self.name = name
}
init(){
self.age = 30
self.name = "zhangsan"
}
init(nickName name:String){
self.age = 40
self.name = "Nike Name:\(name)"
}
}
这个类中我们定义了三个构造方法 ,你可以根据需要选择调用任何一个
let st = Student1()
print(st.name)
let st1 = Student1(name: "lisi")
print(st1.name)
let st2 = Student1(nickName: "wangwu")
print(st2.name)
//结果:
//zhangsan
//lisi
//Nike Name:wangwu
swift 会为每个构造器自动添加外部参数名 ,相当于唯一符号,也可以自己制定外部参数名 。对外部参数名不了解的可以去看Swift详解之三———-函数(你想知道的都在这里)
如果你给一个变量声明位可选类型 , swift会默认赋为 nil ,证明这是一个在逻辑上允许为空得存储型属性var tele:Int?
在一个结构体中,不仅有默认的无参数构造器 , 系统还有声明一个逐一成员构造器
struct MySize {
var width = 0.0,height = 0.0
}
var size = MySize(width: 27.3,height: 45.2) //系统会自动给结构体创建一个逐一成员构造器。
如果在结构体中 ,自己显示指定了构造器 ,将不能再调用逐一构造和默认无参数构造
struct MySize1 {
var width = 0.0,height = 0.0
init(width:Double){
self.width = width ;
height = 2*width ;
}
// init(){
//
// }
}
//var size1 = MySize1(width: 27.3,height: 45.2) //报错 extra argument 'height' in call
//var size1 = MySize() //invalid redeclaration of 'size1'
var size1 = MySize1(width: 27.3) //只能通过这种方式
不过可以自己手动再添加那两个 。
- 指定构造器和便利构造器
这个算构造器这块比较绕的地方 ,有涉及到继承重载的一些东西 。这些都是面向对象的基础 ,这里不再详细说这些 ,做过任何一门面向对象语言的应该都回比较了解 。后面有时间应该会专门写一设计模式的模块 。
类里面的所有存储型属性–包括所有继承自父类的属性–都必须在构造过程中设置初始值。
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。(我们上面例子的构造器 都是指定构造器 )
便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清、晰明。
一下说了这么多概念 ,看着都头晕 ,总结下就是 指定构造器可以根据父类链往上调用父类的构造器 , 遍历构造器调用同一个类中的指定构造器。。就这两句重要的。
我们来看个例子
class Book{
var name:String
init(name:String){
self.name = name
}
convenience init(){
self.init(name:"Swift全解析")
}
}
我们这里也提供了一个没有参数的构造器init()
,这个init()
构造器为新实例提供了一个默认的占位名字”Swift全解析” ,这个就是便利构造器
,前面用关键字 convenience
声明 。只能调用本类中的指定构造器 。
var swift = Book()
print(swift.name) //Swift全解析
var s = Book(name:"object-c")
print(s.name) //object-c
这个就不用解释了。
我们来搞一个子类来提供书本的数量 。
class CountBook:Book{
var bookCount:Int
init(name:String,count:Int){
self.bookCount = count
super.init(name:name);
}
override convenience init(name:String){
self.init(name:name,count:1);
}
}
这里在指定构造器中传入两个参数 ,并且往上调用了父类的构造器 。遍历构造器 ,重写了父类的构造器 ,并且调用了本类的指定构造器 。
let cb2 = CountBook(name:"精通 Swift ")
print("\(cb2.bookCount),\(cb2.name)") //1,精通 Swift
//上面是调用CountBook的便利构造器 count给了初始值所以这里count是1
let cb1 = CountBook()
print(" \(cb1.bookCount),\(cb1.name)") // 1,Swift全解析
//这里估计有些人不太理解了,你看父类 ,init() 是不是init(name:String) 的便利构造器 ,然后子类重写了init(name:String) 又加了count得初始化 。。明白了吧!!
看了这些例子我们再来看下官方给的一些概念性的东西 。
指定构造器和便利构造器
类里面的所有存储型属性–包括所有继承自父类的属性–都必须在构造过程中设置初始值。
Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。
便利构造器是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例
你应当只在必要的时候为类提供便利构造器,比方说某种情况下通过使用便利构造器来快捷调用某个指定构造器,能够节省更多开发时间并让类的构造过程更清、晰明。
构造器链
为了简化指定构造器和便利构造器之间的调用关系,Swift 采用以下三条规则来限制构造器之间的代理调用:
1. 指定构造器必须调用其直接父类的的指定构造器。
2. 便利构造器必须调用同一类中定义的其它构造器。
3. 便利构造器必须最终以调用一个指定构造器结束。
(指定构造器必须总是向上代理
便利构造器必须总是横向代理)
两段式构造过程
Swift 中类的构造过程包含两个阶段。第一个阶段,每个存储型属性通过引入它们的类的构造器来设置初始值。当每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。
两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外一个构造器意外地赋予不同的值。
Swift 编译器将执行 4 种有效的安全检查,以确保两段式构造过程能顺利完成:
安全检查 1
指定构造器必须保证它所在类引入的所有属性都必须先初始化完成,之后才能将其它构造任务向上代理给父类中的构造器。
如上所述,一个对象的内存只有在其所有存储型属性确定之后才能完全初始化。为了满足这一规则,指定构造器必须保证它所在类引入的属性在它往上代理之前先完成初始化。
安全检查 2
指定构造器必须先向上代理调用父类构造器,然后再为继承的属性设置新值。如果没这么做,指定构造器赋予的新值将被父类中的构造器所覆盖。
安全检查 3
便利构造器必须先代理调用同一类中的其它构造器,然后再为任意属性赋新值。如果没这么做,便利构造器赋予的新值将被同一类中其它指定构造器所覆盖。
安全检查 4
构造器在第一阶段构造完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用self的值。
以下是两段式构造过程中基于上述安全检查的构造流程展示:
阶段 1
某个指定构造器或便利构造器被调用;
完成新实例内存的分配,但此时内存还没有被初始化;
指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化;
指定构造器将调用父类的构造器,完成父类属性的初始化;
这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;
当到达了构造器链最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段1完成。
阶段 2
从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问self、修改它的属性并调用实例方法等等。
最终,任意构造器链中的便利构造器可以有机会定制实例和使用self。
构造器的继承和重载
跟 Objective-C 中的子类不同,Swift 中的子类不会默认继承父类的构造器。Swift 的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。
在重载构造器时你必需使用关键字override。即使你重载的是默认构造器 也要加上override (重载属性 方法 下标脚本的时候都要加上override 关键字 )
看完这段话,云里雾里的。。
class Person {
var name = ""
}
class Teacher:Person {
var age:Int
override init(){
age=10 //如果需要对自己的属性进行初始化 在super之前
super.init(); //必须先调用父类的构造 对父类属性进行舒适化
name = "lisi" //这里对父类的属性重新初始化
}
}
这个例子包含了初始化自己,调用父类构造器 初始化父类属性的顺序 。是不是很清楚明了。
- 自动构造器的继承
子类不会默认继承父类的构造器。但是如果特定条件可以满足,父类构造器是可以被自动继承的
1、如果子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
2、如果子类提供了所有父类指定构造器的实现–不管是通过规则1继承过来的,还是通过自定义实现的–它将自动继承所有父类的便利构造器。
这个不用过多解释了吧,前面的例子都有 很好理解 、
- 可失败构造器
如果一个类或者结构体在构造过程中可能会失败,为其定义一个可失败的构造器是非常有必要的,语法 init? 注意:可失败的参数名和参数类型不能和其他非可失败的相同 通过return nil 来表示可失败构造器在什么情况下失败
来看一个例子
class Animal{
var name:String
init?(name:String) {
self.name = name
if (name.isEmpty){
return nil
}
}
}
let sheep = Animal(name: "sheep")
print(sheep?.name) //Optional("sheep")
let dog = Animal(name: "")
print(dog?.name) //nil
当然你也可以在链式构造器(便利构造 指定构造等) 中使用可失败构造器 ,也可以早重载可失败构造器
- 必要构造器
在类的构造前面 加required 表明所有该类的子类都必须实现该构造器 。
class Book1{
var name : String
required init(name:String){
self.name = name
}
}
class swiftBook:Book1 {
}
//let sw = swiftBook() //missing argument for parameter 'name' in call 不能这样调用啦,必须加参数
- 通过闭包来设置默认值
意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift 需要立刻执行此闭包。如果你忽略了这对括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性
如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能够在闭包里访问其它的属性,就算这个属性有默认值也不允许。同样,你也不能使用隐式的self属性,或者调用其它的实例方法。
class Test {
var dog = "dog"
var name:String = {
var n = "zs"
//在这里不能使用self.dog
return n.uppercaseString
}()
}
let t = Test()
print(t.name) //ZS
2、析构过程
在一个类的实例被释放之前,析构函数被立即调用。用关键字deinit来标示析构函数,类似于初始化函数用init来标示。析构函数只适用于类类型。
Swift 通过自动引用计数(ARC)处理实例的内存管理。通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前关闭该文件。
在类的定义中,每个类最多只能有一个析构函数。析构函数不带任何参数,在写法上不带括号
deinit {
//执行析构过程
}
析构函数是在实例释放发生前一步被自动调用。不允许主动调用自己的析构函数。子类继承了父类的析构函数,并且在子类析构函数实现的最后,父类的析构函数被自动调用。即使子类没有提供自己的析构函数,父类的析构函数也总是被调用。
因为直到实例的析构函数被调用时,实例才会被释放,所以析构函数可以访问所有请求实例的属性,并且根据那些属性可以修改它的行为(比如查找一个需要被关闭的文件的名称)。
析构这里就是官方的一些解释了 ,没有什么好解释的,语法就这种 ,在具体的业务中根据自己的需求具体使用吧。。