iOS小知識之NSTimer的迴圈引用二

語言: CN / TW / HK

小知識,大挑戰!本文正在參與“程式設計師必備小知識”創作活動。

切斷target強持有

除了常規的方法解決迴圈引用的問題,還可以通過切段target的強持有,解決迴圈引用的問題。

1.1 中介者模式

- (void)viewDidLoad { [super viewDidLoad]; NSObject *objc = [[NSObject alloc] init]; class_addMethod([NSObject class], @selector(fireHome), (IMP)fireHomeObjc, "[email protected]:"); self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:objc selector:@selector(fireHome) userInfo:nil repeats:YES]; } void fireHomeObjc(id obj){ num++; NSLog(@"hello word - %d -%@",num, obj); } - (void)dealloc{ [self.timer invalidate]; self.timer = nil; } - 建立的NSObject例項物件objc,通過Runtime對NSObject增加fireHome方法,IMP指向fireHomeObjc的函式地址 - 建立NSTimer,將objc傳入target引數,這樣避免NSTimer對self的強持有 - 當頁面退出時,由於self沒有被NSTimer持有,正常呼叫dealloc方法 - 在dealloc中,對NSTimer進行釋放。此時NSTimer對objc的強持有接觸,objc也跟著釋放

1.2 封裝自定義的Timer

建立LGTimerWapper,實現自定義Timer的封裝 開啟LGTimerWapper.h檔案,寫入以下程式碼:

```

import

@interface LGTimerWapper : NSObject

  • (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

  • (void)lg_invalidate;

@end ``` 開啟LGTimerWapper.m檔案,寫入以下程式碼:

```

import "LGTimerWapper.h"

import

@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; self.aSelector = aSelector; 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(LGTimerWapper warpper){ if (warpper.target) { void (lg_msgSend)(void ,SEL, id) = (void )objc_msgSend; lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer); } else { [warpper lg_invalidate]; } } - (void)lg_invalidate{ [self.timer invalidate]; self.timer = nil; }

@end ``` LGTimerWapper的呼叫程式碼:

- (void)viewDidLoad { [super viewDidLoad]; self.timerWapper = [[LGTimerWapper alloc] lg_initWithTimeInterval:1 target:self selector:@selector(fireHome) userInfo:nil repeats:YES]; } - (void)fireHome{ num++; } - 在LGTimerWapper中,定義初始化lg_initWithtimInterval方法和lg_invalidate釋放方法 - 初始化方法 - 控制元件內部的target使用weak修飾,對ViewController進行弱持有 - 監測target中是否存在該selector。如果存在,對當前類使用Runtime新增同名方法編號,指向自身內部fireHomeWapper的函式地址 - 建立真正的NSTimer定時器,將控制元件自身的例項物件傳入target,避免NSTimer對ViewController強持有。 - 當NSTimer回撥時,會進入fireHomeWapper函式 - 函式內部不負責業務處理,如果target存在,使用objc_msgSend,將訊息傳送給target自身下的selector方法。 - 當頁面退出時,ViewController可以正常釋放。但LGTimerWapper和NSTimer相互持有,雙方都無法釋放。 - 由於雙方都無法釋放,NSTimer的回撥會繼續呼叫 - 當進入fireHomeWapper函式,發現target已經不存在了,呼叫LGTimerWapper的lg_invalidate方法,內部對NSTimer進行釋放 - 當NSTimer釋放後,對LGTimerWapper的強持有解除,LGTimerWapper也跟著釋放。