RunLoop(總結詳細)

RunLoop資料



問題: 什么是RunLoop?

  • 從字面意思看

運行循環
跑圈

  • 基本作用

保持程序的持續運行
處理App中的各種事件(比如觸摸事件、定時器事件、Selector事件)
節省CPU資源,提高程序性能:該做事時做事,該休息時休息
.....

沒有runLoop

有runLoop

main函數中的RunLoop

]




RunLoop與線程

  • 每條線程都有唯一的一個與之對應的RunLoop對象
  • 主線程的RunLoop已經自動創建好了,子線程的RunLoop需要主動創建
  • RunLoop在第一次獲取時創建,在線程結束時銷毀

獲得RunLoop對象

  • Foundation

[NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象

  • CoreFoundation

CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象

RunLoop相關類

  • CoreFoundation中關于RunLoop的5個類

    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef
  • **CFRunLoopModeRef **

    • CFRunLoopModeRef代表RunLoop的運行模式
    • 一個 RunLoop包含若干個 Mode,每個Mode又包含若干個Source/Timer/Observer
    • 每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode
    • 如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入
    • 這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
  • 系統默認注冊了5個Mode:

    • kCFRunLoopDefaultMode:App的默認Mode,通常主線程是在這個Mode下運行
    • UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView追蹤觸摸滑動,保證界面滑動時不受其他
      Mode 影響
    • UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用
    • GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到
    • kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode

RunLoop處理邏輯

  • RunLoop處理邏輯-網友整理版版
    RunLoop處理邏輯-網友整理版版
  • ** RunLoop處理邏輯-官方版 **



    RunLoop處理邏輯-官方版

代碼展示區

01-掌握-NSRunLoop基本概念
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    /*
    // Do any additional setup after loading the view, typically from a nib.
    NSRunLoop *currentRunloop =  [NSRunLoop currentRunLoop];
    NSLog(@"currentRunloop = %@", currentRunloop);
    NSRunLoop *mainRunloop = [NSRunLoop mainRunLoop];
    NSLog(@"mainRunloop = %@", mainRunloop);
     */
    /*
    CFRunLoopRef currentRunloop =  CFRunLoopGetCurrent();
    NSLog(@"currentRunloop = %@", currentRunloop);
    CFRunLoopRef mainRunloop = CFRunLoopGetMain();
    NSLog(@"mainRunloop = %@", mainRunloop);
     */
    
    /*
     1.一條線程對應一個RunLoop
     2.主線程的RunLoop默認已經創建好了, 而子線程的需要我們自己手動創建
     3.一個NSRunLoop/CFRunLoopRef, 就代表一個RunLoop對象
     4.如何獲取當前線程對應的RunLoop對象,currentRunLoop/CFRunLoopGetCurrent
     5.如何獲取主線程對應的RunLoop對象,mainRunLoop/CFRunLoopGetMain
     6.只要線程結束了, 那么與之對應的RunLoop對象也會被釋放
     */
    
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(show) object:nil];
    [thread start];

}

- (void)show
{
//    [[NSRunLoop alloc] init]; // 注意, 如果想給子線程添加RunLoop, 不能直接alloc init
    
    [NSRunLoop currentRunLoop]; // 只要調用currentRunLoop方法, 系統就會自動創建一個RunLoop, 添加到當前線程中
}

@end


02-NSRunLoopMode中的類

#import "ViewController.h"

@interface ViewController ()

/** 時間 */
@property (nonatomic, strong) dispatch_source_t timer;
- (IBAction)btnClick:(id)sender;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s", __func__);
    [self gcdTimer];
}
- (IBAction)btnClick:(id)sender {
    NSLog(@"%s", __func__);
}


