一、準(zhǔn)備
timer的創(chuàng)建
第一種:
- 如果在主線(xiàn)程里創(chuàng)建,需要修改下Mode為NSRunLoopCommonModes,不然,當(dāng)滾動(dòng)事件發(fā)生時(shí),會(huì)導(dǎo)致NSTimer不執(zhí)行,主線(xiàn)程的RunLoop是默認(rèn)開(kāi)啟的,所以不需要[[NSRunLoop currentRunLoop] run]。
- 如果在子線(xiàn)程里創(chuàng)建,且當(dāng)前線(xiàn)程里無(wú)滾動(dòng)事件,則不需要修改Mode,子線(xiàn)程的RunLoop默認(rèn)不開(kāi)啟的,需要手動(dòng)加入
Runloop:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer timerWithTimeInterval:1
target:weakSelf
selector:@selector(fireHome)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer
forMode:NSDefaultRunLoopMode];
- (void)fireHome {
num++;
NSLog(@"hello word - %d",num);
}
第二種:
另一種創(chuàng)建timer方法,自動(dòng)加入Runloop無(wú)需手動(dòng)添加,等同于上述方法:
__weak typeof(self) weakSelf = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:1
target:weakSelf
selector:@selector(fireHome)
userInfo:nil
repeats:YES];

二、timer循環(huán)引用分析:
1. timer循環(huán)引用分析

self -> timer -> self循環(huán)引用分析:
-
self強(qiáng)持有timer我們都能直接看出來(lái),那么timer是什么時(shí)候強(qiáng)持有self的呢?看蘋(píng)果官方文檔可知:target方法中,timer對(duì)self對(duì)象進(jìn)行了強(qiáng)持有,因此造成了循環(huán)引用。 - 但是當(dāng)我們按照慣例用
weakSelf去打破強(qiáng)引用的時(shí)候,發(fā)現(xiàn)weakSelf沒(méi)有打破循環(huán)引用,timer仍然在運(yùn)行。 - 即
self -> timer -> weakSelf -> self。
2. __weak typeof(self) weakSelf = self分析
從上面我們會(huì)疑惑為什么block中 self -> block -> wealSelf -> self可以打破循環(huán)引用,而 self -> timer -> weakSelf -> self無(wú)法打破呢?
帶著這個(gè)疑問(wèn),我們要了解__weak typeof(self) weakSelf = self;做了什么。

從上圖可知,
weakSelf和self兩個(gè)指針地址不同但內(nèi)存空間地址相同,也就是兩個(gè)對(duì)象同時(shí)持有同一個(gè)內(nèi)存空間。
并且正常情況下經(jīng)過(guò)__weak typeof(self) weakSelf = self操作我們需要進(jìn)行引用計(jì)數(shù)處理,但是實(shí)際情況是經(jīng)過(guò)弱引用表并沒(méi)有處理引用計(jì)數(shù)。
3. 分析block使用weakSelf為什么可以打破循環(huán)引用呢?
a). 通過(guò)命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m生成 .cpp代碼:
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; // 實(shí)際上是指針賦值
b). 在.cpp文件中我們可以看到如上代碼段,雖然weakself對(duì)象傳入進(jìn)來(lái),但是內(nèi)部實(shí)際操作的是對(duì)象的指針,也就是weakself的指針,我們知道weakself和self雖然內(nèi)存地址相同,但指針是不一樣的,也就是block中并沒(méi)有直接持有self,而是通過(guò)weakSelf指針操作,所以就打破了self -> block -> weakSelf -> self中self這一層的循環(huán)引用,變成了self -> block -> weakSelf (臨時(shí)變量的指針地址)來(lái)打破循環(huán)引用。
4. 總結(jié)
self -> block -> weakSelf -> self:block使用weakSelf之所以能夠打破循環(huán)引用是因?yàn)閎lock內(nèi)部操作的是weakSelf的指針地址,它和self是兩個(gè)不同的指針地址,即 沒(méi)有直接持有self,所以可以weakSelf可以打破self的循環(huán)引用關(guān)系self -> block -> weakSelf。self -> timer -> weakSelf -> self:那timer之所以無(wú)法打破循環(huán)關(guān)系是因?yàn)閠imer創(chuàng)建時(shí)target是對(duì)weakSelf的對(duì)象強(qiáng)持有操作,而weakSelf和self雖然是不同的指針但是指向的對(duì)象是相同的,也就相當(dāng)于間接的強(qiáng)持有了self,所以weakSelf并沒(méi)有打破循環(huán)引用關(guān)系。
二、解決timer循環(huán)引用的四種方法
1. 使用invalidate結(jié)束timer運(yùn)行
我們第一時(shí)間肯定想到的是[self.timer invalidate]不就可以了嗎,當(dāng)然這是正確的思路,那么我們調(diào)用時(shí)機(jī)是什么呢?viewWillDisAppear還是viewDidDisAppear?實(shí)際上在我們實(shí)際操作中,如果當(dāng)前頁(yè)面有push操作的話(huà),當(dāng)前頁(yè)面還在棧里面,這時(shí)候我們釋放timer肯定是錯(cuò)誤的,所以這時(shí)候我們可以用到下面的方法:
- (void)didMoveToParentViewController:(UIViewController *)parent {
// 無(wú)論push 進(jìn)來(lái) 還是 pop 出去 正常運(yùn)行
// 就算繼續(xù)push 到下一層 pop 回去還是繼續(xù)
if (parent == nil) {
[self.timer invalidate];
self.timer = nil;
NSLog(@"timer 走了");
}
}

