博客地址:http://istudying.club/,小编自己搭建的博客哟😀。。。
今天我们来说说循环引用。什么是循环引用?当两个不同的对象各有一个强引用指向对方,导致双方的内存都无法释放的时候,那么循环引用就发生了。
不知道大家有没有听过一句话:人生就像打电话,不是你先挂,就是我先挂。其实用到这里是在合适不过了。假设有两个人打电话,彼此都强引用的对方的电话号码,但是如果双方都不想挂电话的话,那么电话就会一直通着。在OC中也是同样的道理,正常情况下,当一个类或者对象即将释放或者retainCount=0的时候,就会调用dealloc方法,释放相应的内存。但是,当这个对象被其他对象强引用的时候,那么它就不会被回收。此时如果处理不当的话就会造成循环引用。
首先我们先简单的看一个循环引用的例子。首先我们在第二个控制里面,声明一个TestThreeController对象three。
@class TestThreeController;
@interface TestSecController : UIViewController
@property (nonatomic, strong) TestThreeController *three;
@end
然后在.m文件中,定义一个按钮的点击事件用来push,并重写dealloc方法。
- (void)click
{
_three = [[TestThreeController alloc] init];
_three.sec = self; // 这就是造成循环引用的代码
[self.navigationController pushViewController:_thr animated:YES];
}
- (void)dealloc
{
NSLog(@"TestSecController - dealloc");
}
紧接着我们在第三个控制器里面声明一个TestSecController对象的sec。
@class TestSecController;
@interface TestThreeController : UIViewController
@property (nonatomic, strong) TestSecController *sec;
@end
然后在其.m文件中重写dealloc方法。
- (void)dealloc
{
NSLog(@"ThreeViewController - dealloc");
}
正常情况下,从TestThreeController控制器pop到TestSecController的时候,会执行ThreeViewController的dealloc方法。但此时可想而知,不会执行,因为循环应用了。其实解决方法很简单,就是将中一个属性修饰符改成weak修饰就好了(关于strong和weak,我这里简单说明一下,一个对象可以有多个指针共同指向,假如A和B两个Strong指针同时指向一个NSString对象”name”,当A重新指向另一个NSString对象”word”时,此时,B仍然指向”name”,如果B也指向别的对象时,因为”name”此时不被任何对象所持有,就会被系统回收。如果B是weak类型的,那么当A重指向的时候,因为B是弱引用,并不持有”name”这个对象,所以”name”就会被系统回收)。
其实OC中代理也是一样的,这里我就拿系统的tableView来举例,我们在控制器中声明一个@property (nonatomic, strong) UITableView *tableView的属性,那么当前控制器就持有了这个tableView,当我们在给tableView设置dataSource和delegate的时候都指向self(也就是当前的控制器),那么tableView也就持有了当前的控制器,所以循环引用便产生了,现在大家应该明白代理为什么用weak或者assign修饰了吧。
现在我们来说一下blcok,相对代理而言,我个人是比较喜欢blcok的。首先解释一下,在声明block时为啥子用copy?因为block原本在栈区,那么它的生命周期由系统管理,如果它所属的变量的作用域结束,那么它就被废弃了。用copy将栈上的block复制到堆上,这样它的生命周期就由我们手动进行管理了,当其超过变量作用域时,堆上的block还可以继续存在。
还有一个问题,是不是OC中所有的blcok都需要我们使用copy手动将其复制到堆上?答案NO,Cocoa框架中使用含有usingBlock方法名的方法,或者GCD的API中传递Block时,我们不需要这么做,也就是说在usingBlock和GCD中的block并不会造成循环引用。所以,如果是我们自己声明的block,在使用的时候,需要注意会不会造成循环引用,而系统提供的block一般不会造成循环引用。
现在我们来说说NSTimer,这里在控制器里面,我们先声明一个属性
@property (nonatomic, strong) NSTimer *timer;
然后初始化timer
_timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(run) userInfo:nil repeats:YES];
NSTimer为什么会造成循环引用呢,这里我们来分析一下,首先,控制器持有NSTimer,然后在timer初始化的时候,target设置为self,那么timer就持有了当前控制器,这就造成了循环引用,所以在平时我们不使用定时器的时候,记得[_timer invalidate];并且_timer = nil;从而避免循环引用。
说起这个target,我不由得想起了貌似不只有NSTimer有,只有继承UIControl的都有,于是乎,我又在当前控制器声明了一个UIButton,并添加了addTarget,根据上面的逻辑,这不就造成了循环引用了么。说实话,当初我一直也想不通,于是我查阅了一下苹果的官方文档。
对于UIButton,说明如下
The control does not retain the object in the target parameter. It is
your responsibility to maintain a strong reference to the target object
while it is attached to a control
大致意思就是button并不会强引用这个target对象,这样一来,也就不存在循环引用了。
对于NSTimer,说明如下
The object to which to send the message specified by aSelector when the
timer fires. The timer maintains a strong reference to this object until it (the
timer) is invalidated
大致意思就是timer会强引用这个target对象,直到timer执行了invalidate方法。所以,timer在使用的时候需要注意释放的问题,其次NSTimer继承NSObject并不是UIControl,所以不同情况不同对待。
好了,就说这么多了,说的比较简单,如果有什么不对的地方,希望留言指正。