强引用和弱引用:
OC中的内存管理是通过“引用计数器”来实现的。一个对象的生命周期取决于它是否还被其他对象引用(是否retainCount=0)。但在有些情况下,我们并不希望对象的销毁时间由是否被其他对象引用来决定,而是这个对象本该是什么时候销毁就什么时候被销毁。这时,我们得引入“强引用”和“弱引用”的概念。
强引用:
当前对象被其他对象引用时,会执行retain操作,引用计数器+1。当retainCount=0时,该对象才会被销毁。因为我们要进行对象的内存管理,所以这是默认的引用方式。(默认是强引用)
弱引用:
当前对象的生命周期不被是否由其他对象引用限制,它本该什么时候销毁就什么时候被销毁。即使它的引用没断,但是当它的生存周期到了时就会被销毁。
特点:
强引用持有对象;弱引用不持有对象。
强引用可以释放对象,但弱引用不可以,因为弱引用不持有对象,当弱引用指向一个强引用所持有的对象时,当强引用将对象释放掉后,弱引用会自动的被赋值为nil,即弱引用会自动的指向nil。
在强引用中,有时会出现循环引用的情况,这时就需要弱引用来帮忙(__weak)。
理解
当最后一个指向对象的的strong类型的指针离开,这个对象将被释放,如果这个时候还有weak指针指向该对象,则会清除所有剩余的weak指针。
weak指针不持有对象,不影响对象的retainCount。
气球只有被牵着才不会飞走,牵气球的角色有两种:1、弱者-太弱,拉不动,只能在旁边看着,只能作为观察者和使用者,可以有多个。2、强者-拥有气球,负责掌握牵扯气球的线,可以有多个,并且每个都会独自拥有一条牵扯的线。
当所有的线都消失(被剪断)的情况下,气球就会飞走消失。
只有当所有的强者手中的线都间断的时候,才会导致气球消失,而这个时候是不论弱者有多少,都没有任何作用。而所有的弱者都消失时,只要有一个强者还在,那么气球就不会消失。
弱者-weak引用,强者-strong引用
在定义属性时,若声明为retain
类型的,则就是强引用;若声明为assign
类型的,则就是弱引用。
后来内存管理都由ARC来完成后,若是强引用,则就声明为strong
;若是弱引用,则就声明为weak
。
所以说,retain和strong是一致的(声明为强引用)
assign和weak是基本一致的(声明为弱引用)。
之所以说它俩是基本一致是因为它俩还是有所不同的,weak严格的说应当叫“ 归零弱引用 ”,即当对象被销毁后,会自动的把它的指针置为nil,这样可以防止野指针错误。而assign销毁对象后不会把该对象的指针置nil,对象已经被销毁,但指针还在痴痴的指向它,这就成了野指针,这是比较危险的。
weak只能修饰对象类型;assign一般用来修饰基础数据类型(NSInteger,CGFloat等)和C数据类型(int,float,double)等。
todo - 52个方法
unsafe_retain assign
避免“强引用循环“的僵局:
默认的引用方式是强引用,但上面说了有时我们还得使用弱引用,那是什么情况呢?
答案,强引用循环:A对象强引用了B对象,B对象也强引用了A。因为都是强引用,也就是无论是A是B都要在对方的引用断了后才能销毁,但要断了引用,就必须对方对象销毁。就会出现这种僵局,为了避免出现这种情况,就应该有一个对象“示弱”,使其为“弱引用”。
由于要进行内存管理的缘故,OC里的引用默认都是强引用,但为了避免出现”强引用循环僵局“,所以有了弱引用(assign/weak)。
比较常见的,视图中的父子视图之间的引用:父视图强引用子视图,子视图弱引用父视图。
为什么IBOutlet属性是weak的?
因为当我们将控件拖到Storyboard上,相当于新创建了一个对象,而这个对象是加到视图控制器的view上,view有一个subViews属性,这个属性是一个数组,里面是这个view的所有子view,而我们加的控件就位于这个数组中,那么说明,实际上我们的控件对象是属于view的,也就是说view对加到它上面的控件是强引用。当我们使用Outlet属性的时候,我们是在viewController里面使用,而这个Outlet属性是有view来进行强引用的,我们在viewController里面仅仅是对其使用,并没有必要拥有它,所以是weak的。
以UIButton为例:UIViewController->UIView->UIButton
可以改成 strong 吗?
如果将weak改为strong,也是没有问题的,并不会造成强引用循环。当viewController的指针指向其他对象或者为nil,这个viewController销毁,那么对控件就少了一个强引用指针。然后它的view也随之销毁,那么subViews也不存在了,那么控件就又少了一个强引用指针,如果没有其他强引用,那么这个控件也会随之销毁。
IBOutlet的视图属性为什么被设置成weak?可以改成 strong 吗?
delegate属性为什么用weak修饰?
案例说明:
|
|
1.Passenger类 Passenger.h文件
乘客拥有一个代理的属性.并且用修饰词weak修饰的
|
|
2.Passenger类 Passenger.m文件
当该类的对象被销毁时会调用-dealloc方法(在这个案例中用来观察对象是否被销毁了)
|
|
3.Driver类 Driver.m文件
|
|
4.我们介绍最后一个类 ViewController类
在ViewController的方法ViewDidLoad中创建司机对象,并且该对象作用的范围是这个方法内部.这个方法执行完成,driver对象就会被销毁了.
|
|
上面的代码执行完成之后,运行结果如下:
|
|
分析:
使用weak在程序运行的时候不会造成循环引用,对象都会被顺利的销毁,所以会调用司机类和乘客类的delloc方法
使用strong在程序运行的时候会造成循环引用(reatainCount不为0,只要有强引用,计数器就+1),对象都不会的销毁,所以司机类和乘客类不会调用delloc方法,从而造成了内存泄露的问题
block中的循环引用
题外话:block为什么用copy?
block是一个对象,所以block在创建的时候内存是默认在stack(栈)上的. 而不是在heap(堆)上的.所以他的作用域仅限创建时候的当前上下文(函数,方法等),当在作用域外调用block就会崩溃. Copy可以将block从内存栈区移动到堆区.这样在作用域外也不会崩溃了. 但在ARC下, 使用copy与strong其实都一样, 因为block的retain就是用copy来实现的.block还是建议使用copy修饰.因为MRC下就是就是用copy修饰的.
https://www.jianshu.com/p/9a6e00a54b6c
当A对象里面强引用了B对象,B对象又强引用了A对象,这样两者的retainCount值一直都无法为0,于是内存始终无法释放,导致内存泄露。所谓的内存泄露就是本应该释放的对象,在其生命周期结束之后依旧存在。
当然也存在自身引用自身的,当一个对象内部的一个obj,强引用的自身,也会导致循环引用的问题出现。常见的就是block里面引用的问题。
我们使用block解决循环引用虽然可以控制对象持有时间,在block中还能动态的控制是block变量的值,可以赋值nil,也可以赋值其他的值,但是有一个唯一的缺点就是需要执行一次block才行。否则还是会造成循环引用。
值得注意的是,在ARC下__block会导致对象被retain,有可能导致循环引用。而在MRC下,则不会retain这个对象,也不会导致循环引用.
weakSelf 和 strongSelf
1.weakSelf
__weak __typeof(self)weakSelf = self;
这是AFN里面的写法。。
#define WEAKSELF typeof(self) __weak weakSelf = self;
这是我们平时的写法。。
区分__typeof() 和 typeof(): 其实两者都是一样的东西,只不过是C里面不同的标准,兼容性不同罢了。
|
|
这两种写法就是完全一样的。
我们可以用WEAKSELF来解决block循环引用的问题
假设自定义的一个student类
|
|
|
|
这里肯定出现了循环引用了。student的study的Block里面强引用了student自身。根据深入研究Block捕获外部变量和__block实现原理的分析,可以知道,_NSConcreteMallocBlock(存在堆里)捕获了外部的对象,会在内部持有它。retainCount值会加一。(强引用)
这里形成环的原因block里面持有student本身,student本身又持有block。
如下, 就能解决循环引用的问题。
|
|
为什么iOS的Masonry中的self不会循环引用?
|
|
看到这里,读者应该就应该能回答这个问题了。
|
|
关于 Masonry ,它内部会引用变量 self,然后对其执行了setTranslatesAutoresizingMaskIntoConstraints:
方法, 进入block的是self.view,闭包虽然引用了self,但是self并没有引用这个闭包,执行完毕后,block会被销毁,没有形成环, 所以,没有引起循环依赖。
2.strongSelf
如果block里面不加strong typeof(weakSelf)strongSelf = weakSelf会如何呢?
|
|
输出:
|
|
重点就在dispatch_after这个函数里面。在study()的block结束之后,student被自动释放了。又由于dispatch_after里面捕获的 weak 的 student ,根据第二章讲过的weak的实现原理,在原对象释放之后,weak对象就会变成null(对应的weak对象就会被指为nil),防止野指针。所以就输出了null了。
那么我们怎么才能在weakSelf之后,block里面还能继续使用weakSelf之后的对象呢?
究其根本原因就是weakSelf之后,无法控制什么时候会被释放,为了保证在block内不会被释放,需要添加__strong。
在block里面使用的__strong修饰的weakSelf是为了在函数生命周期中防止self提前释放。strongSelf是一个自动变量当block执行完毕就会释放自动变量strongSelf不会对self进行一直进行强引用。
|
|
输出
|
|
weakSelf、strongSelf的用途
weakSelf 是为了block不持有self,避免Retain Circle循环引用。在 Block 内如果需要访问 self 的方法、变量,建议使用 weakSelf。
strongSelf的目的是因为一旦进入block执行,假设不允许self在这个执行过程中释放,就需要加入strongSelf。block执行完后这个 strongSelf 会自动释放,没有不会存在循环引用问题。如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。
关于Retain Circle最后总结一下,有3种方式可以解决循环引用。
《Effective Objective-C 2.0》(编写高质量iOS与OS X代码的52个有效方法)有介绍
@weakify、@strongify实现原理
@weakify、@strongify,这两个关键字是RAC中避免Block循环引用而开发的2个宏
@weakify、@strongify的作用和weakSelf、strongSelf对应的一样。
总结一下
|
|
经过分析以后,其实@weakify(self) 和 @strongify(self) 就是比我们日常写的weakSelf、strongSelf多了一个@autoreleasepool{}而已
深入研究Block用weakSelf、strongSelf、@weakify、@strongify解决循环引用