2. 中介者模式
換個(gè)思路,timer會(huì)造成循環(huán)引用是因?yàn)閠arget強(qiáng)持有了self,造成的循環(huán)引用,那我們是否可以包裝一下target,使得timer綁定另外一個(gè)不是self的target對(duì)象來(lái)打破這層強(qiáng)持有關(guān)系。
@property (nonatomic, strong) id target;
self.target = [[NSObject alloc] init]; // 自己創(chuàng)建的target
class_addMethod([NSObject class],
@selector(fireHome),
(IMP)fireHomeObjc,
"v@:");
self.timer = [NSTimer
scheduledTimerWithTimeInterval:1
target:self.target
selector:@selector(fireHome)
userInfo:nil
repeats:YES];

根據(jù)打印結(jié)果我們發(fā)現(xiàn)在dealloc的時(shí)候也可以實(shí)現(xiàn)timer的釋放,打破了循環(huán)引用。
class_addMethod的作用:看著是給target增加了一個(gè)方法,但是實(shí)際上timer的執(zhí)行是在fireHomeObjc里面執(zhí)行的,而不是應(yīng)該執(zhí)行的fireHome函數(shù)。
分析一下:在沒(méi)有使用自定義的target之前,fireHome函數(shù)的IMP是指向fireHome的這是毋庸置疑的,而使用class_addMethod之后,相當(dāng)于重新指定了fireHome的IMP指針,讓他指向了fireHomeObjc。
- 代碼優(yōu)化:既然class_addMethod中需要一個(gè)函數(shù)的IMP,那么我們直接獲取fireHome的IMP就可以了。
self.target = [[NSObject alloc] init];
Method method = class_getInstanceMethod([self class], @selector(fireHome));
class_addMethod([self.target class], @selector(fireHome), method_getImplementation(method), "v@:");
self.timer = [NSTimer
scheduledTimerWithTimeInterval:1
target:self.target
selector:@selector(fireHome)
userInfo:nil
repeats:YES];
3. NSProxy虛基類(lèi)的方式
NSProxy是一個(gè)虛基類(lèi),它的地位等同于NSObject。
command+shift+0打開(kāi)Xcode參考文檔搜索NSProxy,說(shuō)明如下:
NSProxy
An abstract superclass defining an API for objects that act as stand-ins for other objects or for objects that don’t exist yet.Declaration
@interface NSProxy
Overview
Typically, a message to a proxy is forwarded to the real object or causes the proxy to load (or transform itself into) the real object. Subclasses of
NSProxycan be used to implement transparent distributed messaging (for example,NSDistant<wbr>Object) or for lazy instantiation of objects that are expensive to create.
NSProxyimplements the basic methods required of a root class, including those defined in theNSObjectprotocol. However, as an abstract class it doesn’t provide an initialization method, and it raises an exception upon receiving any message it doesn’t respond to. A concrete subclass must therefore provide an initialization or creation method and override theforward<wbr>Invocation:andmethod<wbr>Signature<wbr>For<wbr>Selector:methods to handle messages that it doesn’t implement itself. A subclass’s implementation offorward<wbr>Invocation:should do whatever is needed to process the invocation, such as forwarding the invocation over the network or loading the real object and passing it the invocation.method<wbr>Signature<wbr>For<wbr>Selector:is required to provide argument type information for a given message; a subclass’s implementation should be able to determine the argument types for the messages it needs to forward and should construct anNSMethod<wbr>Signatureobject accordingly. See theNSDistant<wbr>Object,NSInvocation, andNSMethod<wbr>Signatureclass specifications for more information.
我們不用self來(lái)響應(yīng)timer方法的target,而是用NSProxy來(lái)響應(yīng)。
- DZProxy.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface DZProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end
NS_ASSUME_NONNULL_END
- DZProxy.m
#import "DZProxy.h"
@interface DZProxy()
@property (nonatomic, weak) id object;
@end
@implementation DZProxy
+ (instancetype)proxyWithTransformObject:(id)object {
DZProxy *proxy = [DZProxy alloc];
proxy.object = object; // 我們拿到外邊的self,weak弱引用持有
return proxy;
}
// 僅僅添加了weak類(lèi)型的屬性還不夠,為了保證中間件能夠響應(yīng)外部self的事件,需要通過(guò)消息轉(zhuǎn)發(fā)機(jī)制,讓實(shí)際的響應(yīng)target還是外部self,這一步至關(guān)重要,主要涉及到runtime的消息機(jī)制。
// proxy虛基類(lèi)并沒(méi)有持有vc,而是消息的轉(zhuǎn)發(fā),又給了vc
- (id)forwardingTargetForSelector:(SEL)aSelector {
return self.object;
}
- VC
- (void)viewDidLoad {
[super viewDidLoad];
self.proxy = [DZProxy proxyWithTransformObject:self];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];
}
- (void)dealloc{
[self.timer invalidate];
self.timer = nil;
NSLog(@"%s",__func__);
}
虛基類(lèi)方法是用proxy打破self 這一塊的循環(huán)。