首先有两个概念写在最前:

内存泄漏:系统分配的内存空间在使用完毕之后没有进行及时的回收,称之为发生了内存泄漏。

内存溢出:指在申请内存的时候,没有足够的内存空间可以使用,包括栈溢出和堆溢出。

下面开始啦: 首先,创建出一个循环引用, 创建一个TestViewController,创建一个timer,

_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(fire) userInfo:nil repeats:YES];-(void)fire{NSLog(@"fire");
}-(void)dealloc
{NSLog(@"%@ delloc",self);
}
复制代码

运行上述代码,然后TestViewController pop回去之后,你会发现fire一直在打印,此时就会造成一个循环引用的问题,本质的原因其实是NSTimer对当前的target(self)是一个强引用,在强引用的过程中他们相互持有,所以他们之间就没有办法正常释放。可能有同学会说在析构函数dealloc中将 [_timer invalidate]; _timer = nil; 其实运行下来,你会发现dealloc函数没有执行。我们都知道,dealloc是每个控制器中都有的一个系统方法,由系统响应执行,当前控制器销毁时,dealloc就会被执行,但循环引用造成的当前类没有被销毁。

接下来,我们就去解决这个NSTimer的循环引用问题:

1.第一种方法:在合适的时机销毁NSTimer

-(void)didMoveToParentViewController:(UIViewController *)parent
{
//parent == nil 当父视图为空(iOS8.0之后提供的api,用来管理子视图的生命周期)if (!parent) {[_timer invalidate];_timer = nil;}
}
复制代码

2.第二种方法:引入中间者

其实就是把当前的强引用转到target,如果当前的viewcontroller能够正常回收,那么dealloc方法就能够正常执行。

@property (nonatomic,strong) id target;
//创建一个target对象,
_target = [NSObject new];
对于当前这个_target来说,本质上是要作为消息的处理者,显然_target需要一个selector,所以我们动态添加一个方法到当前对象上来,
class_addMethod([_target class], @selector(fire), class_getMethodImplementation([self class], @selector(fire)), "v@:");
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:_target selector:@selector(fire) userInfo:nil repeats:YES];-(void)dealloc
{NSLog(@"%@ delloc",self);[_timer invalidate];_timer = nil;
}
此时viewcontroller的析构函数就可以正常执行。
复制代码

3.第三种方法:高级的中间者

此时我们需要借助一个虚基类NSProxy,(NSProxy其主要用来消息转发的处理)

//  HZProxy.h
#import <Foundation/Foundation.h>
@interface HZProxy : NSProxy
//还是要有个target
@property (nonatomic,weak) id target;
@end//  HZProxy.m
#import "HZProxy.h"@implementation HZProxy//获取当前的方法签名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{return [self.target methodSignatureForSelector:sel];
}
//指定当前消息的处理者
-(void)forwardInvocation:(NSInvocation *)invocation
{[invocation invokeWithTarget:self.target];
}//执行timer
//虚基类只有alloc方法,所以初始化,直接调用alloc
_hzProxy = [HZProxy alloc];
//当前Proxy的target设为当前的self,因为真正要处理消息的其实是当前的viewcontroller(其实这个target就相当于delegate)
_hzProxy.target  = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:_hzProxy = [HZProxy alloc];_hzProxy.target  = self;_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:_hzProxy selector:@selector(fire) userInfo:nil repeats:YES]; selector:@selector(fire) userInfo:nil repeats:YES];
当前_timer的对象的处理就变成了_hzProxy
运行一下,可以看到viewcontroller的析构函数可以正常执行
复制代码

4.第四种:带block的timer

在我们创建timer的时候,苹果也意识到NSTimer的api是存在一定问题的,所以在iOS10.0之后提供了一种block的方法来去解决NSTimer的循环引用的问题。

__weak typeof(self) weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0f repeats:YES block:^(NSTimer * _Nonnull timer) {__strong typeof(self) strongSelf = weakSelf;[strongSelf fire];}];
复制代码

但是为了兼容当前的api在iOS10.0之前的情况,所以这个时候我们可以HOOK一下

首先我们创建一个NSTimer的分类,
//  NSTimer+ZHTimer.h
#import <Foundation/Foundation.h>@interface NSTimer (ZHTimer)
+(NSTimer *)zh_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(void))block;
@end//  NSTimer+ZHTimer.m
#import "NSTimer+ZHTimer.h"@implementation NSTimer (ZHTimer)
+(NSTimer *)zh_scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(void))block
{return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(zh_blockHandle:) userInfo:block repeats:YES]; //这里面这个self,其实指的是当前的类对象,在内存中只存在一份,就是以单例的形式存在,所以我们在每一次创建实例变量都要通过这个类对象来创建,
//所以并不需要担心当前的target造成循环引用,因为单例不需要被释放,只有当APP被Q的时候,存在内存中的单例才会被释放掉。
}+(void)zh_blockHandle:(NSTimer *)timer{void(^block)(void) = timer.userInfo;if (block) {block();}
}@end//调用一下
__weak typeof(self) weakSelf = self;
_timer = [NSTimer zh_scheduledTimerWithTimeInterval:1.0f repeats:YES block:^{__strong typeof(self) strongSelf = weakSelf;[strongSelf fire];}];复制代码

