衡阳派盒市场营销有限公司

您好,歡迎來電子發(fā)燒友網! ,新用戶?[免費注冊]

您的位置:電子發(fā)燒友網>源碼下載>數(shù)值算法/人工智能>

再讀蘋果線程配置與Run Loop

大?。?/span>0.6 MB 人氣: 2017-10-12 需要積分:1
 本文為再讀蘋果《Threading Programming Guide》筆記第二篇,作者付宇軒表示:如今關于iOS多線程的文章層出不窮,但我覺得若想更好的領會各個實踐者的文章,應該先仔細讀讀官方的相關文檔,打好基礎,定會有更好的效果。
  文章中有對官方文檔的翻譯,也有自己的理解,官方文檔中代碼片段的示例在這篇文章中都進行了完整的重寫,還有一些文檔中沒有的代碼示例,并且都使用Swift完成,給大家一些Objective-C與Swift轉換的參考。
  系列閱讀
  初識線程線程配置與Run Loop
  線程屬性配置
  線程也是具有若干屬性的,自然一些屬性也是可配置的,在啟動線程之前我們可以對其進行配置,比如線程占用的內存空間大小、線程持久層中的數(shù)據(jù)、設置線程類型、優(yōu)先級等。
  配置線程的棧空間大小
  在前文中提到過線程對內存空間的消耗,其中一部分就是線程棧,我們可以對線程棧的大小進行配置:
  Cocoa框架:在OS X v10.5之后的版本和iOS2.0之后的版本中,我們可以通過修改NSThread類的stackSize屬性,改變二級線程的線程棧大小,不過這里要注意的是該屬性的單位是字節(jié),并且設置的大小必須得是4KB的倍數(shù)。POSIX API:通過pthread_attr_- setstacksize函數(shù)給線程屬性pthread_attr_t結構體設置線程棧大小,然后在使用pthread_create函數(shù)創(chuàng)建線程時將線程屬性傳入即可。
  注意:在使用Cocoa框架的前提下修改線程棧時,不能使用NSThread的detachNewThreadSelector: toTarget:withObject:方法,因為上文中說過,該方法先創(chuàng)建線程,即刻便啟動了線程,所以根本沒有機會修改線程屬性。
  配置線程存儲字典 配置線程類型 設置線程優(yōu)先級
  注意:設置線程的優(yōu)先級時可以在線程運行時設置。
  線程執(zhí)行的任務
  Autorelease Pool
  - (void)myThreadMainRoutine { NSAutoreleasePool*pool = [[NSAutoreleasePoolalloc] init]; // 頂層自動釋放池// 線程執(zhí)行任務的邏輯代碼[pool release]; }
  - (void)myThreadMainRoutine { @autoreleasepool{ // 線程執(zhí)行任務的邏輯代碼} }
  intmain(intargc, char* argv[]) { @autoreleasepool{ returnUIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
  func performInBackground(){ autoreleasepool({ // 線程執(zhí)行任務的邏輯代碼 print(“I am a event, perform in Background Thread.”)})}
  func performInBackground(){ autoreleasepool{ // 線程執(zhí)行任務的邏輯代碼print(“I am a event, perform in Background Thread.”)} }
  設置異常處理 創(chuàng)建Runloop
  簡單的來說,RunLoop用于管理和監(jiān)聽異步添加到線程中的事件,當有事件輸入時,系統(tǒng)喚醒線程并將事件分派給RunLoop,當沒有需要處理的事件時,RunLoop會讓線程進入休眠狀態(tài)。這樣就能讓線程常駐在進程中,而不會過多的消耗系統(tǒng)資源,達到有事做事,沒事睡覺的效果。
  終止線程
  Run Loop
  注:Core Foundation框架是一組C語言接口,它們?yōu)閕OS應用程序提供基本數(shù)據(jù)管理和服務功能,比如線程和Run Loop、端口、Socket、時間日期等。
  注:在二級線程中獲取Run Loop有兩種方式,通過NSRunloop的類方法currentRunLoop獲取Run Loop對象(NSRunLoop),或者通過Core Foundation框架中的CFRunLoopGetCurrent()函數(shù)獲取當前線程的Run Loop對象(CFRunLoop)。NSRunLoop是CFRunLoop的上層封裝。
  letnsrunloop = NSRunLoop.currentRunLoop() letcfrunloop = CFRunLoopGetCurrent()
  Run Loop的事件來源 Run Loop的觀察者
  Run Loop準備開始運行時。當Run Loop準備要執(zhí)行一個Timer Source事件時。當Run Loop準備要執(zhí)行一個Input Source事件時。當Run Loop準備休眠時。當Run Loop被進入的事件消息喚醒并且還沒有開始讓處理器執(zhí)行事件消息時。退出Run Loop時。
  Run Loop的觀察者在NSRunloop中沒有提供相關接口,所以我們需要通過Core Foundation框架使用它,可以通過CFRunLoopObserverCreate方法創(chuàng)建Run Loop的觀察者,類型為CFRunLoopObserverRef,它其實是CFRunLoopObserver的重定義名稱。上述的那些可以被監(jiān)聽的運行狀態(tài)被封裝在了CFRunLoopActivity結構體中,對應關系如下:
  CFRunLoopActivity.EntryCFRunLoopActivity.BeforeTimersCFRunLoopActivity.BeforeSourcesCFRunLoopActivity.BeforeWaitingCFRunLoopActivity.AfterWaitingCFRunLoopActivity.Exit
  Run Loop的觀察者和Timer事件類似,可以只使用一次,也可以重復使用,在創(chuàng)建觀察者時可以設置。如果只使用一次,那么當監(jiān)聽到對應的狀態(tài)后會自行移除,如果是重復使用的,那么會留在Run Loop中多次監(jiān)聽Run Loop相同的運行狀態(tài)。
  Run Loop Modes
  Run Loop Modes可以稱之為Run Loop模式,這個模式可以理解為對Run Loop各種設置項的不同組合,舉個例子,iPhone手機運行的iOS有很多系統(tǒng)設置項,假設白天我打開蜂窩數(shù)據(jù),晚上我關閉蜂窩數(shù)據(jù),而打開無線網絡,到睡覺時我關閉蜂窩數(shù)據(jù)和無線網絡,而打開飛行模式。假設在這三個時段中其他的所有設置項都相同,而只有這三個設置項不同,那么就可以說我的手機有三種不同的設置模式,對應著不同的時間段。那么Run Loop的設置項是什么呢?那自然就是前文中提到的不同的事件來源以及觀察者了,比如說,Run Loop的模式A(Mode A),只包含接收Timer Source事件源的事件消息以及監(jiān)聽Run Loop運行時的觀察者,而模式B(Mode B)只包含接收Input Source事件源的事件消息以及監(jiān)聽Run Loop準備休眠時和退出Run Loop時的觀察者,如下圖所示:
  再讀蘋果線程配置與Run Loop
  所以說,Run Loop的模式就是不同類型的數(shù)據(jù)源和不同觀察者的集合,當Run Loop運行時要設置它的模式,也就是告知Run Loop只需要關心這個集合中的數(shù)據(jù)源類型和觀察者,其他的一概不予理會。那么通過模式,就可以讓Run Loop過濾掉它不關心的一些事件,以及避免被無關的觀察者打擾。如果有不在當前模式中的數(shù)據(jù)源發(fā)來事件消息,那只能等Run Loop改為包含有該數(shù)據(jù)源類型的模式時,才能處理事件消息。
  在Cocoa框架和Core Foundation框架中,已經為我們預定義了一些Run Loop模式:
  默認模式:在NSRunloop中的定義為NSDefaultRunLoopMode,在CFRunloop中的定義為kCFRunLoopDefaultMode。該模式包含的事件源囊括了除網絡鏈接操作的大多數(shù)操作以及時間事件,用于當前Run Loop處于空閑狀態(tài)等待事件時,以及Run Loop開始運行時。NSConnectionReplyMode:該模式用于監(jiān)聽NSConnection相關對象的返回結果和狀態(tài),在系統(tǒng)內部使用,我們一般不會使用該模式。NSModalPanelRunLoopMode:該模式用于過濾在模態(tài)面板中處理的事件(Mac App)。NSEventTrackingRunLoopMode:該模式用于跟蹤用戶與界面交互的事件。模式集合:或者叫模式組,顧名思義就是將多個模式組成一個組,然后將模式組認為是一個模式設置給Run Loop,在NSRunloop中的定義為NSRunLoopCommonModes,在CFRunloop中的定義為kCFRunLoopCommonModes。系統(tǒng)提供的模式組名為Common Modes,它默認包含NSDefaultRunLoopMode、NSModalPanelRunLoopMode、NSEventTrackingRunLoopMode這三個模式。
  以上五種系統(tǒng)預定的模式中,前四種屬于只讀模式,也就是我們無法修改它們包含的事件源類型和觀察者類型。而模式組我們可以通過Core Foundation框架提供的CFRunLoopAddCommonMode(_ rl: CFRunLoop!, _ mode: CFString!)方法添加新的模式,甚至是我們自定義的模式。這里需要注意的是,既然在使用時,模式組是被當作一個模式使用的,那么自然可以給它設置不同類型的事件源或觀察者,當給模式組設置事件源或觀察者時,實際是給該模式組包含的所有模式設置。比如說給模式組設置了一個監(jiān)聽Run Loop準備休眠時的觀察者,那么該模式組里的所有模式都會被設置該觀察者。
  Input Source
  前文中說過,Input Sources接收到各種操作輸入事件消息,然后異步的分派給對應事件處理方法。在Input Sources中又分兩大類的事件源,一類是基于端口事件源(Port-based source),在CFRunLoopSourceRef的結構中為source1,主要通過監(jiān)聽應用程序的Mach端口接收事件消息并分派,該類型的事件源可以主動喚醒Run Loop。另一類是自定義事件源(Custom source),在CFRunLoopSourceRef的結構中為source0,一般是接收其他線程的事件消息并分派給當前線程的Run Loop,比如performSwlwctor:onThread:。..系列方法,該類型的事件源無法自動喚醒Run Loop,而是需要手動將事件源設置為待執(zhí)行的標記,然后再手動喚醒Run Loop。雖然這兩種類型的事件源接收事件消息的方式不一樣,但是當接收到消息后,對消息的分派機制是完全相同的。
  Port-Based Source
  Cocoa框架和Core Foundation框架都提供了相關的對象和函數(shù)用于創(chuàng)建基于端口的事件源。在Cocoa框架中,實現(xiàn)基于端口的事件源主要是通過NSPort類實現(xiàn)的,它代表了交流通道,也就是說在不同的線程的Run Loop中都存在NSPort,那么它們之間就可以通過發(fā)送與接收消息(NSPortMessage)互相通信。所以我們只需要通過NSPort類的類方法port創(chuàng)建對象實例,然后通過NSRunloop的方法將其添加到Run Loop中,或者在創(chuàng)建二級線程時將創(chuàng)建好的NSPort對象傳入即可,無需我們再做消息、消息上下文、事件源等其他配置,都由Run Loop自行配置好了。而在Core Foundation框架中就比較麻煩一些,大多數(shù)配置都需要我們手動配置,在后面會詳細舉例說明。
  Custom Input Source
  Cocoa框架中沒有提供創(chuàng)建自定義事件源的相關接口,我們只能通過Core Foundation框架中提供的對象和函數(shù)創(chuàng)建自定義事件源,手動配置事件源各個階段要處理的邏輯,比如創(chuàng)建CFRunLoopSourceRef事件源對象,通過CFRunLoopScheduleCallBack回調函數(shù)配置事件源上下文并注冊事件源,通過CFRunLoopPerformCallBack回調函數(shù)處理接收到事件消息后的邏輯,通過CFRunLoopCancelCallBack函數(shù)銷毀事件源等等,在后文中會有詳細舉例說明。
  雖然Cocoa框架沒有提供創(chuàng)建自定義事件源的相關對象和接口,但是它為我們預定義好了一些事件源,能讓我們在當前線程、其他二級線程、主線程中執(zhí)行我們希望被執(zhí)行的方法,讓我們看看NSObject中的這些方法:
  func performSelectorOnMainThread(_ aSelector: Selector, withObject arg: AnyObject?, waitUntilDone wait: Bool) func performSelectorOnMainThread(_ aSelector: Selector, withObject arg: AnyObject?, waitUntilDone wait: Bool, modes array: [String]?)
  這兩個方法允許我們將當前線程中對象的方法讓主線程去執(zhí)行,可以選擇是否阻塞當前線程,以及希望被執(zhí)行的方法作為事件消息被何種Run Loop模式監(jiān)聽。
  注:如果在主線程中使用該方法,當選擇阻塞當前線程,那么發(fā)送的方法會立即被主線程執(zhí)行,若選擇不阻塞當前線程,那么被發(fā)送的方法將被排進主線程Run Loop的事件隊列中,并等待執(zhí)行。
  func performSelector(_aSelector: Selector, withObjectanArgument: AnyObject?, afterDelaydelay: NSTimeInterval) func performSelector(_aSelector: Selector, withObjectanArgument: AnyObject?, afterDelaydelay: NSTimeInterval, inModesmodes: [String])
  這兩個方法允許我們給當前線程發(fā)送事件消息,當前線程接收到消息后會依次加入Run Loop的事件消息隊列中,等待Run Loop迭代執(zhí)行。該方法還可以指定消息延遲發(fā)送時間及消息希望被何種Run Loop模式監(jiān)聽。
  注:該方法中的延遲時間并不是延遲Run Loop執(zhí)行事件消息的事件,而是延遲向當前線程發(fā)送事件消息的時間。另外,即便不設置延遲時間,那么發(fā)送的事件消息也不一定立即被執(zhí)行,因為在Run Loop的事件消息隊列中可以已有若干等待執(zhí)行的消息。
  func performSelector(_ aSelector: Selector, onThread thr: NSThread, withObject arg: AnyObject?, waitUntilDone wait: Bool) func performSelector(_ aSelector: Selector, onThread thr: NSThread, withObject arg: AnyObject?, waitUntilDone wait: Bool, modes array: [String]?)
  這兩個方法允許我們給其他二級線程發(fā)送事件消息,前提是要取得目標二級線程的NSThread對象實例,該方法同樣提供了是否阻塞當前線程的選項和設置Run Loop模式的選項。
  注:使用該方法給二級線程發(fā)送事件消息時要確保目標線程正在運行,換句話說就是目標線程要有啟動著的Run Loop。并且保證目標線程執(zhí)行的任務要在應用程序代理執(zhí)行applicationDidFinishLaunching:方法前完成,否則主線程就結束了,目標線程自然也就結束了。
  funcperformSelectorInBackground(_ aSelector: Selector, withObject arg: AnyObject?)
  該方法允許我們在當前應用程序中創(chuàng)建一個二級線程,并將指定的事件消息發(fā)送給新創(chuàng)建的二級線程。
  classfunc cancelPreviousPerformRequestsWithTarget(_aTarget: AnyObject)classfunc cancelPreviousPerformRequestsWithTarget(_aTarget: AnyObject, selectoraSelector: Selector, objectanArgument: AnyObject?)
  這兩個方法是NSObject的類方法,第一個方法作用是在當前線程中取消Run Lop中某對象通過performSelector:withObject:afterDelay:方法發(fā)送的所有事件消息執(zhí)行請求。第二個方法多了兩個過濾參數(shù),那就是方法名稱和參數(shù),取消指定方法名和參數(shù)的事件消息執(zhí)行請求。
  Timer Source
  Timer Source顧名思義就是向Run Loop發(fā)送在將來某一時間執(zhí)行或周期性重復執(zhí)行的同步事件消息。當某線程不需要其他線程通知而需要自己通知自己執(zhí)行任務時就可以用這種事件源。舉個應用場景,在iOS應用中,我們經常會用到搜索功能,而且一些搜索框具有自動搜索的能力,也就是說不用我們點擊搜索按鈕,只需要輸入完我想要搜索的內容就會自動搜索,大家想一想如果每輸入一個字就開始立即搜索,不但沒有意義,性能開銷也大,用戶體驗自然也很糟糕,我們希望當輸入完這句話,或至少輸入一部分之后再開始搜索,所以我們就可以在開始輸入內容時向執(zhí)行搜索功能的線程發(fā)送定時搜索的事件消息,讓其在若干時間后再執(zhí)行搜索任務,這樣就有緩沖時間輸入搜索內容了。
  這里需要注意的是Timer Source發(fā)送給Run Loop的周期性執(zhí)行任務的重復時間是相對時間。比如說給Run Loop發(fā)送了一個每隔5秒執(zhí)行一次的任務,每次執(zhí)行任務的正常時間為2秒,執(zhí)行5次后終止,假設該任務被立即執(zhí)行,那么當該任務終止時應該歷時30秒,但當?shù)谝淮螆?zhí)行時出現(xiàn)了問題,導致任務執(zhí)行了20秒,那么該任務只能再執(zhí)行一次就終止了,執(zhí)行的這一次其實就是第5次,也就是說不論任務的執(zhí)行時間延遲與否,Run Loop都會按照初始的時間間隔執(zhí)行任務,并非按Finish-To-Finish去算的,所以一旦中間任務有延時,那么就會丟失任務執(zhí)行次數(shù)。關于Timer Source的使用,在后文中會有詳細舉例說明。
  Run Loop內部運行邏輯
  在Run Loop的運行生命周期中,無時無刻都伴隨著執(zhí)行等待執(zhí)行的各種任務以及在不同的運行狀態(tài)時通知不同的觀察者,下面我們看看Run Loop中的運行邏輯到底是怎樣的:
  通知對應觀察者Run Loop準備開始運行。通知對應觀察者準備執(zhí)行定時任務。通知對應觀察者準備執(zhí)行自定義事件源的任務。開始執(zhí)行自定義事件源任務。如果有基于端口事件源的任務準備待執(zhí)行,那么立即執(zhí)行該任務。然后跳到步驟9繼續(xù)運轉。通知對應觀察者線程進入休眠。
  如果有下面的事件發(fā)生,則喚醒線程:
  * 接收到基于端口事件源的任務。
  定時任務到了該執(zhí)行的時間點。Run Loop的超時時間到期。Run Loop被手動喚醒。
  通知對應觀察者線程被喚醒。
  執(zhí)行等待執(zhí)行的任務。
  * 如果有定時任務已啟動,執(zhí)行定時任務并重啟Run Loop。然后跳到步驟2繼續(xù)運轉。
  如果有非定時器事件源的任務待執(zhí)行,那么分派執(zhí)行該任務。如果Run Loop被手動喚醒,重啟Run Loop。然后跳轉到步驟2繼續(xù)運轉。
  通知對應觀察者已退出Run Loop。
  以上這些Run Loop中的步驟也不是每一步都會觸發(fā),舉一個例子:
  1.對應觀察者接收到通知Run Loop準備開始運行 -》 3.對應觀察者接收到通知Run Loop準備執(zhí)行自定義事件源任務 -》 4.開始執(zhí)行自定義事件源任務 -》 任務執(zhí)行完畢且沒有其他任務待執(zhí)行 -》 6.線程進入休眠狀態(tài),并通知對應觀察者 -》 7.接收到定時任務并喚醒線程 -》 8.通知對應觀察者線程被喚醒 -》 9.執(zhí)行定時任務并重啟Run Loop -》 2.通知對應觀察者準備執(zhí)行定時任務 -》 Run Loop執(zhí)行定時任務,并在等待下次執(zhí)行任務的間隔中線程休眠 -》 6.線程進入休眠狀態(tài),并通知對應觀察者…
  這里需要注意的一點是從上面的運行邏輯中可以看出,當觀察者接收到執(zhí)行任務的通知時,Run Loop并沒有真正開始執(zhí)行任務,所以觀察者接收到通知的時間與Run Loop真正執(zhí)行任務的時間有時間差,一般情況下這點時間差影響不大,但如果你需要通過觀察者知道Run Loop執(zhí)行任務的確切時間,并根據(jù)這個時間要進行后續(xù)操作的話,那么就需要通過結合多個觀察者接收到的通知共同確定了。一般通過監(jiān)聽準備執(zhí)行任務的觀察者、監(jiān)聽線程進入休眠的觀察者、監(jiān)聽線程被喚醒的觀察者共同確定執(zhí)行任務的確切時間。
?

非常好我支持^.^

(0) 0%

不好我反對

(0) 0%

再讀蘋果線程配置與Run Loop下載

相關電子資料下載

      發(fā)表評論

      用戶評論
      評價:好評中評差評

      發(fā)表評論,獲取積分! 請遵守相關規(guī)定!

      ?
      棋牌游戏大厅下载| 豪门娱乐| 大发888娱乐城贴吧| bet365彩票| 百家乐官网游戏真人游戏| 世界顶级赌场酒店| 百家乐官网真人大头贴| 玩百家乐官网请高手指点| 百家乐玩揽法的论坛| 德州扑克概率| 百家乐官网棋牌辅助| 百家乐真人游戏网上投注| 大发888代充信用卡| 百家乐官网是哪个国家| 百家乐论坛白菜| 大发888娱乐城开户| 百家乐官网百乐发破解版| 百家乐视频大厅| 德州扑克发牌| 百家乐官网开庄几率| 哪个百家乐投注好| 网上真钱斗地主| 顶尖百家乐官网的玩法技巧和规则| 时时博百家乐的玩法技巧和规则| 衢州市| 马牌百家乐官网的玩法技巧和规则| 皇室百家乐的玩法技巧和规则| 象山县| 百家乐神仙道官网| 二爷百家乐官网的玩法技巧和规则 | 百家乐棋牌辅助| 明升m88| CEO百家乐现金网| 博彩娱乐城| 百家乐5式直缆打法| 民和| 信誉百家乐博彩网| 现金百家乐| 谈谈百家乐赢钱技巧| 昭觉县| 百家乐官网赢足球博彩皇冠|