iOS小知识之NSTimer的循环引用二

语言: CN / TW / HK

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

切断target强持有

除了常规的方法解决循环引用的问题,还可以通过切段target的强持有,解决循环引用的问题。

1.1 中介者模式

- (void)viewDidLoad { [super viewDidLoad]; NSObject *objc = [[NSObject alloc] init]; class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "[email protected]:"); self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:objc selector:@selector(fireHome) userInfo:nil repeats:YES]; } void fireHomeObjc(id obj){ num++; NSLog(@"hello word - %d -%@",num, obj); } - (void)dealloc{ [self.timer invalidate]; self.timer = nil; } - 创建的NSObject实例对象objc,通过Runtime对NSObject增加fireHome方法,IMP指向fireHomeObjc的函数地址 - 创建NSTimer,将objc传入target参数,这样避免NSTimer对self的强持有 - 当页面退出时,由于self没有被NSTimer持有,正常调用dealloc方法 - 在dealloc中,对NSTimer进行释放。此时NSTimer对objc的强持有接触,objc也跟着释放

1.2 封装自定义的Timer

创建LGTimerWapper,实现自定义Timer的封装 打开LGTimerWapper.h文件,写入以下代码:

```

import

@interface LGTimerWapper : NSObject

  • (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

  • (void)lg_invalidate;

@end ``` 打开LGTimerWapper.m文件,写入以下代码:

```

import "LGTimerWapper.h"

import

@interface LGTimerWapper()

@property (nonatomic, weak) id target; @property (nonatomic, assign) SEL aSelector; @property (nonatomic, strong) NSTimer *timer;

@end

@implementation LGTimerWapper

  • (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{ if (self == [super init]) { self.target = aTarget; self.aSelector = aSelector; if ([self.target respondsToSelector:self.aSelector]) { Method method = class_getInstanceMethod([self.target class], aSelector); const char *type = method_getTypeEncoding(method); class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
        self.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo]; 
    }
    

    } return self; } void fireHomeWapper(LGTimerWapper warpper){ if (warpper.target) { void (lg_msgSend)(void ,SEL, id) = (void )objc_msgSend; lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer); } else { [warpper lg_invalidate]; } } - (void)lg_invalidate{ [self.timer invalidate]; self.timer = nil; }

@end ``` LGTimerWapper的调用代码:

- (void)viewDidLoad { [super viewDidLoad]; self.timerWapper = [[LGTimerWapper alloc] lg_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES]; } - (void)fireHome{ num++; } - 在LGTimerWapper中,定义初始化lg_initWithtimInterval方法和lg_invalidate释放方法 - 初始化方法 - 控件内部的target使用weak修饰,对ViewController进行弱持有 - 监测target中是否存在该selector。如果存在,对当前类使用Runtime添加同名方法编号,指向自身内部fireHomeWapper的函数地址 - 创建真正的NSTimer定时器,将控件自身的实例对象传入target,避免NSTimer对ViewController强持有。 - 当NSTimer回调时,会进入fireHomeWapper函数 - 函数内部不负责业务处理,如果target存在,使用objc_msgSend,将消息发送给target自身下的selector方法。 - 当页面退出时,ViewController可以正常释放。但LGTimerWapper和NSTimer相互持有,双方都无法释放。 - 由于双方都无法释放,NSTimer的回调会继续调用 - 当进入fireHomeWapper函数,发现target已经不存在了,调用LGTimerWapper的lg_invalidate方法,内部对NSTimer进行释放 - 当NSTimer释放后,对LGTimerWapper的强持有解除,LGTimerWapper也跟着释放。