iOS循环引用常见场景和解决办法

好多场景会导致循环引用,例如使用Block、线程、委托、通知、观察者都可能会导致循环引用。

1、委托

遵守一个规则,委托方持有代理方的强引用,代理方持有委托方的弱引用。

实际场景中,委托方会是一个控制器对象,代理方可能是一个封装着网络请求并获取数据的对象。

例如:ViewController中需从网络中获取数据,让后展示到列表当中,从网络获取的类是 DataUpdateOp

当然,大多数情况下,很多人愿意用block 回传网络请求数据,像对AFNetworking做一个简单的二次封装。

这里只是将一下如果用代理的话,应该如何避免循环引用。而且做了验证控制器对象在没有被回收的时候才做响应的操作。

实际场景中,因为网络请求的封装不尽相同,可能会更复杂。

2、Block

block捕获外部变量(一般是控制器本身或者控制器的属性)会导致循环引用

这时候引起了循环引用,present vc之后,vc被展示出来,子视图一致存在,在completion块中,有引用了self,也就是父控制器。这时父控制器子控制器都在内存当中,如果子控制器里面做了耗时操作,耗内存的操作,可能会导致内存不足。

解决方法: 使用 'weak strong dance' 技术

3、线程与计时器

不正确是使用 NSThread 和 NSTimer对象也可能导致循环引用

运行异步操作的典型步骤:

1、如果没有编写更高级的代码来管理自定义的队列,则在全局队列上使用 dispatch_async方法。

2、在需要的时间和地点用NSThread开启异步执行。

3、使用NSTimer周期性的执行一短代码

错误示例:

以上代码:对象持有了计时器,同时计时器也持有了对象,运行循环也持有了计时器,直到计时器的invalidate方法被调用。

这就造成对计时器对象的附加引用,即使代码中没有显示的引用关系。这仍然会导致循环引用。

实际上:NSTimer对象导致了被运行时持有的间接引用,这些引用是强引用,而且目标的引用计数器会以2(而不是1)增长。必须对计时器调用 inivalidatae方法,移除引用。

如果以上代码中,控制器被创建多次,那么控制器是不会被销毁的。会造成严重的内存泄漏。

如果使用了NSThread,也同样会发生这样的问题。

解决办法:

1、主动调用invalidated,

2、将代码分离到多个类中。

首先,不要指望delloc方法会被调用,因为一旦和控制器发生循环引用,那么delloc方法永远不会被调用。delloc()中的 [self.timer invalidated];永远不会被执行。

因为运行循环会跟踪活跃的计时器对象和线程对象,所以在代码找那个设置为nil并不能销毁对象。想要解决这个问题,可以创建一个自定义的方法,以更加明确的方式执行清理操作。

在一个视图控制器中,调用这个清理方法的最佳时机是用户离开视图控制器的时候,这个时机既可以是点击返回按钮,也可可以是其他类似的行为(类直到此事发生的地方),我们可以定义一个cleanUp()方法.

上面的这种写法不能清除timer

3.1清理Timer的方案两种方法:

 

3.2 方案二 将持有关系分散到多个类中---任务类执行具体动作,所有者类调用任务

优点1、清理器有良好的职责持有者
优点2、需要时任务可以被多个持有者重复使用
具体:控制器只负责展示数据, 新建一个类NewFeedUpdateTask,周期性的执行任务,检查填充视图控制器的最新的数据

从使用方面来看,viewController 持有了 NewFeedUpdateTask对象, 控制器没有被除了父控制器之外的对象所持有。
因此,当用户离开页面时,也就是点击了返回按钮时,引用计数器会被降为0,视图控制器会被销毁。这反过来会导致跟新任务停止。
进而导致计时器会被设定为无效,从而触发关联对象包括(timer 和 updateTask )的析构。

注意

当使用 NSTimer 和 NSThread 时,总应该通过间接的层实现明确的销毁过程。这个间接层应该使用弱引用,从而保证所有的对象能够在停止使用后执行销毁动作,