iOS開發,ios開發吧

來源:互聯網
上載者:User

iOS開發,ios開發吧
RunLoop概念

運行迴圈,一個 run loop 就是一個事件處理的迴圈,用來不停的調度工作以及處理事件

作用

  • 保持程式的持續運行
  • 監聽處理App中的各種事件(觸摸事件,定時器事件,selector事件)
  • 節省CPU資源,提高程式效能:該做事時做事,該休息時休息
  • 一次RunLoop迴圈負責繪製螢幕上所有的點
入口函數
int main(int argc, char * argv[]) {   @autoreleasepool {       return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));   }
}

     UIApplicationMain()此函數內部就啟動了一個RunLoop,所以此函數一直沒有返回,保持了程式的持續運行,這個預設啟動的RunLoop是跟主線程相關的

RunLoop對象     

  • Foundation架構下 : NSRunLoop (基於 CFRunLoopRef 的封裝,提供了物件導向的 API,但是這些 API 不是安全執行緒的)
  • Core Foundation架構下 : CFRunLoopRef (純 C 函數的 API,所有這些 API 都是安全執行緒的)

RunLoop與線程

  • 每條線程有唯一的一個與之對應的RunLoop對象
  • 主線程的RunLoop已經自動建立好了,子線程的RunLoop需要手動建立
  • RunLoop在第一次擷取時建立,線上程結束時銷毀
     NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];//主線程對應的RunLoop     [NSRunLoop mainRunLoop];//當前線程對應的RunLoop     currentRunloop.getCFRunLoop;//轉化為CFRunLoop          CFRunLoopGetMain();     CFRunLoopGetCurrent();          //開啟一個子線程     [[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil] start];          -(void)run {             //建立子線程對應的RunLoop,currentRunLoop 懶載入的        [NSRunLoop currentRunLoop];          }

以下是蘋果官方源碼,通過分析原始碼可以看出利用pthread作為全域字典中的key,並建立與之對應的RunLoop作為Value,RunLoop在我們擷取的時候建立,不擷取不建立,主線程的RunLoop在一開始就自動建立。線程與RunLoop是一一對應的關係

CFRunLoopRef CFRunLoopGetMain(void) {    CHECK_FOR_FORK();    static CFRunLoopRef __main = NULL; // no retain needed    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed    return __main;}CFRunLoopRef CFRunLoopGetCurrent(void) {    CHECK_FOR_FORK();    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);    if (rl) return rl;    return _CFRunLoopGet0(pthread_self());}//全域的Dictionary,key 是 pthread_t, value 是 CFRunLoopRefstatic CFMutableDictionaryRef __CFRunLoops = NULL;//訪問 Dictionary 時的鎖static CFLock_t loopsLock = CFLockInit;//擷取一個 pthread 對應的 RunLoopCF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {    //如果傳進來的線程等於 0    if (pthread_equal(t, kNilPthreadT)) {        //當前線程等於主線程        t = pthread_main_thread_np();    }    //給操作加鎖    __CFLock(&loopsLock);    //如果當前RunLoop為空白,建立。    if (!__CFRunLoops) {        __CFUnlock(&loopsLock);        // 建立字典        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);        // 建立主線程        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());        // 儲存主線程        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);                if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {            CFRelease(dict);        }        CFRelease(mainLoop);        __CFLock(&loopsLock);    }    // 從字典中擷取當前線程的RunLoop    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));    __CFUnlock(&loopsLock);    if (!loop) {        // 如果當前線程的runloop不存在,那麼就為該線程建立一個對應的runloop        CFRunLoopRef newLoop = __CFRunLoopCreate(t);        __CFLock(&loopsLock);        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));        // 把當前子線程和對應的runloop儲存到字典中        if (!loop) {            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);            loop = newLoop;        }        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it        __CFUnlock(&loopsLock);        CFRelease(newLoop);    }    if (pthread_equal(t, pthread_self())) {        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);        }    }    return loop;}
RunLoop相關類

 Core Foundation中關於RunLoop的5個類

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef (基於時間的觸發器)
  • CFRunLoopObserverRef

說明:一個RunLoop包含若干個Mode,每個Mode又包含若干個Source、Timer、Observer,每次RunLoop啟動時,只能指定一個Mode,這個Mode被稱作CurrentMode,如果需要切換Mode,只能退出Loop,在重新指定一個Mode進入,這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。

1.CFRunLoopSourceRef :事件來源(輸入源)

     - Source0:非基於Port的 (使用者主動觸發的事件)

     - Source1:基於Port的 (系統內部的訊息事件)

(Port是線程間通訊的一種方式,如果兩個線程之間想通訊,可以通過Port來通訊。)

 

2.CFRunLoopTimerRef

基於時間觸發器,當其加入RunLoop時,RunLoop會註冊對應的時間點,當時間點到,RunLoop會被喚醒執行裡面的回調。

 

3.CFRunLoopObserverRef 

觀察者,能夠監聽RunLoop的狀態改變。

/* Run Loop Observer Activities */typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0),//即將進入LoopkCFRunLoopBeforeTimers = (1UL << 1),//即將處理TimerkCFRunLoopBeforeSources = (1UL << 2),//即將處理SourcekCFRunLoopBeforeWaiting = (1UL << 5),//即將進入休眠kCFRunLoopAfterWaiting = (1UL << 6),//剛從休眠中喚醒kCFRunLoopExit = (1UL << 7),//即將退出LoopkCFRunLoopAllActivities = 0x0FFFFFFFU};

 