- (void)gcdTimer
{
    /*
     dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
     NSLog(@"-----------");
     });
     */
    
    NSLog(@"%s", __func__);
    // 0.獲取一個全局并發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    // 1.創建一個定時器
    // 第四個參數: 傳遞一個隊列, 該隊列對頂了將來的回調方法在哪個線程中執行
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    self.timer = timer;
    //    NSLog(@"%@", timer);
    
    // 2.指定定時器開始的時間和間隔的時間, 以及精準度
    /*
     第1個參數: 需要給哪個定時器設置
     第2個參數: 定時器開始的時間 / DISPATCH_TIME_NOW立即執行
     第3個參數: 定時器開始之后的間隔時間
     第4個參數: 定時器間隔執行的精準度, 傳入0代表最精準(盡量的讓定時器精準), 傳入一個大于0的值, 代表多少秒的范圍是可以接受的
     第四個參數存在的意義: 主要是為了提高程序的性能
     注意點: Dispatch的定時器接收的時間是納秒
     */
    
    // 開始時間
    //    dispatch_time_t startTime = DISPATCH_TIME_NOW;
    dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
    
    // 間隔時間
    uint64_t interval = 1.0 * NSEC_PER_SEC;
    dispatch_source_set_timer(timer, startTime, interval, 0 * NSEC_PER_SEC);
    
    // 3.指定定時器的回調方法
    /*
     第1個參數: 需要給哪個定時器設置
     第2個參數: 需要回調的block
     */
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"++++++++++++++ %@", [NSThread currentThread]);
        
    });
    
    // 4.開啟定時器
    dispatch_resume(timer);
}
- (void)timer
{
    /*
     // 1.創建一個NSTimer
     NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
     // 2.將NSTimer添加到RunLoop中, 并且告訴系統, 當前Tiemr只有在RunLoop的默認模式下才有效
     //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
     
     // 2.將NSTimer添加到RunLoop中, 并且告訴系統, 當前Tiemr只有在Tracking的默認模式下才有效
     //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
     
     // 2.將NSTimer添加到RunLoop中, 并且告訴系統, 在所有被"標記"common的模式都可以運行
     */
    /*
     common modes = <CFBasicHash 0x7fc0b8615250 [0x104be7180]>{type = mutable set, count = 2,
     entries =>
     0 : <CFString 0x1058bae50 [0x104be7180]>{contents = "UITrackingRunLoopMode"}
     2 : <CFString 0x104bc3080 [0x104be7180]>{contents = "kCFRunLoopDefaultMode"}
     }
     UITrackingRunLoopMode和kCFRunLoopDefaultMode都被標記為了common模式, 所以只需要將timer的模式設置為forMode:NSRunLoopCommonModes, 就可以在默認模式和追蹤模式都能夠運行
     */
    //    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
    // 注意: 如果是通過scheduledTimerWithTimeInterval創建的NSTimer, 默認就會添加到RunLoop得DefaultMode中 , 所以它會自動運行
    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
    // 雖然默認已經添加到DefaultMode中, 但是我們也可以自己修改它的模式
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}


- (void)show
{
    NSLog(@"%s", __func__);
}

@end


03-CFRunLoopObserverRef

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 創建Observer
    /*
     第1個參數: 指定如何給observer分配存儲空間
     第2個參數: 需要監聽的狀態類型/ kCFRunLoopAllActivities監聽所有狀態
     第3個參數: 是否每次都需要監聽
     第4個參數: 優先級
     第5個參數: 監聽到狀態改變之后的回調
     */
    CFRunLoopObserverRef  observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"即將進入runloop");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"即將處理timer");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"即將處理source");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"即將進入睡眠");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"剛從睡眠中喚醒");
                break;
            case kCFRunLoopExit:
                NSLog(@"即將退出");
                break;
            default:
                break;
        }
    });
    
    
    // 給主線程的RunLoop添加一個觀察者
    /*
     第1個參數: 需要給哪個RunLoop添加觀察者
     第2個參數: 需要添加的Observer對象
     第3個參數: 在哪種模式下可以可以監聽
     */
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
    
    // 釋放對象
    CFRelease(observer);
    
    [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
}

- (void)show{
     NSLog(@"show");
}
@end


04-掌握-RunLoop應用場景

#import "ViewController.h"

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;

/** 子線程 */
@property (nonatomic, strong) NSThread *thread;
@end

@implementation ViewController

- (void)viewDidLoad
{
    /*
     自動釋放池什么時候創建和釋放
     1.第一次創建, 是在runloop進入的時候創建  對應的狀態 = kCFRunLoopEntry
     2.最后一次釋放, 是在runloop退出的時候  對應的裝 = kCFRunLoopExit
     3.其它創建和釋放
        * 每次睡覺的時候都會釋放前自動釋放池, 然后再創建一個新的
     
     _wrapRunLoopWithAutoreleasePoolHandler activities = 0x1,   
     1  = kCFRunLoopEntry  進入loop  創建自動釋放池
     
     _wrapRunLoopWithAutoreleasePoolHandler activities = 0xa0,  
     160 = kCFRunLoopBeforeWaiting  即將進入睡眠 ,先釋放上一次創建的自動釋放池, 然后再創建一個新的釋放池
     +
     kCFRunLoopExit 即將退出loop  釋放自動釋放池
     
     */
    NSLog(@"%@", [NSRunLoop currentRunLoop]);
//    NSLog(@"%d %d", 0x1, 0xa0);
    
    
    NSThread *thread = [NSThread alloc] initWithTarget:self selector:@selector(show) object:nil];
    self.thread = thread;
    [thread start];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s", __func__);
    /*
    // 1.在指定模式下進行特定的操作
    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"abc"] afterDelay:2.0 inModes:@[UITrackingRunLoopMode]];
     */
    
    // 默認清空下一個線程只能使用一次, 也就是說只能執行一個操作, 執行完畢之后就不能使用了
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)show{
    NSLog(@"%s", __func__);
//    while(1);
    
    // 1.子線程的NSRunLoop需要手動創建
    // 2.子線程的NSRunLoop需要手動開啟
    // 3.如果子線程的NSRunLoop沒有設置source or timer, 那么子線程的NSRunLoop會立刻關閉
//    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    
   
//    NSTimer *timer = [NSTimer timerWithTimeInterval:5.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
//    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//    [[NSRunLoop currentRunLoop] run];
    
    
    // 注意點: NSRunLoop只會檢查有沒有source和timer, 沒有就關閉, 不會檢查observer
    CFRunLoopObserverRef  observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    });

    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    // 釋放對象
    CFRelease(observer);
    
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"end -----");
}

