RunLoop 應用:NSTimer、 PerformSelector、常駐線程
iOS 中有兩套API訪問 Foundation(NSRunLoop), CoreFoundation CFRunLoopRef
一、RunLoop 引入
- 在程序的啟動入口Main 函數中,UIApplicationMain 函數內部就啟動了一個與
主線程
相關聯的RunLoop
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- RunLoop 字面意義上:跑圈(那我可不可以看成是死循環呢),那么來驗證一下吧
int main(int argc, char * argv[]) {
@autoreleasepool {
// 查看了UIApplicationMain 的返回值是int類型
// UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString * __nullable principalClassName, NSString * __nullable delegateClassName);
NSLog(@"welcome to Runloop");
int jjTest = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"快到這里來");
return jjTest;
}
}
控制的輸出是這樣的.png
- 從上可以看出main 函數里面啟動了個RunLoop,而且是保持運行的狀態
二、RunLoop里面到底有啥呢?
- 上面說過RunLoop 和 主線程的關系,那線程和RunLoop 是什么關系呢?
- 每條線程都有
一個唯一的
與之對應的RunLoop對象 - Foundation:
- 1.獲取當前線程RunLoop:[NSRunLoop currentRunLoop]
- 2.獲取主線程RunLoop:[NSRunLoop mainRunLoop]
- CoreFoundation:
- 1.獲取當前線程RunLoop:CFRunLoopGetCurrent()
- 2.獲取主線程RunLoop:CFRunLoopGetMain()
- 每條線程都有
- 了解一個對象的最好方式就是打印出來看看:
RunLoop 對象
我的天該怎么看:JJ看到的貌似是一些CFRunLoopObserver CFRunLoopSource, 還有timers 等對象,打印出來發現還是有些復雜了,what should i do~
那就只能看看官方文檔有關RunLoop的描述了,如果英文不好的就繼續往下
-
根據文檔的描述:RunLoop相關聯的類有五個(再看看打印出來的內容)
- CFRunLoopRef
- CFRunLoopSourceRef
- CFRunLoopObserverRef
- CFRunLoopTimerRef
- CFRunLoopModeRef
RunLoop 中對象
RunLoop 對象圖示
三、RunLoop相關類的理解
-
1.從上圖可以看出
- 一個RunLoop 可以有多個Mode(模式),每個模式又包含若干個
Source
/Observer
/Timer
- RunLoop 一次只能運行一種Mode,切換Mode需要退出當前Mode
- 一個RunLoop 可以有多個Mode(模式),每個模式又包含若干個
-
CFRunLoopModeRef 一共有五種模式
- kCFRunLoopDefaultMode 默認模式,通常主線程在這個模式下運行
- kCFRunLoopCommonModes 占位符(表示)
- UITrackingRunLoopMode 界面跟蹤Mode,用于追蹤Scrollview的觸摸滑動
- UIInitializationRunLoopMode:剛啟動App時進入的第一個Mode,啟動后不在使用。
- GSEventReceiveRunLoop:內部Mode,接收系事件。
-
CFRunLoopSourceRef(函數調用棧)
- Source0:非基于Port(處理事件)
- Source1:基于Port (接收分發系統事件)
- 觸摸事件:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"-------");
}
- 觸摸事件的函數調用棧調用順序入下圖:
不難看出Source0、1的作用
CFRunLoopTimerRef:基于時間的觸發器(和NSTimer差不多)
CFRunLoopObserverRef:監聽RunLoop狀態的觀察者
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 1
kCFRunLoopBeforeTimers = (1UL << 1), // 2
kCFRunLoopBeforeSources = (1UL << 2), // 4
kCFRunLoopBeforeWaiting = (1UL << 5), // 32
kCFRunLoopAfterWaiting = (1UL << 6), // 64
kCFRunLoopExit = (1UL << 7), // 128
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
- RunLoop 處理邏輯
轉載
四、RunLoop 使用
-
監聽RunLoop:監聽RunLoop的狀態 用途:在RunLoop喚醒前做些操作
- 創建Observer:CFRunLoopObserverCreateWithHandler
- 添加觀察者 :CFRunLoopAddObserver
- 釋放Observer:CFRelease
- 本例ARC狀態,ARC只能對OC對象進行內存管理,C中的對象需要自行管理
// 創建observer CFRunLoopObserverRef obser = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { NSLog(@"-----Runloop狀態------%zd", activity); }); // 添加觀察者:監聽RunLoop的狀態 CFRunLoopAddObserver(CFRunLoopGetMain(), obser, kCFRunLoopDefaultMode); // 釋放Observer CFRelease(obser);
-
在RunLoop中創建定時器:
- 定時器NSTimer的創建方式一:timerWithTimeInterval:
- 創建timer
- 將timer 添加到RunLoop中
NSTimer *timer =[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(JYTest) userInfo:nil repeats:YES]; // 定時器只運行在NSDefaultRunLoopMode下,一旦RunLoop進入其他模式,這個定時器就不會工作 // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 定時器只運行在UITrackingRunLoopMode下,一旦RunLoop進入其他模式,這個定時器就不會工作 // [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]; // 標記為common modes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- 定時器NSTimer的創建方式一:timerWithTimeInterval:
}
- 定時器NSTimer的創建方式二:scheduledTimerWithTimeInterval
- 創建timer:此方法是自動將timer 添加到線程中
- 注意NSTimer 在`子線程創建timer` 需要手動開啟當前線程的RunLoop
```objc
- (void)scheduledTimer {
// 調用了
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(JYTest) userInfo:nil repeats:YES];
// 改變Timer的模式調用了scheduledTimer返回的定時器,已經自動被添加到當前runLoop中,而且是NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void) JYTest {
NSLog(@"----0 0 ");
}
-
需求一 開辟一個不死線程來處理自定義的一些事件(常駐線程)
- 如何讓一個線程處理一個任務之后不死呢?
- 答:主線程是通過創建RunLoop 使得主線程不死,一直處理事件。所以創建一個子線程,并創建子線程與之對應的RunLoop
-
創建一個不死線程:
- 創建一個子線程
- 為了監聽線程是否被銷毀,本例自定了一個
JJThread
,并重寫dealloc 方法
// 將線程定義成屬性
@property (nonatomic, strong) JJThread *thread;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.thread = [[JJThread alloc] initWithTarget:self selector:@selector(jjTest) object:nil];
[self.thread start];
}
- 創建RunLoop,方式一
- 1.前面說過RunLoop中必須有source,Observer,Timer 其中的任一一個,若為空則執行一次RunLoop,就退出Runloop
- 2.stackoverflow解釋上面說的這個問題
- (void)jjTest {
// NSRunloop 中必須要有timer, source, observer 一個或者多個,不然會導致
NSLog(@"-----jjTest-------%@", [NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"-----jjTest-------%@", [NSThread currentThread]);
// http://stackoverflow.com/questions/31199802/what-does-nsrunloop-currentrunloop-runmodensdefaultrunloopmode-beforedate
}
-
創建RunLoop,方式二
- 上面說RunLoop 必須要有中必須要有timer, source, observer 一個或者多個,這里為什么沒有源還行呢。
- 答:因為RunLoop一直在執行退出,進入的狀態,這個中間若是接收到performSelector 發放不就有source,可以嘗試一下
- 注意點:while 條件中會定義一個BOOL 的屬性,做標記管理RunLoop 是否退出死循環。
- (void)jjTest {
NSLog(@"----------run----%@", [NSThread currentThread]);
while (1) {
[[NSRunLoop currentRunLoop] run];
}
}
- (void)jjRun {
NSLog(@"------jjRun------%@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(jjRun) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- PS:有需要源碼github/JeversonJee下載