手動(dòng)目錄
- NSTimer 打破強(qiáng)持有的方法
方法一: 在 viewWillDisappear 中釋放NSTimer
方法二:在didMoveToParentViewController中釋放
方法三:消息轉(zhuǎn)發(fā)
方法四:中介者模式
___普通方式
___runtimer方式- 為什么weakSelf不能打破循環(huán)引用
iOS開(kāi)發(fā)中,遇到定時(shí)器的時(shí)候很多犹撒,大多數(shù)人會(huì)選擇用NSTimer颁湖。
NSTimer 有一個(gè)新的API不用考慮循環(huán)引用
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
但是它支持的版本是10.12之后蔗候。
我們用的多的還是這個(gè)API:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
這個(gè)API就會(huì) 有一個(gè)問(wèn)題:循環(huán)引用造成VC釋放不掉健爬。
為什么會(huì)釋放不掉外永? 我們?cè)贏PI中看說(shuō)明:
target
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.
target 是一個(gè)強(qiáng)持有润绎。
NSTimer 打破強(qiáng)持有的方法
方法一: 在 viewWillDisappear 中釋放NSTimer
@property (nonatomic, strong) NSTimer *timer;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];
- (void)fireHome {
}
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self.timer invalidate];
self.timer = nil;
}
這種方法很顯然包警,局限性很大书幕,大多數(shù)情況下,這種當(dāng)時(shí)都是不友好的揽趾,因?yàn)榇蠖鄶?shù)情況下台汇,都會(huì)進(jìn)行push或者present。
方法二:在didMoveToParentViewController中釋放
- (void)didMoveToParentViewController:(UIViewController *)parent{
// 無(wú)論push 進(jìn)來(lái) 還是 pop 出去 正常跑
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}
方法三:消息轉(zhuǎn)發(fā)
這種方式 需要借助虛基類(lèi)來(lái)實(shí)現(xiàn)篱瞎。
思路: 創(chuàng)建一個(gè)虛基類(lèi)苟呐,指定Timer的target為這個(gè)虛基類(lèi)。然后讓虛基類(lèi)把消息在發(fā)送給原來(lái)的target
// 虛基類(lèi) LGProxy
@interface LGProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
@interface LGProxy()
@property (nonatomic, weak) id object;
@end
@implementation LGProxy
+ (instancetype)proxyWithTransformObject:(id)object{
LGProxy *proxy = [LGProxy alloc];
proxy.object = object;
return proxy;
}
-(id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
// 用這種方式創(chuàng)建NSTimer --- 指定target 為 LGProxy.object
self.proxy = [LGProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];
- (void)fireHome {
NSLog(@"hello word - %d",num);
}
方法四:中介者模式
普通方式
用一個(gè)類(lèi)保存Timer的target俐筋、SEL等信息
// .h
@interface JEWeakTimer : NSObject
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats;
@end
// .m
@interface JEWeakTimerTarget : NSObject
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer* timer;
@end
@implementation JEWeakTimerTarget
- (void) fire:(NSTimer *)timer {
if(self.target) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];
#pragma clang diagnostic pop
} else {
[self.timer invalidate];
}
}
@end
@implementation JEWeakTimer
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
target:(id)aTarget
selector:(SEL)aSelector
userInfo:(id)userInfo
repeats:(BOOL)repeats {
JEWeakTimerTarget* timerTarget = [[JEWeakTimerTarget alloc] init];
timerTarget.target = aTarget;
timerTarget.selector = aSelector;
timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:timerTarget
selector:@selector(fire:)
userInfo:userInfo
repeats:repeats];
return timerTarget.timer;
}
@end
runtime 方式
思路: 通過(guò)runtime 動(dòng)態(tài)添加 動(dòng)態(tài)向中介者中添加一個(gè)方法實(shí)現(xiàn)(SEL(timer的SEL) - > IMP(自定義的IMP )牵素,在自定義的IMP中,通過(guò)runtime 向原來(lái)的類(lèi)中發(fā)送消息
// .h
@interface LGTimerWapper : NSObject
- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
@end
// .m
#import <objc/message.h>
@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; // vc
self.aSelector = aSelector; // 方法 -- vc 釋放
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(JETimerWapper *warpper){
if (warpper.target) {
void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer);
}
}
@end
為什么weakSelf不能打破循環(huán)引用
上面說(shuō)的各種解決NSTimer不釋放的問(wèn)題澄者,都沒(méi)有提到weakSelf笆呆。為什么block可以解決循環(huán)引用请琳,而NSTimer不可以?
其實(shí)把Block本質(zhì)了解之后赠幕,再來(lái)看這個(gè)問(wèn)題就很好處理了俄精。
在iOS-- block中提到一個(gè)地方,block_assign (第二層拷貝) 中是對(duì)對(duì)象的指針進(jìn)行持有榕堰。
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
_Block_retain_object(object);
*dest = object; // ?? 持有的是指針地址竖慧,而不是對(duì)象本身
break;
self和weakSelf雖然都是指向同一個(gè)對(duì)象,但他們是兩個(gè)不同的地址逆屡,weakSelf不強(qiáng)持有對(duì)象圾旨,也就是不操作引用計(jì)數(shù)。
block在copy的時(shí)候魏蔗,會(huì)強(qiáng)持有臨時(shí)變量的指針地址砍的,而不是指針指向的對(duì)象,所以weakSelf可以解決block循環(huán)引用問(wèn)題莺治,而NSTimer強(qiáng)持有的是對(duì)象廓鞠。