NSTimer詳解----使用、保留環問題、與runloop的關係
一、使用NSTimer你需要了解的內容
(1)只有將計時器放在執行迴圈中,它才能正常的觸發任務。
(2)NSTimer物件會保留target,直到計時器失效,呼叫invalidate可令其失效;一次性計時器觸發完就失效
(3)反覆執行的timer容易造成保留環。
(4)可以使用分類,用block打破保留環,後面會具體介紹
iOS 10之後引入新方法,可以得到timer弱引用避免保留環
__weak MyClass* weakSelf = self;
_timer = [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer* t) {
MyClass* _Nullable strongSelf = weakSelf;
[strongSelf doSomething];
}];
(5)NSTimer的實時性
無論是單次執行的NSTimer還是重複執行的NSTimer都不是準時的,這與當前NSTimer所處的執行緒有很大的關係,如果NSTimer當前所處的執行緒正在進行大資料處理(假設為一個大迴圈),NSTimer本次執行會等到這個大資料處理完畢之後才會繼續執行。
這期間有可能會錯過很多次NSTimer的迴圈週期,但是NSTimer並不會將前面錯過的執行次數在後面都執行一遍,而是繼續執行後面的迴圈,也就是在一個迴圈週期內只會執行一次迴圈。
無論迴圈延遲的多離譜,迴圈間隔都不會發生變化,在進行完大資料處理之後,有可能會立即執行一次NSTimer迴圈,但是後面的迴圈間隔始終和第一次新增迴圈時的間隔相同。
二、相關API介紹
//使用下面兩個方法建立timer,你需要手動顯示將timer新增進自定義的runloop中
timerWithTimeInterval:invocation:repeats:
timerWithTimeInterval:target:selector:userInfo:repeats
//使用下面兩個函式,timer會預設新增到當前runloop或者預設runloop中
scheduledTimerWithTimeInterval:invocation:repeats:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
//這裡分析最後一個函式:這個方法建立出來的計時器可以在一段時間後執行任務,也可以令其重複執行任務;計時器會自動保留物件,等到任務執行完畢再釋放物件。呼叫invalidate可以使timer失效;執行完任務後,一次性的timer會失效。如果將計時器設定為重複執行的模式,必須手動呼叫invalidate使其停止
//觸發timer
- (void)fire;
//使timer無效
(void)invalidate;
@property (copy) NSDate *fireDate;
@property (readonly) NSTimeInterval timeInterval;
//下面是iOS10之後引入的方法
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));//新增到當前runloop的預設model中
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
三、簡單使用
@interface
NSTimer *autoTimer;
@implementation
// Start timer
autoTimer = [NSTimer scheduledTimerWithTimeInterval:(3.0)
target:self
selector:@selector(autoTimerFired:)
userInfo:nil
repeats:YES];
// Stop timer:
[autoTimer invalidate];
autoTimer = nil;
四、timer與runloop
(1)timer為什麼要加入runloop?
NSTimer其實也是一種資源,如果看過多執行緒變成指引文件的話,我們會發現所有的source如果要起作用,就得加到runloop中去。同理timer這種資源要想起作用,那肯定也需要加到runloop中才會又效嘍。如果一個runloop裡面不包含任何資源的話,執行該runloop時會立馬退出。你可能會說那我們APP的主執行緒的runloop我們沒有往其中新增任何資源,為什麼它還好好的執行。我們不新增,不代表框架沒有新增,如果有興趣的話你可以列印一下main thread的runloop,你會發現有很多資源。
NSTimer例項總是需要在執行迴圈上進行排程才能正常執行。如果您從主執行緒中執行此操作,則可以使用scheduleTimerWithTimeInterval,它將自動新增到主執行迴圈,無需手動呼叫NSRunLoop方法addTimer。如果需要的話,可以建立計時器並自行新增。
如果您從後臺執行緒建立一個沒有自己的執行迴圈的計時器(預設情況下,當您使用後臺排程佇列或執行佇列時,正在執行的執行緒將不具有自己的執行迴圈)然後您必須手動將計時器新增到執行迴圈。
或者,如果您真的希望計時器在後臺執行緒上執行,而不是為該執行緒建立執行迴圈並將計時器新增到新的執行迴圈,則可以使用GCD排程計時器,它不需要runloop。有關Objective-C示例
簡單的英文大家應該可以理解的……^_^
If you want a repeating timer to be invoked on a dispatch_queue_t, use dispatch_source_create with a DISPATCH_SOURCE_TYPE_TIMER:
dispatch_queue_t queue = dispatch_queue_create("com.firm.app.timer", 0);
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), 20ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
// stuff performed on background queue goes here
NSLog(@"done on custom background queue");
// if you need to also do any UI updates, synchronize model updates,
// or the like, dispatch that back to the main queue:
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"done on main queue");
});
});
dispatch_resume(timer);
That creates a timer that runs once every 20 seconds (3rd parameter to dispatch_source_set_timer), with a leeway of a one second (4th parameter to dispatch_source_set_timer).
To cancel this timer, use dispatch_source_cancel:
dispatch_source_cancel(timer);
因此,除非在後臺執行緒中建立定時器,否則只需使用scheduledTimerWithTimeInterval,而不必擔心手動將其新增到執行迴圈中
(2)runloop 的mode的切換對timer的影響
以實際例子說明
// 0.沒有設定runloop模式的方式
// [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
// 1.建立NSTimer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
// 2.1.新增到runloop
// 把定時器新增到當前的runloop中,並選擇預設執行模式
// kCFRunLoopDefaultMode == NSDefaultRunLoopMode
// 但是這種模式下如果拖拽介面,runloop會自動進入UITrackingMode,優先於定時器追蹤模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 2.2.我們更改一下模式UITrackingRunLoopMode
// 當runloop的模式是UITrackingRunLoopMode時定時器才工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// 2.3.還有一種runloop的mode,佔位執行模式
// 就可以無論是介面追蹤還是普通模式都可以執行
/**
common modes = <CFBasicHash 0x7fb7424021b0 [0x10f12f7b0]>{type = mutable set, count = 2,
entries =>
0 : <CFString 0x11002a270 [0x10f12f7b0]>{contents = "UITrackingRunLoopMode"}
2 : <CFString 0x10f14fb60 [0x10f12f7b0]>{contents = "kCFRunLoopDefaultMode"}
*/
/**
NSTimer的問題,NSTimer是runloop的一個觸發源,由於NSTimer是新增到runloop中使用的,所以如果只新增到default模式,會導致拖拽介面的時候runloop進入介面跟蹤模式而定時器暫停執行,不拖拽又恢復的問題,這時候就應該使用runloop的NSRunLoopCommonModes模式,能夠讓定時器在runloop兩種模式切換時也不受影響。
*/
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
五、NSTimer保留環問題
1 @interface NSTimer (EOCBlocksSupport)
2
3 + (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
4 block:(void(^)())block
5 repeats:(BOOL)repeats;
6
7 @end
8
9 @implementation NSTimer (EOCBlocksSupport)
10
11 + (NSTimer *)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
12 block:(void(^)())block
13 repeats:(BOOL)repeats
14 {
15 return [self scheduledTimerWithTimeInterval:interval
16 target:self
17 selector:@selector(eoc_blockInvoke:)
18 userInfo:[block copy]
19 repeats:repeats];
20 }
21
22 + (void)eoc_blockInvoke:(NSTimer*)timer {
23 void (^block)() = timer.userInfo;
24 if (block) {
25 block();
26 }
27 }
在xxxViewContoler裡面使用這個擴充套件。
-(void)startPolling {
__weak EOCClass * weakself = self;
_pollTimer = eoc_scheduledTimerWithTimeInterval:5.0
block:^ {
EOCClass *strongSelf = weakself;
//上邊程式碼也可以用__strong來修飾強關係,這裡面應該是變為autoReleaseing的了。可以保證在這個作用於範圍使用。
[strongSelf doReFresh];
}
repeats:(BOOL)YES;
}
此處使用__weak還能讓程式更加安全,倘若開發者在delloc的時候忘記呼叫invalidate,從而使定時器在執行【這裡就有疑問了,xxxcontroller持有timer,當控制器回收時timer也應該引用數為0啊,原因是這裡runloop還會持有一份引用。】,倘若這樣的事情發生,weakself會變為nil。
下面整理的stack overflow上的問題,對於理解NSTimeryou
疑問
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:self selector:@selector(tick) userInfo:nil repeats:YES];
這句程式碼,NSTimer將保留target造成保留環,(如果使用timer的例項為A,當指向A例項的最後一個引用移走後,A例項不會被銷燬因為timer還保留著它(這時timer重複執行),而timer也不會釋放因為A引用著它,所以A例項永久的存在,也就是記憶體洩漏了),我瞭解timer必須invalidated,那麼我在dealloc方法中停用timer可以避免,對嗎?
解答1:在dealloc中無效定時器是無用的(在這裡):定時器保持對其目標的強烈引用。這意味著只要定時器保持有效,其目標將不會被釋放。作為推論,這意味著定時器的目標在其dealloc方法中嘗試使定時器無效是沒有意義的,只要定時器有效,dealloc方法將不被呼叫。
針對解答2:
__weak id weakSelf = self;
timer = [NSTimer scheduledTimerWithTimeInterval:30.0f target:weakSelf selector:@selector(tick) userInfo:nil repeats:YES];
具有以下作用:(i)對self有一個弱應用; (ii)讀取弱引用來提供指向NSTimer的指標。 它不會產生具有弱引用的NSTimer的效果。 該程式碼和使用__strong引用之間的唯一區別是,如果self被釋放,那麼你將傳遞nil給定時器。
擴充---stack overflow上提供的一種使用GCD的API事項的重複執行
- (void) doSomethingRepeatedly
{
// Do it once
NSLog(@"doing something …");
// Repeat it in 2.0 seconds
__weak typeof(self) weakSelf = self;
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[weakSelf doSomethingRepeatedly];
});
}
相關文章
- NSTimer使用詳解
- iOS 中 NSTimer 使用詳解iOS
- WebSocket詳解(六):刨根問底WebSocket與Socket的關係Web
- UML類圖與類的關係詳解
- iOS RunLoop詳解iOSOOP
- NSTimer會保留其目標物件物件
- 將NSTimer新增至RunLoop中的兩種方法區別OOP
- 五分鐘看懂UML類圖與類的關係詳解
- 類圖的6大關係詳解
- MapStruct與lombok載入順序問題與annotationProcessorPaths的關係?StructLombok
- CV關於Mysql中ON與Where區別問題詳解buaMySql
- Laravel 中的多對多關係詳解Laravel
- 類之間的6種關係詳解
- 問一個很基礎小白的問題,類與函式的關係。RT函式
- 與if的關係
- 深入解讀ESB與SOA的關係
- 關係等級儲存問題
- webpack(1)安裝環境與解決環境問題Web
- Oracle使用者訪問許可權與PUBLIC角色的關係Oracle訪問許可權
- 物件導向程式設計程式碼詳解(依賴關係,關聯關係,組合關係)物件程式設計
- C指標和陣列的關係詳解指標陣列
- RunLoop原始碼分析、基本使用場景和常見問題OOP原始碼
- RMAN備份中檔案與channel對應關係的格式問題
- 為硬體保留記憶體 問題的解決方法記憶體
- 請教:關於排課問題的實體關係
- nuget使用經驗:複雜依賴關係下的包版本問題
- 關聯關係與依賴關係的區別
- SAP 交貨單與HU指派關係資料不一致問題的解決方案
- 交換機中網路環路常見問題詳解
- leetcode問題與web開發有什麼關係? - RediitLeetCodeWeb
- GCD定時器替換NSTimer不準的問題GC定時器
- 解析SQLite中的常見問題與總結詳解SQLite
- 請教一個資料表關係的問題
- iOS RunLoop 總結以及相關面試題解答iOSOOP面試題
- runLoop瞭解OOP
- 多對一(主鍵)關係,create問題
- 網友問題--service_names與tnsnames.ora中的service_name關係
- RunLoop刨根問底OOP