iOS: NSTimer的循环引用(解决)

栏目: IOS · 发布时间: 5年前

内容简介:首先有两个概念写在最前:内存泄漏:系统分配的内存空间在使用完毕之后没有进行及时的回收,称之为发生了内存泄漏。内存溢出:指在申请内存的时候,没有足够的内存空间可以使用,包括栈溢出和堆溢出。

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

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

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

下面开始啦: 首先,创建出一个循环引用, 创建一个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的地方还请指出,非常感谢。


以上所述就是小编给大家介绍的《iOS: NSTimer的循环引用(解决)》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Web Form Design

Web Form Design

Luke Wroblewski / Rosenfeld Media / 2008-5-2 / GBP 25.00

Forms make or break the most crucial online interactions: checkout, registration, and any task requiring information entry. In Web Form Design, Luke Wroblewski draws on original research, his consider......一起来看看 《Web Form Design》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

html转js在线工具
html转js在线工具

html转js在线工具