問題描述:
用NSTimer來實現每隔一定時間執行制定的任務,例如最常見的廣告輪播圖。如果我們在 timerWithTimeInterval:1 target:self 中指定target為當前控制器,控制器則會被timer強引用,而控制器對timer也是強引用的。一般,我們終止定時器往往在界面銷毀時,即dealloc方法中寫 [_timer invalidate];。基於上面的分析,由於循環引用的存在,控制器永遠也不會走dealloc方法,定時器會一直執行方法,造成內存泄露。
解決方案:
利用消息轉發來斷開NSTimer對象與視圖之間的引用關系。初始化NSTimer時把觸發事件的target替換成一個單獨的對象,然后這個對象中NSTimer的SEL方法觸發時讓這個方法在當前的視圖self中實現。
背景知識:
NSProxy:NSProxy 是一個抽象類,它接收到任何自己沒有定義的方法他都會產生一個異常,所以一個實際的子類必須提供一個初始化方法或者創建方法,並且重載forwardInvocation:方法和methodSignatureForSelector:方法來處理自己沒有實現的消息。
從類名來看是代理類,專門負責代理對象轉發消息的。相比NSObject類來說NSProxy更輕量級,通過NSProxy可以幫助Objective-C間接的實現多重繼承的功能。
代碼:
#import <Foundation/Foundation.h> @interface XZHProxy : NSProxy /** * 代理的對象 */ @property (nonatomic,weak)id obj; @end
#import "XZHProxy.h" @implementation XZHProxy /** 這個函數讓重載方有機會拋出一個函數的簽名,再由后面的forwardInvocation:去執行 為給定消息提供參數類型信息 */ - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSMethodSignature *sig = nil; sig = [self.obj methodSignatureForSelector:aSelector]; return sig; } /** * NSInvocation封裝了NSMethodSignature,通過invokeWithTarget方法將消息轉發給其他對象.這里轉發給控制器執行。 */ - (void)forwardInvocation:(NSInvocation *)anInvocation{ [anInvocation invokeWithTarget:self.obj]; } @end
#import "ViewController.h" #import "XZHProxy.h" @interface ViewController () /** *代理 */ @property (nonatomic,strong)XZHProxy *proxy; /** * 定時器 */ @property (nonatomic,strong)NSTimer *timer; /** * 計數 */ @property (nonatomic,assign)NSInteger count; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.proxy = [XZHProxy alloc]; //作為當前控制器的代理 self.proxy.obj = self; //target為代理 self.timer = [NSTimer timerWithTimeInterval:1 target:self.proxy selector:@selector(timerEvent) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; } - (void)timerEvent{ NSLog(@"%zd",self.count++); } - (void)dealloc{ NSLog(@"----dealloc"); //釋放計時器 [self.timer invalidate]; }
