[iOS]定時器NSTimer、CADisplayLink的記憶體管理
阿新 • • 發佈:2021-06-11
NSTimer、CADisplayLink會對target產生強引用,如果target同時對他們產生強引用,則會發生迴圈引用。
以NSTimer為例,解決迴圈引用的問題。
方法1:使用block
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. __weak typeof(self) weakself = self; self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0repeats:YES block:^(NSTimer * _Nonnull timer) { [weakself func]; }]; } - (void)func { NSLog(@"%s",__func__); } - (void)dealloc { NSLog(@"%s",__func__); [self.timer invalidate]; }
方法2:使用NSObject作為中間物件
Proxy1.h @interface Proxy1 : NSObject + (instancetype)initWithTarget:(id)target; @end
Proxy1.m @interface Proxy1 () @property (nonatomic,weak) id target; @end @implementation Proxy1 + (instancetype)initWithTarget:(id)target { Proxy1 *proxy = [[Proxy1 alloc] init]; proxy.target = target; return proxy; } - (id)forwardingTargetForSelector:(SEL)aSelector {return self.target; } @end
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[Proxy1 initWithTarget:self] selector:@selector(func) userInfo:nil repeats:YES]; } - (void)func { NSLog(@"%s",__func__); } - (void)dealloc { NSLog(@"%s",__func__); [self.timer invalidate]; }
方法3:使用NSProxy作為中間物件
Proxy2.h @interface Proxy2 : NSProxy + (instancetype)initWithTarget:(id)target; @end
Proxy2.m @interface Proxy2 () @property (nonatomic,weak) id target; @end @implementation Proxy2 + (instancetype)initWithTarget:(id)target { Proxy2 *proxy = [Proxy2 alloc]; proxy.target = target; return proxy; } - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { return [self.target methodSignatureForSelector:sel]; } - (void)forwardInvocation:(NSInvocation *)invocation { [invocation invokeWithTarget:self.target]; } @end
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[Proxy2 initWithTarget:self] selector:@selector(func) userInfo:nil repeats:YES]; } - (void)func { NSLog(@"%s",__func__); } - (void)dealloc { NSLog(@"%s",__func__); [self.timer invalidate]; }
方法3的優點:
執行效率高,無需執行父類的方法搜尋過程,直接進行訊息轉發。
關於NSProxy補充:
通過呼叫isKindOfClass
Proxy1 *proxy1 = [Proxy1 initWithTarget:self]; Proxy2 *proxy2 = [Proxy2 initWithTarget:self]; NSLog(@"%d",[proxy1 isKindOfClass:[ViewController class]]); // 0 NSLog(@"%d",[proxy2 isKindOfClass:[ViewController class]]); // 1
proxy1為Proxy1型別,Proxy1繼承自NSObject,可以正常處理isKindOfClass方法,所以判斷結果為0.
proxy2為Proxy2型別,Proxy2繼承自NSProxy,大部分方法會直接進入訊息轉發階段,會改為使用target進行呼叫,所以判斷結果為1.
通過觀察NSProxy的原始碼發現,該方法直接進行了訊息轉發。
/** * Calls the -forwardInvocation: method to determine if the 'real' object * referred to by the proxy is an instance of the specified class. * Returns the result.<br /> * NB. The default operation of -forwardInvocation: is to raise an exception. */ - (BOOL) isKindOfClass: (Class)aClass { NSMethodSignature *sig; NSInvocation *inv; BOOL ret; sig = [self methodSignatureForSelector: _cmd]; inv = [NSInvocation invocationWithMethodSignature: sig]; [inv setSelector: _cmd]; [inv setArgument: &aClass atIndex: 2]; [self forwardInvocation: inv]; [inv getReturnValue: &ret]; return ret; }