以上就是个人对NSTimer循环引用的理解以及处理方法,可能不是很完善,很深入,有Bug的地方还请指出,非常感谢。

转载于:https://juejin.im/post/5d0ae3456fb9a07ea803cf9c

iOS: NSTimer的循环引用(解决)相关推荐

  1. [iOS]-NSTimer与循环引用的理解

    目录: 参考的博客: 问题引入 循环引用 简单的循环引用 Block中的循环引用强弱共舞 Delegate中的循环引用 NSTimer 创建NSTimer 销毁NSTimer invalidate f ...

  2. [iOS]ARC下循环引用的问题

    转载自:http://blog.cnbang.net/tech/2085/ [iOS]ARC下循环引用的问题 2013-8-30 最初 最近在开发应用时碰到使用ASIHttpRequest后在某些机器 ...

  3. [zz] C++智能指针循环引用解决

    转载自:http://blog.csdn.net/segen_jaa/article/details/8080167 参考文章:http://www.cnblogs.com/TianFang/arch ...

  4. SpringBoot:循环引用解决方式

    SpringBoot:循环引用解决方式 1 前言 SpringBoot启动时提示循环引用: Relying upon circular references is discouraged and th ...

  5. iOS容易造成循环引用的三种场景,就在你我身边!

    ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是--循环引用.循环引用可以简单理解为A引用了B,而B又引用了A,双方都同 ...

  6. python循环引用解决 cannot import partially initialize

    文章目录 1. 问题 2. 程序结构 3. 健康的引用关系 1. 问题 在python项目中,通常我们要将功能划分为多个文件, 可能出现一些,各模块需要一些公共的值.实例,同时相互存在循环引用的问题. ...

  7. nstimer循环引用_ios开发中经典循环引用场景?

    1.属性传值循环引用 如:- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexP ...

  8. iOS循环引用问题集合、内存泄漏、僵尸对象、代码静态分析

    内存泄漏:https://my.oschina.net/llfk/blog/1031291 内存泄漏监测自动化:http://www.cocoachina.com/articles/18490 fac ...

  9. Block的循环引用

    2019独角兽企业重金招聘Python工程师标准>>> 在ios常见的循环引用中曾经提到过block: 看看上面最基本的block循环应用,self包含block,block包含了s ...

最新文章

  1. 数据结构和算法分析:第三章 表、队列和栈
  2. Boost:bind绑定__cdecl(成员函数)测试程序
  3. python 在excel指定列添加数据_python读取excel指定列数据并写入到新的excel方法
  4. ApacheCN 安卓译文集(二)20211226 更新
  5. IDEA配置JUnit进行单元测试
  6. python面试题汇总_【Python环境】Python面试题汇总(一)
  7. elasticsearch同义词配置elasticsearch-analysis-dynamic-synonym
  8. 一、struts入门
  9. 大数据之实时数据分析之Apache Doris数据库
  10. 有限元计算计算机配置, 有限元分析计算对电脑配置都有什么要求
  11. TS:虚机ipv6网络不通问题-2022.5.16(已解决-博客分享)
  12. 您的服务器组件没有得到合法授权,服务器将会受限模式运行
  13. 搜索习题-传教士与野人问题
  14. 永洪科技怎么样_【永洪科技工资|永洪科技待遇怎么样】-看准网
  15. 网页浏览flash时不停黑屏
  16. Hexo+腾讯云+Icarus主题 搭建自定义个人博客
  17. 沈阳python需求大吗_我为什么放弃了敲代码,做产品?
  18. Python模拟登陆 —— 征服验证码 4 果壳
  19. 【转】在win10接双显示器(扩展模式)怎么把鼠标游标切换到第二台显示器上?
  20. Delphi写游戏外挂

热门文章

  1. mysql源码安装都能装什么模块_源码安装后,添加其他模块
  2. 目标检测开源代码汇总 object detection algorithm codes
  3. Matplotlib使用日期作为横坐标
  4. centos7系统下scala安装详解
  5. 计算机科学与技术历史步伐,计算机科学与技术1001班先进班级体申报材料.pdf
  6. html浏览位置坐标,HTML5教程 | HTML5地理定位(GeoLocation API)
  7. Linux课程设计八音盒,单片机课程设计——八音盒精要.doc
  8. k8s 通过环境变量获取Pod信息
  9. Elasticsearch入常用RESTful API总结
  10. Android Action Bar 详解篇