- (void)test
{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

@end



如何開啟一個常駐線程

  • Listing 3-1 shows the main routine for a thread that attaches a run loop observer to its run loop.
    The purpose of the example is to show you how to create a run loop observer, so the code simply sets up a run loop observer to monitor all run loop activities.
    The basic handler routine (not shown) simply logs the run loop activity as it processes the timer requests.
    • 翻譯: 顯示了主程序的線程高度運行循環觀察其運行循環。
      這個例子的目的是向您展示如何創建一個運行循環觀察者,因此代碼簡單地設置一個運行循環觀察員監督所有運行循環活動。
      最基本的處理程序例程(圖中未顯示)簡單的日志處理計時器請求運行循環活動。
**Listing 3-1**  Creating a run loop observer
- (void)threadMain

{
    // The application uses garbage collection, so no autorelease pool is needed.
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];

    // Create a run loop observer and attach it to the run loop.
    CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};

    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);

    if (observer) {
        CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];

        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }

    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
    NSInteger loopCount = 10;
    do {
        // Run the run loop 10 times to let the timer fire.
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
   } while (loopCount);
}

// 官方文檔介紹
When configuring the run loop for a long-lived thread, it is better to add at least one input source to receive messages. 
Although you can enter the run loop with only a timer attached, once the timer fires, it is typically invalidated,
which would then cause the run loop to exit. Attaching a repeating timer could keep the run loop running over a longer period of time, 
but would involve firing the timer periodically to wake your thread, which is effectively another form of polling. 
By contrast, an input source waits for an event to happen, keeping your thread asleep until it does.

/*
    當你配置一個線程為常駐線程時,最好的方式是添加至少一個輸入源去接收消息。
    盡管你可以只用一個定時器連接進入RunLoop,一旦定時器被銷毀,RunLoop也通常是無效的,這會導致退出當前運行循環。
    附加一個相關的定時器可以讓保持長時間的運行,但需要觸發計時器定期喚醒你的線程,這實際上是另一種形式的循環。
    相比之下,一個輸入源等待一個事件發生,保持你的線程運行,直到它睡著了。
*/

基于端口的常駐線程

  • shows the primary thread code for launching a secondary worker thread. Because the Cocoa framework performs many of the intervening steps for configuring the port and run loop, the launchThread method is noticeably shorter than its Core Foundation equivalent; however, the behavior of the two is nearly identical. One difference is that instead of sending the name of the local port to the worker thread, this method sends the NSPort object directly.
    • 顯示了啟動一個主要線程代碼輔助工作線程。因為Cocoa框架執行許多干預措施的配置端口和運行循環,launchThread方法明顯短于其核心基礎等效;然而,兩個的行為幾乎是相同的。一個區別是,而不是發送本地端口的名稱工作線程,該方法直接發送NSPort對象。
NSPort* myPort = [NSMachPort port];

if (myPort) {
    // This class handles incoming port messages.
    [myPort setDelegate:self];

    // Install the port as an input source on the current run loop.
    [[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];

    // Detach the thread. Let the worker release the port.
    [NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:) toTarget:[MyWorkerClass class] withObject:myPort];
}

// 官方文檔介紹
In order to set up a two-way communications channel between your threads, 
you might want to have the worker thread send its own local port to your main thread in a check-in message. 
Receiving the check-in message lets your main thread know that all went well in launching the second thread and also gives you a way to send further messages to that thread.

/* 翻譯:為了建立一個雙向通信信道之間的線程,你可能想要工作線程發送自己的本地端口到主線程在登記信息。
主線程接收登記信息讓你知道一切順利發射第二個線程也給你進一步消息發送給該線程的方法。
*/
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容