美文网首页iOS开发
【OC梳理】循环引用及解决

【OC梳理】循环引用及解决

作者: 忠橙_g | 来源:发表于2018-02-08 10:28 被阅读28次

什么是循环引用

循环引用是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与消息转发机制

相关文章

网友评论

    本文标题:【OC梳理】循环引用及解决

    本文链接:https://www.haomeiwen.com/subject/rpeazxtx.html