首先有兩個概念寫在最前:api
內存泄漏:系統分配的內存空間在使用完畢以後沒有進行及時的回收,稱之爲發生了內存泄漏。bash
內存溢出:指在申請內存的時候,沒有足夠的內存空間可使用,包括棧溢出和堆溢出。函數
下面開始啦: 首先,建立出一個循環引用, 建立一個TestViewController,建立一個timer,ui
_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就會被執行,但循環引用形成的當前類沒有被銷燬。atom
接下來,咱們就去解決這個NSTimer的循環引用問題:spa
1.第一種方法:在合適的時機銷燬NSTimercode
-(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 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的地方還請指出,很是感謝。