什么是循环引用
循环引用是iOS开发中经常遇到的问题,它指的是两个或多个对象通过相互之间的强引用,形成了一个保留环,即使已经没有外部对象持有,也无法对其进行释放操作,也无法释放其占用的内存空间(引用计数器始终大于0)。
举个简单的例子:
对象A持有对象B,对象B持有对象C,对象C持有对象A,这时候它们之间就形成了一个引用环:
这时候如果有一个对象D,引用了对象A,那么由于ABC之间的循环引用,它们的引用计数器如下:
那么即使D释放了对象A,A、B、C的引用计数器仍然都是1,它们都不会被释放回收。
循环引用有什么危害
由于循环引用的存在,使得产生循环引用的对象始终占有内存空间,过多的循环引用会导致程序的内存占用不断升高,最终导致程序Creach。
常见的产生循环引用的几种情况
1.Delegate及其他类似的相互引用的情况
用weak而不是strong就能解决这个问题了:
@property (nonatomic, weak) id <DelegateProtocol> delegate;
2.Block
Block的循环引用,主要是发生在ViewController中持有了block,比如:
@property (nonatomic, copy) CallbackBlock callbackBlock;
同时在对callbackBlock进行赋值的时候又调用了ViewController的方法,比如:
self.callbackBlock = ^{
[self doSomething];
}];
就会发生循环引用,因为:ViewController->强引用了callback->强引用了ViewController,解决方法也很简单:
__weak __typeof(self) weakSelf = self;
self.callbackBlock = ^{
[weakSelf doSomething];
}];
那是不是所有的block都会发生循环引用呢?其实不然,比如UIView的类方法Block动画,NSArray等的 类的遍历方法,都不会发生循环引用,因为当前控制器一般不会强引用一个类。
3.NSTimer
NSTimer是一种很容易忽略的循环引用的情况。
因为timer会强引用self,而self又持有了timer,这就造成了循环引用。
那么能不能像Block那样用一个weak指针解决呢?比如
__weak typeof(self) weakSelf = self;
self.mytimer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(doSomeThing) userInfo:nil repeats:YES];
但是其实并没有用,因为不管是weakSelf还是strongSelf,最终在NSTimer内部都会重新生成一个新的指针指向self,这是一个强引用的指针,结果就会导致循环引用。
如何解决呢?用NSProxy就是一个狠简便的方法
NSProxy -- 解决NSTimer循环引用的利器
NSProxy本身是一个抽象类,它遵循NSObject协议,提供了消息转发的通用接口。NSProxy通常用来实现消息转发机制和惰性初始化资源。
PS:NSProxy的作用远不止这一个方面,如果想要更多的了解,这里有一些文章可以参考:
iOS开发--利用NSProxy实现消息转发-模块化的网络接口层设计
用 NSProxy 实现面向切面编程
oc中少见的不继承于NSObject 的类NSProxy
想要更多?自行百度~
解决NSTimer循环引用问题
使用NSProxy,你需要写一个子类继承它,然后需要实现init以及消息转发的相关方法:
//WeakProxy.h
@interface WeakProxy : NSProxy
@property ( weak , nonatomic , readonly) id target;
+ (instancetype)proxyWithTarget:(id)target;
- (instancetype)initWithTarget:(id)target;
@end
//WeakProxy.m
@implementation WeakProxy
- (instancetype)initWithTarget:(id)target{
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target{
return[[self alloc] initWithTarget:target];
}
//当一个消息转发的动作NSInvocation到来的时候,在这里选择把消息转发给对应的实际处理对象
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}
}
//当一个SEL到来的时候,在这里返回SEL对应的NSMethodSignature
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return[self.target methodSignatureForSelector:aSelector];
}
//是否响应一个SEL
- (BOOL)respondsToSelector:(SEL)aSelector{
return[self.target respondsToSelector:aSelector];
}
@end
创建NSTimer时,使用如下方法:
self.timer = [NSTimer timerWithTimeInterval:1
target:[WeakProxy proxyWithTarget:self]
selector:@selector(timerInvoked:)
userInfo:nil
repeats:YES];
原理如下:
把虚线处变成了弱引用。于是,Controller就可以被释放掉,我们在Controller的dealloc中调用invalidate,就断掉了Runloop对Timer的引用,于是整个三个淡蓝色的就都被释放掉了。
参考文章:NSProxy与消息转发机制