當目前為止,你的iOS代碼是不是都只有一個mainThread,你的程式一直都是串列的,總是幹完任務A再去幹任務B,哪怕AB之間完全沒有依賴關係。mainThread是用來處理與UI相關的事件的,如果你在mainThread中執行一些需要耗費大量時間的任務(比如從網路下載資料),那麼這個程式的使用者體驗將是極差的,因為使用者往往需要等待很久。
重要:任何與UI相關的操作都應該放在mainThread中來處理。 同時,要知道現在是個行動裝置都在標稱自己是多核的,你好意思讓你的程式只佔用其中一個,而讓另外的都自顧自兒的空轉嗎?!我想我沒有必要去闡述多線程編程有什麼哪些優點,會帶來什麼好處,因為大家都懂的。 當然,要實現程式的並發,也並不要求運行它的裝置一定是多核的,我們可以通過對CPU的調度(時間片輪轉)來實現”表明上“的同時執行。在開始這篇文章之前,有必要先解釋一下幾個關鍵性的術語: thread(線程)——可簡單理解為可獨立執行的程式碼片段。 process(進程)——指一個正在啟動並執行可執行程式,一個進程可以包含多個線程。 task(任務)——對程式執行的工作的抽象。 concurrency(並發)——指程式可以同時或者幾乎同時執行多個任務。
前面提到,即使是在單核CPU的情況下,也可以實現程式的並發。比如,在1s之內有10個相同優先順序的task需要執行,系統可以將1s的CPU時間均分為10個100ms,並把這10個100ms的CPU時間分配給10個task。那麼,表面上看來,在1s的CPU時間內這10個task同時執行了。但是,現在的系統已經實現了真正的多核,沒有必要這麼吝嗇的在一個CPU上去瓜分時間片,況且CPU的調度也是耗費資源和時間的。1.1 利用線程實現並發 在iOS下實現程式的並發,將是前所未有的簡單。最直接的:
[self performSelectorInBackground:@selector(try:) withObject:para1];
簡單的調用一下performSelectorInBackground:withObject:函數,把你想要並發執行的函數(任務)傳給第一個參數,如樣本中的try,第二個參數para1是try函數的參數。這樣系統就自動為你開闢了一個後台線程,你的任務已經是在後台而不是mainThread了。 當然你也可以自己手動建立一個新的Thread,如下:
[NSThread detachNewThreadSelector:@selector(try:) toTarget:self withObject:para1]
這是類方法。也可以建立一個Thread執行個體:
NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:nil];//start thread manually[myThread start];
為了防止記憶體泄露,如果你使用的是記憶體管理模型(即含有retain、release操作)你應該在try中添加autoreleasepool,像main函數中做的那樣:
-(void)try:(id)para1{ @autoreleasepool{ /*do something here*/ }}
你調用後台線程時,也應該這麼做,這兩種方式並沒有實質性的區別。如果你使用的是記憶體回收機制(ARC),那麼建立的自動釋放池會被忽略,但它是無害的。
如果你建立了多個Thread執行個體,你可以通過:
performSelector:onThread:withObject:waitUntilDone:
方法來調用它們執行相應的task。 你還可以對建立的Thread執行個體進行相應的配置。1.1.1 堆棧 你在程式中建立的每一個線程,系統都會在進程空間中分配一部分記憶體空間作為該線程的堆棧,該堆棧管理線程的資料,比如線程中所有的局部變數都將在堆棧裡。你可以調用下列函數來配置和訪問線程堆棧:
- (NSUInteger)stackSize NS_AVAILABLE(10_5, 2_0);- (void)setStackSize:(NSUInteger)s NS_AVAILABLE(10_5, 2_0);+ (NSArray *)callStackReturnAddresses NS_AVAILABLE(10_5, 2_0);+ (NSArray *)callStackSymbols NS_AVAILABLE(10_6, 4_0);
1.1.2 線程的鍵-值字典 每個線程都維護了一個鍵-值字典,該字典中儲存了該線程的一些狀態資訊,字典線上程啟動並執行過程中隨時都可以訪問。你可以使用NSThread的threadDictionary方法來擷取一個NSMutableDictionary對象,並在裡面添加線程需要的索引值對。
- (NSMutableDictionary *)threadDictionary;
1.1.3 線程的運行狀態
配置線程何時運行,何時退出,以及得知線程當前的運行狀態。 讓線程先睡眠一段時間,然後自動運行:
+ (void)sleepUntilDate:(NSDate *)date;+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
取消線程執行,開始線程,退出線程:
- (void)cancel NS_AVAILABLE(10_5, 2_0);- (void)start NS_AVAILABLE(10_5, 2_0);+ (void)exit;
查詢線程的狀態:
- (BOOL)isExecuting NS_AVAILABLE(10_5, 2_0);- (BOOL)isFinished NS_AVAILABLE(10_5, 2_0);- (BOOL)isCancelled NS_AVAILABLE(10_5, 2_0);
1.1.4 優先順序
在預設的情況下,建立的新線程的優先順序與當前線程是相同的。核心調度演算法在決定該運行那個線程時,會把線程的優先順序作為考量因素,較高優先順序的線程會比較低優先順序的線程具有更多的運行機會。你可以通過:
//class method+ (double)threadPriority;+ (BOOL)setThreadPriority:(double)p;//instance method- (double)threadPriority NS_AVAILABLE(10_6, 4_0);- (void)setThreadPriority:(double)p NS_AVAILABLE(10_6, 4_0);
來設定線程的優先順序。
重要:讓你的線程處於預設優先順序值是一個不錯的選擇。增加某些線程的優先順序,同時有可能增加了某些較低優先順序線程的饑餓程度。如果你的應用程式套件組合含較高優先順序和較低優先順序線程,而且它們之間必須互動,那麼較低優先順序的饑餓狀態有可能阻塞其他線程,並造成效能瓶頸。