Swift学习笔记16——自动引用计数(Automatic Reference Counting)
2015-10-06 10:39
591 查看
对于类实例,它可能存在被多个变量引用的情况。如果在还有变量引用的情况下释放了改实例的话,那么其他变量再尝试访问这个实例的方法或属性的时候,程序就会崩溃。所以必须确保在以后都没有变量使用这个实例的情况下,才能去释放这个实例。对于值类型(结构体等),因为不存在多个变量对应一个实例的情况,所以不会有上述问题。为了解决这个问题,Swift使用自动引用计数(ARC)来管理内存。它只对引用类型起作用,对于值类型不起作用。
解释引用计数的概念
当你给一个创建一个类实例,并且把这个类实例赋值给某个变量或常量的时候,那么这个变量或常量就“拥有”这个实例,我们称为有了一个“强引用”。所谓的引用计数,就是这个实例被多少个常量或变量强引用了。
上面这最后一句代码就是变量a对一个Apple类的实例有了一个强引用。这时候这个实例的引用计数为1。
因为类实例是引用类型,所以你可以把这个引用传递给其他的常量或变量。
这时候这个实例就被三个变量或常量所引用,这个时候它的引用计数为3.
当你把其中的b设为nil的时候,引用计数就会变为2.
当该实例的引用计数变为0的时候,这个实例就会被销毁,销毁的时候就会调用它的deinit方法。注:因为上面用了常量,所以不能手动把常量设为nil,只能等这个常量离开作用域后被系统自动销毁。(如果你是在main.swift的全局中定义这些变量或常量的话,因为在main执行完之后才会释放这些变量或常量,所以不会打印deinit。但是你可以把他们放到一个函数里面,然后在main.swift里面调用这个函数。当这个函数执行完之后,这些变量或常量就会被释放。)
所以自动引用计数的规则很简单:
1、赋值给不加修饰符的常量和变量的时候,实例的引用计数加1。
2、当一个变量设为nil,或者变量(常量)离开作用域的时候,这个常量或变量所引用实例的引用计数减1。
3、当一个实例的引用计数为0的时候,它就会被销毁。
但是上面看似简单的规则也会有很多问题。比如下面的循环引用问题。
先定义两个类,一个Telephone类和一个Person类。Telephone类有一个Person类的属性,Person类里面有一个Telephone类的属性。
然后我们定义一个Person类的实例和一个Telephone类的实例。并对他们赋值
下面分析一下Person实例和Telephone实例的引用计数。
在定义Person实例的时候,赋值给了变量person,所以第一句代码后,Person实例的引用计数为1。同样第二句代码后,Telephone实例的引用计数也为1。
然后第三句代码把telephone赋值给了person.telephone。也就是person.telephone也对这个Telephone实例有了强引用,这时候Telephone实例的引用计数为2。
同样,第四局过后,Person实例的引用计数也为2。
现在的状态是一个Person实例强引用了一个Telephone实例,这个Telephone实例又强引用了这个Person实例。你强引用我,我强引用你。这样就成了一个循环引用。
接着执行下面代码,person变量和telephone变量释放对实例的强引用。
但是Person实例中的telephone属性仍然强引用着Telephone实例。同样的Telephone实例的person属性也引用着Person实例。所以Person实例和Telephone实例的引用计数都为1。但此时我们已经没办法再访问Person和Telephone的实例了。同时又因为他们的引用计数都为1,系统也不会释放他们。这样就造成了内存泄露。
为了解决这种循环引用的问题,办法就是截断这个循环。
第一种笨笨的解决方法就是在你把person或telephone变量设为nil之前,把person.telephone或telephone.person设为nil。这样就手动切断了循环引用。
而通用的解决方法就是引用一个新概念——弱引用(Weak Reference)
弱引用和强引用最大区别就是:当你把一个实例赋值给一个弱引用变量的时候,这个变量的引用计数不会加1。
为了实现这一点,在定义变量的时候在最前面加上weak关键字。下面我们重新定义Person类和Telephone类。
然后我们再次调用下面的代码
因为Telephone类里面的person属性是弱引用的,所以执行完了第4句之后,Telephone实例被telephone变量和person.telephone实例所引用,引用计数为2。而Person实例只被person变量所引用,引用计数为1.
当执行完person = nil 之后,person的引用计数就变为了0, 这个时候系统就会释放Person实例,这个过程中,person.telephone也会被释放,所以会导致Telephone实例的引用计数减1,变为1。
当执行完telephone = nil 之后,Telephone实例的引用计数变为0。系统释放Telephone实例。
关于这个弱引用再补充几点
第一、当一个弱引用变量所引用的实例被释放的时候,这个弱引用变量会被自动置为nil。
第二、因为第一条的内容,所以弱引用只能对变量使用,并且必须是可选类型。
第三、如果你在创建实例的时候就把它复制给一个弱引用变量,因为弱引用变量不会增加这个实例的引用计数,所以这个实例创建后立马就会被销毁。
第四、如果你将一个已经赋值的弱引用变量赋值给一个强引用变量(常量),那么这个实例的引用计数会加1。
Unowned Reference
Unowned Reference和弱引用一样,不会对实例产生强引用。区别在于Unowned Reference假设它所指向的实例总是有值的。所以Unowned Reference一般不会设置为可选类型。但缺点就是当Unowned Reference所指向的实例被释放的时候,Unowned Reference变量不会自动置为nil。
语法就是将weak关键字替换为unowned。但一个变量永远不会为nil的时候,建议使用unowned修饰。
循环引用第二种情况——闭包循环引用
在闭包的时候我们说过,闭包是引用类型,且会捕获值。设想,你把一个闭包声明为一个类的属性的时候,这个类的实例拥有了对这个闭包的强引用。此时如果你在这个闭包里面访问了这个类的其他属性(self.someProperty)或者方法(self.someMethod)的话。那么这个闭包就会捕获所访问的属性或方法,统称"捕获了self"。在访问实例的属性或方法的时候,必须使用self.的方式。Swift此意在提醒你可能会产生循环引用。
那么这时候又是一个循环引用了,self引用闭包,闭包引用self。导致这个实例永远不会被释放。
下面定义一个有闭包的Person类
这个闭包我们声明为了lazy类型,因为如果你想要在闭包里面访问到self的话,必须是在类初始化之后才行。而一般的属性是在类初始化的最开头阶段初始化的,所以不加lazy的闭包不能访问self关键字。上面的代码很明显闭包和类实例已经可能会产生循环引用了。为什么说可能呢?因为如果你一直没用到闭包的话,那么这个闭包就不会被初始化,所以也不会产生闭包对self的强引用,也就谈不上循环引用了。
所以如果仅仅执行下面代码
但是如果执行下面代码
这时候因为循环引用导致Person实例不会被释放。
解决这个循环引用同样有两种方式。
第一种是在不需要这个实例的时候,将这个可能会引起循环引用的闭包设为nil。
第二种是利用闭包的捕获列表。
下面是第二种方法的介绍
下面是语法定义例子,分别是有参数和没参数的闭包。在这种情况下,闭包对捕获的self不会产生强引用。(题外话,在OC中是通过定义另外一个对self的弱引用变量,然后将这个弱引用变量传递给block来实现的。)
这里就是用两个关键字weak和unowned将self修饰。weak和unowned的区别和之前所讲的是一样的。
解释引用计数的概念
当你给一个创建一个类实例,并且把这个类实例赋值给某个变量或常量的时候,那么这个变量或常量就“拥有”这个实例,我们称为有了一个“强引用”。所谓的引用计数,就是这个实例被多少个常量或变量强引用了。
class Apple { deinit{ print("deinit") } } var a:Apple! = Apple()
上面这最后一句代码就是变量a对一个Apple类的实例有了一个强引用。这时候这个实例的引用计数为1。
因为类实例是引用类型,所以你可以把这个引用传递给其他的常量或变量。
var b = a let v = a
这时候这个实例就被三个变量或常量所引用,这个时候它的引用计数为3.
当你把其中的b设为nil的时候,引用计数就会变为2.
当该实例的引用计数变为0的时候,这个实例就会被销毁,销毁的时候就会调用它的deinit方法。注:因为上面用了常量,所以不能手动把常量设为nil,只能等这个常量离开作用域后被系统自动销毁。(如果你是在main.swift的全局中定义这些变量或常量的话,因为在main执行完之后才会释放这些变量或常量,所以不会打印deinit。但是你可以把他们放到一个函数里面,然后在main.swift里面调用这个函数。当这个函数执行完之后,这些变量或常量就会被释放。)
所以自动引用计数的规则很简单:
1、赋值给不加修饰符的常量和变量的时候,实例的引用计数加1。
2、当一个变量设为nil,或者变量(常量)离开作用域的时候,这个常量或变量所引用实例的引用计数减1。
3、当一个实例的引用计数为0的时候,它就会被销毁。
但是上面看似简单的规则也会有很多问题。比如下面的循环引用问题。
先定义两个类,一个Telephone类和一个Person类。Telephone类有一个Person类的属性,Person类里面有一个Telephone类的属性。
class Telephone { var person: Person? deinit{ print("Telephone deinit") } } class Person { var telephone: Telephone? deinit{ print("Person deinit") } }
然后我们定义一个Person类的实例和一个Telephone类的实例。并对他们赋值
var person = Person() var telephone = Telephone() person.telephone = telephone telephone.person = person
下面分析一下Person实例和Telephone实例的引用计数。
在定义Person实例的时候,赋值给了变量person,所以第一句代码后,Person实例的引用计数为1。同样第二句代码后,Telephone实例的引用计数也为1。
然后第三句代码把telephone赋值给了person.telephone。也就是person.telephone也对这个Telephone实例有了强引用,这时候Telephone实例的引用计数为2。
同样,第四局过后,Person实例的引用计数也为2。
现在的状态是一个Person实例强引用了一个Telephone实例,这个Telephone实例又强引用了这个Person实例。你强引用我,我强引用你。这样就成了一个循环引用。
接着执行下面代码,person变量和telephone变量释放对实例的强引用。
person = nil telephone = nil
但是Person实例中的telephone属性仍然强引用着Telephone实例。同样的Telephone实例的person属性也引用着Person实例。所以Person实例和Telephone实例的引用计数都为1。但此时我们已经没办法再访问Person和Telephone的实例了。同时又因为他们的引用计数都为1,系统也不会释放他们。这样就造成了内存泄露。
为了解决这种循环引用的问题,办法就是截断这个循环。
第一种笨笨的解决方法就是在你把person或telephone变量设为nil之前,把person.telephone或telephone.person设为nil。这样就手动切断了循环引用。
而通用的解决方法就是引用一个新概念——弱引用(Weak Reference)
弱引用和强引用最大区别就是:当你把一个实例赋值给一个弱引用变量的时候,这个变量的引用计数不会加1。
为了实现这一点,在定义变量的时候在最前面加上weak关键字。下面我们重新定义Person类和Telephone类。
class Telephone { weak var person: Person? //把这个变量定义为了一个弱引用变量 deinit{ print("Telephone deinit") } } class Person { var telephone: Telephone? deinit{ print("Person deinit") } }
然后我们再次调用下面的代码
var person: Person? = Person() var telephone: Telephone? = Telephone() person!.telephone = telephone telephone!.person = person //第4句 person = nil // 执行完这句后打印 Person deinit telephone = nil // 执行完这句后打印 Telephone deinit
因为Telephone类里面的person属性是弱引用的,所以执行完了第4句之后,Telephone实例被telephone变量和person.telephone实例所引用,引用计数为2。而Person实例只被person变量所引用,引用计数为1.
当执行完person = nil 之后,person的引用计数就变为了0, 这个时候系统就会释放Person实例,这个过程中,person.telephone也会被释放,所以会导致Telephone实例的引用计数减1,变为1。
当执行完telephone = nil 之后,Telephone实例的引用计数变为0。系统释放Telephone实例。
关于这个弱引用再补充几点
第一、当一个弱引用变量所引用的实例被释放的时候,这个弱引用变量会被自动置为nil。
第二、因为第一条的内容,所以弱引用只能对变量使用,并且必须是可选类型。
第三、如果你在创建实例的时候就把它复制给一个弱引用变量,因为弱引用变量不会增加这个实例的引用计数,所以这个实例创建后立马就会被销毁。
第四、如果你将一个已经赋值的弱引用变量赋值给一个强引用变量(常量),那么这个实例的引用计数会加1。
Unowned Reference
Unowned Reference和弱引用一样,不会对实例产生强引用。区别在于Unowned Reference假设它所指向的实例总是有值的。所以Unowned Reference一般不会设置为可选类型。但缺点就是当Unowned Reference所指向的实例被释放的时候,Unowned Reference变量不会自动置为nil。
语法就是将weak关键字替换为unowned。但一个变量永远不会为nil的时候,建议使用unowned修饰。
循环引用第二种情况——闭包循环引用
在闭包的时候我们说过,闭包是引用类型,且会捕获值。设想,你把一个闭包声明为一个类的属性的时候,这个类的实例拥有了对这个闭包的强引用。此时如果你在这个闭包里面访问了这个类的其他属性(self.someProperty)或者方法(self.someMethod)的话。那么这个闭包就会捕获所访问的属性或方法,统称"捕获了self"。在访问实例的属性或方法的时候,必须使用self.的方式。Swift此意在提醒你可能会产生循环引用。
那么这时候又是一个循环引用了,self引用闭包,闭包引用self。导致这个实例永远不会被释放。
下面定义一个有闭包的Person类
class Person { var name: String? lazy var printName: Void->Void = { print(self.name) } init(name: String){ self.name = name; } deinit{ print("Person deinit") } }
这个闭包我们声明为了lazy类型,因为如果你想要在闭包里面访问到self的话,必须是在类初始化之后才行。而一般的属性是在类初始化的最开头阶段初始化的,所以不加lazy的闭包不能访问self关键字。上面的代码很明显闭包和类实例已经可能会产生循环引用了。为什么说可能呢?因为如果你一直没用到闭包的话,那么这个闭包就不会被初始化,所以也不会产生闭包对self的强引用,也就谈不上循环引用了。
所以如果仅仅执行下面代码
var p: Person? = Person(name: "Kate") p = nil //打印出 Person deinit
但是如果执行下面代码
var p: Person? = Person(name: "Kate") p?.printName() p = nil //打印出 Optional("Kate")
这时候因为循环引用导致Person实例不会被释放。
解决这个循环引用同样有两种方式。
第一种是在不需要这个实例的时候,将这个可能会引起循环引用的闭包设为nil。
第二种是利用闭包的捕获列表。
下面是第二种方法的介绍
下面是语法定义例子,分别是有参数和没参数的闭包。在这种情况下,闭包对捕获的self不会产生强引用。(题外话,在OC中是通过定义另外一个对self的弱引用变量,然后将这个弱引用变量传递给block来实现的。)
//有参数的情况 lazy var printName: ((String)->Void)? = { [unowned self] (say: String) -> Void in print(say,self.name) } //没参数的情况 lazy var printName2: (Void->Void)? = { [weak self] in print(self!.name) }
这里就是用两个关键字weak和unowned将self修饰。weak和unowned的区别和之前所讲的是一样的。
相关文章推荐
- Swift 一些环境配置
- Swift 与众不同的地方
- Swift 与众不同的地方
- swift2.0 计算圆面积
- 3.Swift 功能集锦(一)
- Swift简单语法
- 2.Swift 类和接口详解
- 1.Swift 基础语法
- Swift和OC代码注释分析 #pragma mark, FIXME and TODO
- swift2.0 - 渐来的美好(也许应该要收回我之前说的话了)
- Swift学习笔记15——初始化(Initialization)和析构(Deinitialization)其二
- 「Swift学习笔记」使用UILabel显示多行文本
- swift 设计模式---委托(delegate)模式传值
- swift设计模式--观察者模式
- Swift学习笔记14——初始化(Initialization)和析构(Deinitialization)其一
- Swift Mailer ——Comprehensive mailing tools for PHP
- swift页面传值之block(闭包)传值
- Swift学习笔记13——类继承(Inheritance)
- Swift语法之 guard
- [swift]监测iphone自由落体动作