-(void)addObserver{        /*     參數一:怎麼分配儲存空間     參數二:要監聽的狀態 kCFRunLoopAllActivities 所有的狀態     參數三:是否要持續監聽     參數四:優先順序,總是傳0     參數五:當狀態改變時候的回調     */        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(@"即將處理Sources事件");                break;            case kCFRunLoopBeforeWaiting:                NSLog(@"即將進入睡眠");                break;            case kCFRunLoopAfterWaiting:                NSLog(@"被喚醒");                break;            case kCFRunLoopExit:                NSLog(@"runloop退出");                break;                            default:                break;        }            });    //給runloop添加監聽者    /*     參數一:要監聽哪個runloop     參數二:觀察者     參數三:運行模式     */    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);}

 

4.CFRunLoopModeRef :RunLoop的運行模式

在RunLoop中有多個運行模式,但是RunLoop只能選擇一種模式運行,Mode裡面至少要有Timer或者Source

系統預設註冊了5個Mode:

  • kCFRunLoopDefaultMode:App的預設Mode,通常主線程是在這個Mode下運行
  • UITrackingRunLoopMode:介面跟蹤Mode,用於ScrollView追蹤觸摸滑動,保證介面滑動時不受其他Mode影響
  • UIInitializationRunLoopMode:在剛進入App時進入的第一個Mode,啟動完成後就不在使用
  • GCEventReceiveRunLoopMode:接受系統事件的內部Mode,通常用不到
  • NSRunLoopCommonModes:這是一個佔位用的Mode,不是一種真正的Mode
RunLoop相關問題及解釋 

1.Timer與滑動控制項的問題

問題:當在不停地拖動滑動控制項的時候,定時器不工作了

錯誤回答:runloop的優先順序

分析:runloop的幾種常用模式:DefaultMode預設模式,以及UITrackingMode模式,CommonModes佔位模式,runloop進入一種模式的時候,另一種模式的事件不會去處理,當Timer在啟動並執行時候處於DefaultMode模式,當拖動滑動控制項的時候,runloop會立即處理UI事件,切換到UITrackingRunLoopMode模式後,此時DefaultMode模式下的Timer就不工作了

解決:將Timer置於NSRunLoopCommonModes佔位模式下

- (void)viewDidLoad {    [super viewDidLoad];    //1.建立定時器    NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];        //2.將Timer添加到RunLoop中    //[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];    //[[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode];    [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];}- (void)run {    NSLog(@"-------- %@",[NSThread currentThread]);}
View Code

 

    

問題:在多線程開發中,耗時操作我們一般會放在子線程中執行,請問這種線程有什麼特點?

執行個體:假如在上面的定時器的run方法中,執行一個耗時操作,此時會卡住主線程,拖動滑動控制項會很不流暢,應該如何解決?

分析:子線程中預設不會開啟RunLoop迴圈,所以子線程在執行完任務之後就會被回收

- (void)viewDidLoad {    [super viewDidLoad];    dispatch_async(dispatch_get_global_queue(0, 0), ^{        //1.建立定時器        NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];        //2.將Timer添加到RunLoop中        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];                //3.讓RunLoop運行起來        [[NSRunLoop currentRunLoop] run];//死迴圈,後面的代碼不會執行        NSLog(@"+++++++++");    });}- (void)run {    //耗時操作    [NSThread sleepForTimeInterval:1.0];    NSLog(@"-------- %@",[NSThread currentThread]);}
View Code

或者:

- (void)viewDidLoad {    [super viewDidLoad];   [NSThread detachNewThreadSelector:@selector(time2) toTarget:self withObject:nil];}-(void)time2{    //建立當前線程的RunLoop    NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];    //該方法內部自動添加到RunLoop中,並且運行模式是預設模式    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];    //開啟RunLoop    [currentLoop run];}- (void)run {    //耗時操作    [NSThread sleepForTimeInterval:1.0];    NSLog(@"run ----- %@ ---- %@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);}
View Code

 

 

此博文會繼續不斷完善及更新關於涉及到runloop的知識,如果有理解不正確或者有涉及到runloop的應用執行個體,請留言吧,歡迎討論

參考文章:http://blog.ibireme.com/2015/05/18/runloop/

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.