原文地址:http://d3caifu.com/ebook/MultiThread.htmlphp
隨着計算機/移動設備不斷髮展,提供了愈來愈強大的計算能力和資源,從硬件層面來看首先是CPU的芯片集成度愈來愈高,提供更快的處理能力;其次是多核化多CPU發展;最後剝離一些圖片動畫渲染等到獨立的協處理器處理,硬件的提高促進了操做系統軟件OS的不斷進化,更好的利用硬件能力和資源,提升系統的性能,多線程多任務處理就是最爲關鍵的技術。html
線程分爲主線程和後臺線程2種:一個應用只有惟一的一個主線程,其它線程都是後臺線程。主線程主要負責UI界面更新和響應用戶事件。git
OSX系統提供了多種線程編程相關技術,咱們主要討論NSThread,NSOperationQueue,GCD,Run Loop等相關概念和編程要點。github
傳統的多線程編程技術,爲了並行處理加強性能每每採用手段是建立更多線程去並行處理,但這帶來一個問題:究竟建立多少個線程是合適的最優的?線程的建立準備須要耗費必定的系統內存資源,到必定數量後,性能的提高並不必定是跟線程的多少成線性增加的,這樣對多線程編程提出了很高的要求,如何控制保持合理數量的線程保證性能最優?web
GCD是內核級的多線程併發技術,不須要關注線程建立,只須要把執行的程序單元體檢到GCD的分發隊列便可,GCD線程技術特色:
1)建立線程是系統自動完成,簡化了傳統的編程模式
2)內核級,建立線程不會佔用應用程序的內存空間
2)基於C語言函數級的接口編程,性能優
3)GCD的分發隊列按先進先出的順序執行線程任務sql
GCD的多線程任務都是經過Dispatch Queue來管理,有多種類型的分發隊列:數據庫
1.Serial串行隊列編程
每一個串行隊列,順序執行,先進先出,每次只容許執行一個任務,任務執行完成後才能執行隊列中下一個任務。
能夠建立多個串行隊列,多個串行隊列之間仍然是並行調度。swift
串行隊列的單任務調度特色,使得它特別適合管理保護存在衝突訪問的資源,好比文件讀寫,全局數據的訪問等。安全
除了主線程隊列,任務其它串行隊列都須要代碼手工去建立。
2.Concurrent並行隊列
並行隊列,每次能夠按照順序執行一個或多個任務單元,併發的任務數由系統根據計算資源自動的動態分配控制。
系統默認提供四種不一樣高低優先級的並行隊列,能夠按需獲取。除此以外也能夠經過代碼建立本身的並行隊列。
3.Main dispatch queue主線程隊列
每一個應用中自動生成,惟一的全局的串行隊列,無須手工建立,用於在應用的主線程上執行任務。UI界面元素的數據更新必須在主線程隊列執行。
1.建立串行隊列
//建立私有隊列 dispatch_queue_t queue = dispatch_queue_create("com.yourdomain.TestQueue", NULL); //獲取主線程隊列 dispatch_queue_t queue = dispatch_get_main_queue() ;
2.獲取系統默認的並行隊列
系統默認有4種不一樣優先級隊列,高優先級的隊列優先獲得調度機會,從高到低依次以下:
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 #define DISPATCH_QUEUE_PRIORITY_LOW (-2) #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
3.代碼建立並行隊列
dispatchqueuet queue = dispatchqueuecreate(com.yourdomain.TestQueue
, DISPATCHQUEUECONCURRENT);
有2種方式添加任務對隊列執行,一種是使用dispatchasync異步添加,一種是使用dispatchsync同步添加。
異步添加的任務不會阻塞當前的線程,程序能夠繼續執行添加任務完以後的代碼功能,添加到隊列的任務由GCD後續動態分配調用。
dispatch_async(queue, ^{ NSLog(@"Do some tasks!"); }); NSLog(@"The tasks may or may not have run");
上述代碼的執行結果:
The tasks may or may not have run
Do some tasks!
而同步添加的任務會阻塞當前的程序流程,直到添加的任務執行完成後,才能執行後續的代碼功能。
dispatch_sync(queue, ^{
NSLog(@"Do some work 1 here"); }); dispatch_sync(queue, ^{ NSLog(@"Do some work 2 here"); }); NSLog(@"All works have completed");
上述代碼的執行結果:
Do some work 1 here
Do some work 2 here
All works have completed
某些特定的隊列任務,執行完成後須要將處理結果或狀態通知到主線程或其它隊列作進一步的處理。
能夠對任務定義完成的回調處理任務。
下面的代碼摘自SDWebImage庫,定義了一個回調塊,在_ioQueue中執行文件是否存在的判斷,而後將是否存在的結果經過回調塊completionBlock通知到主線程隊列。
-(void)diskImageExistsWithKey:(NSString *)key completion:(SDWebImageCheckCacheCompletionBlock)completionBlock { dispatch_async(_ioQueue, ^{ BOOL exists = [_fileManager fileExistsAtPath:[self defaultCachePathForKey:key]]; if (completionBlock) { dispatch_async(dispatch_get_main_queue(), ^{ completionBlock(exists); }); } }); }
下面的代碼演示異步任務讀取數據庫,經過回調塊在主線程執行TableView的UI更新。
dispatch_async(self.dbQueue , ^ { self.datas = [self queryDataFromDB]; dispatch_async(dispatch_get_main_queue(),^ { [self.tableView reloadData]; } ); });
將多個任務加入到一個組,當組內全部任務都執行完畢後收到通知。
1.組+隊列控制任務執行
下面的代碼建立了任務組和串行的隊列,同時加入2個任務到組,組內左右任務執行完成後收到通知。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
NSLog(@"Do some work 1 here"); }); dispatch_group_async(group, queue, ^{ NSLog(@"Do some work 2 here"); }); dispatch_group_notify(group, queue, ^{ NSLog(@"group complete"); });
上面代碼執行完的結果:
Do some work 1 here
Do some work 2 here
group complete
組內的任務都是異步執行的,若是須要組內的任務執行完成才能進行後續的流程,能夠增長組的等待函數阻塞當前的流程
dispatch_group_async(group, queue, ^{
NSLog(@"Do some work 1 here"); }); dispatch_group_async(group, queue, ^{ NSLog(@"Do some work 2 here"); }); dispatch_group_notify(group, queue, ^{ NSLog(@"group complete"); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"Do some work 3 here!");
這時代碼執行結果以下,能夠看出只有work1和work2都執行完成,work3纔會獲得執行。
Do some work 1 here
Do some work 2 here
Do some work 3 here!
group complete
2.動態控制組任務
使用dispatch_group_enter和dispatch_group_leave作爲任務開始和結束的標記, 可用脫離dispatch_group_async API的使用而方便的在代碼其它流程加入組的控制。
好比咱們須要在多個網絡請求完成後作一個統一的UI更新操做,可用使用動態控制組的方式去方便的實現。
在每一個網絡請求發起前調用dispatch_group_enter(group),在接收數據完成後調用dispatch_group_leave(group)。
dispatch_group_enter(group); [webAPI sendWithURL:@"http://xxx.path1" block:^( ){ dispatch_group_leave(group) } ]; dispatch_group_enter(group); [webAPI sendWithURL:@"http://xxx.path2" block:^( ){ dispatch_group_leave(group) } ]; dispatch_group_notify(group, queue, ^{ NSLog(@"group complete"); });
當循環處理的任務之間沒有關聯時,即執行順序能夠任意時,可使用GCD提供dispatch_apply方法將循環串行的任務並行化處理。
int count = 10; for (i = 0; i < count; i++) { printf("%u\n",i); }
int count = 10 ; dispatch_apply(count, queue, ^(size_t i) { printf("%u\n",i); });
當容許有限的資源能夠併發使用時,能夠經過信號量控制訪問,當信號量有效時容許訪問(即信號量大於1),不然等待其它線程釋放資源,信號量變成有效時在執行任務。
dispatch_semaphore_t fd_sema = dispatch_semaphore_create(10); dispatch_semaphore_wait(fd_sema, DISPATCH_TIME_FOREVER); int fd = open("/Users/test/file.data", O_RDONLY); close(fd); dispatch_semaphore_signal(fd_sema);
使用dispatch_after延時任務執行。
下面的代碼演示了延時0.5秒執行任務
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), queue, ^{ NSLog(@"after 0.5s timeout , Do some other works!"); });
在併發任務隊列中,可使用dispatch_barrier_async來對任務作分隔保護,dispatch_barrier_async以前加入到隊列的任務執行完成後,才能執行後續加入的任務。barrier英文意思爲障礙物,所以咱們能夠理解爲執行完前面的任務才能越過障礙物執行後續的任務。
dispatch_queue_t queue = dispatch_queue_create("com.yourdomain.TestQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"Do some work 1 here"); }); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"Do some work 2 here"); }); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"Do some work 3 here"); }); dispatch_barrier_async(queue, ^{ NSLog(@"Do some work 4 here"); }); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"Do some work 5 here"); });
上面代碼的執行結果爲:
Do some work 2 here
Do some work 1 here
Do some work 3 here
Do some work 4 here
Do some work 5 here
經過dispatch_suspend函數暫停隊列執行,dispatch_resume恢復隊列執行。
-(void)suspendQueue { if (queue) { dispatch_suspend(queue); } } -(void)resumeQueue { if (queue) { dispatch_resume(queue); } }
1.使用dispatch_once實現單例
```
+ (instancetype)sharedInstance {
static HTTPConfig *instance = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ instance = [[self alloc] init]; }); return instance;
}
```
2.變量訪問控制
讀取方法是用同步調用
寫方法是異步保證性能
HTTPServer.h
@interface HTTPServer : NSObject { NSString *interface; } -(NSString *)interface; -(void)setInterface:(NSString *)value; HTTPServer.m -(NSString *)interface { //定義臨時變量 增長引用計數 防止對象被釋放 __block NSString *result; dispatch_sync(serverQueue, ^{ result = interface; }); return result; } -(void)setInterface:(NSString *)value { NSString *valueCopy = [value copy]; dispatch_async(serverQueue, ^{ interface = valueCopy; }); }
上面的讀取方案若是存在隊列嵌套調用的話會產生死鎖,interface的get方法能夠優化以下:
#import "HTTPConfig.h" static const void * const kHTTPQueueSpecificKey = &kHTTPQueueSpecificKey; @interface HTTPConfig () { NSString *interface; dispatch_queue_t serverQueue; } @end @implementation HTTPConfig -(instancetype)init { self = [super init]; if(self) { serverQueue = dispatch_queue_create("com.uu.queue", NULL); dispatch_queue_set_specific(serverQueue, kHTTPQueueSpecificKey, (__bridge void *)self, NULL); } return self; } -(NSString *)interface { id currentObj = (__bridge id)dispatch_get_specific(kHTTPQueueSpecificKey); if(currentObj){ return interface; } //定義臨時變量 增長引用計數 防止對象被釋放 __block NSString *result; dispatch_sync(serverQueue, ^{ result = interface; }); return result; } -(void)setInterface:(NSString *)value { NSString *valueCopy = [value copy]; dispatch_async(serverQueue, ^{ interface = valueCopy; }); } @end
3.FMDB數據庫中使用
開源的FMDB中提供了FMDatabaseQueue類,採用了GCD的Queue來保證線程安全。
FMDatabaseQueue類的實現代碼
-(instancetype)initWithPath:(NSString*)aPath flags:(int)openFlags vfs:(NSString *)vfsName { self = [super init]; if (self != nil) { _db = [[[self class] databaseClass] databaseWithPath:aPath]; FMDBRetain(_db); #if SQLITE_VERSION_NUMBER >= 3005000 BOOL success = [_db openWithFlags:openFlags vfs:vfsName]; #else BOOL success = [_db open]; #endif if (!success) { NSLog(@"Could not create database queue for path %@", aPath); FMDBRelease(self); return 0x00; } _path = FMDBReturnRetained(aPath); _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL); _openFlags = openFlags; } return self; } -(void)inDatabase:(void (^)(FMDatabase *db))block { /* Get the currently executing queue (which should probably be nil, but in theory could be another DB queue * and then check it against self to make sure we're not about to deadlock. */ FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock"); FMDBRetain(self); dispatch_sync(_queue, ^() { FMDatabaseQueue *currentSyncQueue2 = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); FMDatabase *db = [self database]; block(db); if ([db hasOpenResultSets]) { NSLog(@"Warning: there is at least one open result set around after performing [FMDatabaseQueue inDatabase:]"); } }); FMDBRelease(self); }
FMDB數據庫具體使用:全部的數據庫操做都在一個串行隊列中順序執行,防止了衝突,保證了數據讀寫的一致性。
FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath]; [queue inDatabase:^(FMDatabase *db) { FMResultSet *rs = [db executeQuery:@"select * from Track"]; while ([rs next]) { NSLog(@"%@",[rs resultDictionary]); } }];
NSOperationQueue是基於Objective-C封裝的異步對象操做隊列,提供了更爲靈活強大的功能:
1) 任務定義能夠基於NSOperation,NSInvocationOperation,NSBlockOperation不一樣的方式;
2)隊列但是可管理的相對於GCD,能夠取消隊列中的任務;
3)併發的任務數能夠經過maxConcurrentOperationCount控制。
全部的任務操做首先要封裝成NSOperation的子類,加入隊列執行。
下面列出NSOperationQueue頭文件中定義的屬性和方法,經過這些方法咱們能夠對NSOperationQueue,對它提供的特性/功能/使用有一個基本的瞭解。
@interface NSOperationQueue : NSObject //增長操做任務到隊列 -(void)addOperation:(NSOperation *)op; //增長多個操做任務到隊列 -(void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; //增長基於Block塊定義的操做任務到隊列 -(void)addOperationWithBlock:(void (^)(void))block ; //全部的操做任務 @property (readonly, copy) NSArray *operations; //操做任務個數 @property (readonly) NSUInteger operationCount ; //最大容許的併發數,設置爲1時等價於GCD的串行隊列,大於1至關爲併發隊列 @property NSInteger maxConcurrentOperationCount; //隊列掛起狀態控制 @property (getter=isSuspended) BOOL suspended; //隊列名稱 @property (nullable, copy) NSString *name ; //對列服務質量 @property NSQualityOfService qualityOfService; //隊列對應的底層GCD的隊列,從這裏能夠印證NSOperationQueue在GCD的基礎上作了封裝 @property ( assign ) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0); //取消全部的任務 -(void)cancelAllOperations; //等待全部任務完成 -(void)waitUntilAllOperationsAreFinished; //獲取當前正在執行任務的隊列 +(NSOperationQueue *)currentQueue ; //獲取當前的主線程隊列 +(NSOperationQueue *)mainQueue ; @end
NSInvocationOperation將目標對象,執行的方法和相關參數封裝成Operation對象,加入隊列併發執行。
-(void)invocationOperationTest { NSOperationQueue *aQueue = [[NSOperationQueue alloc] init]; NSString *data = @"invocation paras"; NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(doWork:) object:data]; [aQueue addOperation:theOp]; } -(void)doWork:(NSString*)data { NSLog(@"doWork show data %@ ",data ); }
NSBlockOperation能夠將一個或多個代碼片斷封裝成Block對象加入到隊列,異步的併發執行。當有多個多個代碼片斷加入到NSBlockOperation中時,它至關於GCD的Groups分組控制。
NSBlockOperation * opt = [[NSBlockOperation alloc] init];
[opt addExecutionBlock:^{
NSLog(@"Run in block 1 "); }]; [opt addExecutionBlock:^{ NSLog(@"Run in block 2 " ); }]; [opt addExecutionBlock:^{ NSLog(@"Run in block 3 " ); }]; [opt addExecutionBlock:^{ NSLog(@"Run in block 4 " ); }]; [[NSOperationQueue currentQueue] addOperation:opt];
上面代碼的執行以下,能夠看出這些Block塊之間是異步併發執行的。
Run in block 2
Run in block 1
Run in block 3
Run in block 4
NSOperation是一個抽象類,不行直接使用,必須之類化使用。前面介紹的NSInvocationOperation,NSBlockOperation都是NSOperation的子類。當它們不能知足咱們須要時能夠定義NSOperation的子類來實現多線程任務。
下面是NSOperation子類實現線程操做任務時的幾個關鍵方法:
//表示是否容許併發執行
-(BOOL)isAsynchronous;
//KVO屬性方法,表示任務執行狀態
-(BOOL)isExecuting;
//KVO屬性方法,表示任務是否執行完成
-(BOOL)isFinished;
//任務啓動前的setup方法,知足執行條件時,啓動線程執行main方法
-(void)start;
//實現具體的任務邏輯
-(void)main;
下面例子定義了HTTPImageOperation類,用來異步下載網絡圖片。
HTTPImageOperation.h的定義
@interface HTTPImageOperation : NSOperation -(instancetype)initWithImageURL:(NSURL*)url; @property (nonatomic, copy) void (^downCompletionBlock)(NSImage *image); @end
HTTPImageOperation.m的實現
#import "HTTPImageOperation.h" @interface HTTPImageOperation () { BOOL executing; BOOL finished; } @property(nonatomic,strong)NSURL *url; @end @implementation HTTPImageOperation -(instancetype)initWithImageURL:(NSURL*)url { self = [super init]; if(self) { _url = url; } return self; } //表示是否容許併發執行 -(BOOL)isAsynchronous { return YES; } -(BOOL)isExecuting { return executing; } -(BOOL)isFinished { return finished; } -(void)start { if([self isCancelled]){ [self willChangeValueForKey:@"isFinished"]; finished = YES; [self didChangeValueForKey:@"isFinished"]; return; } [self willChangeValueForKey:@"isExecuting"]; [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil]; executing = YES; [self didChangeValueForKey:@"isExecuting"]; } -(void)main { @try { NSImage *image = [[NSImage alloc]initWithContentsOfURL:self.url]; if(self.downCompletionBlock){ self.downCompletionBlock(image); } [self completeOperation]; } @catch(...) { [self completeOperation]; } } -(void)completeOperation { [self willChangeValueForKey:@"isFinished"]; [self willChangeValueForKey:@"isExecuting"]; executing = NO; finished = YES; [self didChangeValueForKey:@"isExecuting"]; [self didChangeValueForKey:@"isFinished"]; } @end
HTTPImageOperation的使用:下載2個網絡圖片,而且顯示在2個ImageView中。
NSURL *url1 = [NSURL URLWithString:@"http://www.jobbole.com/wp-content/uploads/2016/01/75c6c64ae288896908d2c0dcd16f8d65.jpg"]; HTTPImageOperation *op1 = [[HTTPImageOperation alloc]initWithImageURL:url1]; op1.downCompletionBlock = ^(NSImage *image){ if(image){ self.leftImageView.image = image; } }; NSURL *url2 = [NSURL URLWithString:@"http://ww1.sinaimg.cn/mw690/bfdcef89gw1exedm1rzkpj20j602d757.jpg"]; HTTPImageOperation *op2 = [[HTTPImageOperation alloc]initWithImageURL:url2]; op2.downCompletionBlock = ^(NSImage *image){ if(image){ self.rightImageView.image = image; } }; [[NSOperationQueue mainQueue] addOperation:op1]; [[NSOperationQueue mainQueue] addOperation:op2];
可使用addDependency:方法在2個或多個任務間設置依賴關係,當操做任務的依賴的任務所有執行完成後,任務才能獲得執行。
下面的例子中只有opt1任務執行後,opt2才能獲得執行。
NSOperationQueue *aQueue = [[NSOperationQueue alloc] init]; NSBlockOperation * opt1 = [[NSBlockOperation alloc] init]; [opt1 addExecutionBlock:^{ NSLog(@"Run in block 1 "); }]; NSBlockOperation * opt2 = [[NSBlockOperation alloc] init]; [opt2 addExecutionBlock:^{ NSLog(@"Run in block 2 "); }]; [opt2 addDependency:opt1]; [aQueue addOperation:opt1]; [aQueue addOperation:opt2];
NSOperation的completionBlock屬性,用來作爲任務執行完成後的回調定義,前面介紹的NSBlockOperation例子中,能夠設置completionBlock作爲全部任務快執行完成的回調通知。
opt.completionBlock = ^{ NSLog(@"Run completion " ); };
能夠取消單個任務,也能夠取消隊列中所有未執行的任務。
// 取消單個操做 [operation cancel]; // 取消queue中全部的操做 [queue cancelAllOperations];
修改隊列的suspended屬性來控制隊列的暫停或恢復執行。
//暫停執行 [queue setSuspended:YES]; //恢復執行 [queue setSuspended:NO];
NSOperation能夠經過queuePriority屬性設置5個不一樣等級的優先級,在同一個隊列中優先級越高的任務最早獲得執行。要注意的是有依賴關係的任務仍是按依賴關係執行任務,不受優先級的影響。
下面例子中opt3任務優先獲得執行。
NSOperationQueue *aQueue = [[NSOperationQueue alloc] init]; NSBlockOperation * opt1 = [[NSBlockOperation alloc] init]; [opt1 addExecutionBlock:^{ NSLog(@"Run in block 1 "); }]; NSBlockOperation * opt2 = [[NSBlockOperation alloc] init]; [opt2 addExecutionBlock:^{ NSLog(@"Run in block 2 "); }]; NSBlockOperation * opt3 = [[NSBlockOperation alloc] init]; opt3.queuePriority = NSOperationQueuePriorityVeryHigh; [opt3 addExecutionBlock:^{ NSLog(@"Run in block 3 "); }]; [aQueue addOperations:@[opt1,opt2,opt3] waitUntilFinished:NO];
NSThread是傳統意義上底層pthread線程的OC封裝,提供更多的靈活性
1)設置線程的服務質量Qos
2)能夠設置線程堆棧大小
2)線程提供local數據字典:能夠存儲key/value數據
相對於NSOperation ,GCD更高級的多線程技術,NSThread也有本身獨特的優點:
1)實時性更高
2)與RunLoop結合,提供了更爲靈活高效的線程管理方式
NSThread的缺點:建立線程代價較大,須要同時佔用應用和內核的內存空間(GCD的線程只佔用內核的內存空間);編寫線程相關的代碼相對繁雜。
1.經過target-selector方式:是比較簡單方便的線程建立方法,直接將某個對象的方法和須要的參數包裝爲線程的執行方法
下面是代碼示例,建立新的獨立線程,執行當前類的threadExecuteMethod方法。
[NSThread detachNewThreadSelector:@selector(threadExecuteMethod:) toTarget:self withObject:nil];
2.直接使用NSThread建立線程
這個方式比較靈活,能夠在線程啓動前設置一些參數,好比線程名稱,優先級,堆棧大小等。
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadExecuteMethod:) object:nil]; thread.name = @"Thread1"; [thread start];
3.對NSThread子類化
對一些處理加工性耗時的操做,能夠獨立出來,封裝成NSThread的子類進行獨立處理。處理完成的結果能夠以通知或代理的方法通知到主線程。
經過重載main方法實現子類化,注意在main中要加上@autoreleasepool 自動釋放池確保線程處理過程當中及時釋放內存資源。
// // WorkThread.h // NSQueueDemo // // Created by zhaojw on 1/21/16. // Copyright © 2016 MacDev.io. All rights reserved. // #import <Foundation/Foundation.h> @interface WorkThread : NSThread --((instancetype)initWithImageURL:(NSURL*)url; @end #import "WorkThread.h" @interface WorkThread () @property(nonatomic,strong)NSURL *url; @end @implementation WorkThread -(instancetype)initWithImageURL:(NSURL*)url { self = [super init]; if(self){ _url = url; } return self; } -(void)main { NSLog(@"WorkThread main"); @autoreleasepool { NSImage *image = [[NSImage alloc]initWithContentsOfURL:_url]; //Do some other image process work } } @end
獲取當前線程和主線程相關方法:
//獲取當前運行的線程 +(NSThread *)currentThread; //是否支持多線程 +(BOOL)isMultiThreaded; //是不是主線程的屬性 @property (readonly) BOOL isMainThread ; //是不是主線程 +(BOOL)isMainThread; //獲取主線程 +(NSThread *)mainThread ;
線程的配置參數
//線程的local數據字典 @property (readonly, retain) NSMutableDictionary *threadDictionary; //線程優先級 +(double)threadPriority; //修改優先級 +(BOOL)setThreadPriority:(double)p; //線程服務質量Qos @property NSQualityOfService qualityOfService; //線程名稱 @property (copy) NSString *name ; //堆棧大小 @property NSUInteger stackSize ;
線程的調試接口 獲取當前線程調用鏈堆棧
//堆棧返回地址 +(NSArray<NSNumber *> *)callStackReturnAddresses ; //堆棧調用鏈 +(NSArray<NSString *> *)callStackSymbols ;
線程的執行取消完成狀態:
//是否正在執行 @property (readonly, getter=isExecuting) BOOL executing ; //是否完成 @property (readonly, getter=isFinished) BOOL finished ; //是否取消 @property (readonly, getter=isCancelled) BOOL cancelled;
線程的控制方法:
//取消執行 -(void)cancel; //啓動執行 -(void)start; //線程執行的主方法,子類化線程實現這個方法便可 -(void)main; //休眠到指定日期 +(void)sleepUntilDate:(NSDate *)date; //按期休眠 +(void)sleepForTimeInterval:(NSTimeInterval)ti; //退出線程 +(void)exit;
在主線程執行的方法
aSelector:方法
thr:線程
arg:方法的參數
wait:是否等待執行完成
modes:runloop的模式
-(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:( NSArray *)array; -(void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
在指定的線程上執行方法
-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array; -(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
後臺線程執行的方法
-(void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;
1.OSAtomic原子操做
對基本的簡單數據類型提供原子操做的函數,包括數學加減運算和邏輯操做。相比較加鎖保護資源的方式,原子操做更輕量級,性能更高。
``` int32_t theValue1 = 0; int32_t theValue2 = 0; OSAtomicIncrement32(&theValue1); OSAtomicDecrement32(&theValue2); ```
2.加鎖
1)NSLock:互斥鎖
下面的代碼展現了最簡單的鎖的使用。
tryLock方法嘗試獲取鎖,若是沒有可用的鎖,函數返回NO,不會阻塞當前任務執行。
NSLock *theLock = [[NSLock alloc] init];
if ([theLock tryLock]) {
//Do some work [theLock unlock]; }
2)NSRecursiveLock:遞歸鎖主要用在循環或遞歸操做中,保證了同一個線程執行屢次加鎖操做不會產生死鎖;只要保證加鎖解鎖次數相同便可釋放資源使其它線程獲得資源使用。
使用場景:同一個類中多個方法,遞歸操做,循環處理中對受保護的資源的訪問
NSRecursiveLock *theLock = [[NSRecursiveLock alloc] init];
for(int i=0;i<10;i++){ [theLock lock]; //Do some work [theLock unlock]; }
3)NSConditionLock:條件鎖
condition爲一個整數參數,知足condition條件時得到鎖,解鎖時能夠設置condition條件。
lock,lockWhenCondition與unlock,unlockWithCondition能夠任意組合使用。unlockWithCondition表示解鎖而且設置condition值;lockWhenCondition表示condition爲參數值時得到鎖。
#import "NSConditionLockTest.h" @interface NSConditionLockTest () @property(nonatomic,strong)NSMutableArray *queue; @property(nonatomic,strong)NSConditionLock *condition; @end @implementation NSConditionLockTest -(void)doWork1 { NSLog(@"doWork1 Begin"); while(true) { sleep(1); [self.condition lock]; NSLog(@"doWork1 "); [self.queue addObject:@"A1"]; [self.queue addObject:@"A2"]; [self.condition unlockWithCondition:2]; } NSLog(@"doWork1 End"); } -(void)doWork2 { NSLog(@"doWork2 Begin"); while(true) { sleep(1); [self.condition lockWhenCondition:2]; NSLog(@"doWork2 "); [self.queue removeAllObjects]; [self.condition unlock]; } NSLog(@"doWork2 End"); } -(void)doWork { [self performSelectorInBackground:@selector(doWork1) withObject:nil ]; [self performSelector:@selector(doWork2) withObject:nil afterDelay:0.1]; } -(NSMutableArray *)queue { if(!_queue){ _queue = [[NSMutableArray alloc]init]; } return _queue; } -(NSConditionLock*)condition { if(!_condition) { _condition = [[NSConditionLock alloc]init]; } return _condition; } @end
3.NSCondition
Condition經過一些條件控制來多個線程協做完成任務。當條件不知足時線程等待;條件知足時經過發送signal信號來通知等待的線程繼續處理。
#import "NSConditionTest.h" @interface NSConditionTest () @property(nonatomic,assign)BOOL completed; @property(nonatomic,strong)NSCondition *condition; @end @implementation NSConditionTest -(void)clearCondition{ self.completed = NO; } -(void)doWork1{ NSLog(@"doWork1 Begin"); [self.condition lock]; while (!self.completed) { [self.condition wait]; } NSLog(@"doWork1 End"); [self.condition unlock]; } -(void)doWork2{ NSLog(@"doWork2 Begin"); //do some work [self.condition lock]; self.completed = YES; [self.condition signal]; [self.condition unlock]; NSLog(@"doWork2 End"); } -(void)doWork { [self performSelectorInBackground:@selector(doWork1) withObject:nil ]; [self performSelector:@selector(doWork2) withObject:nil afterDelay:0.1]; } -(NSCondition*)condition { if(!_condition){ _condition = [[NSCondition alloc]init]; } return _condition; } @end
執行doWork方法後輸出以下:
doWork1 Begin
doWork2 Begin
doWork2 End
doWork1 End
4.@synchronized同步指令
synchronized是自動實現的加鎖技術,同時增長了異常處理。通俗的講就是編譯器自動插入加鎖和解鎖的代碼,同時捕獲異常,避免異常時不及時釋放鎖致使死鎖。
下面是摘自 Countly 中使用synchronized指令的句子。
-(void)recordEvent:(NSString *)key count:(int)count { @synchronized (self) { NSArray* events = [[[CountlyDB sharedInstance] getEvents] copy]; for (NSManagedObject* obj in events) { CountlyEvent *event = [CountlyEvent objectWithManagedObject:obj]; if ([event.key isEqualToString:key]) { event.count += count; event.timestamp = (event.timestamp + time(NULL)) / 2; [obj setValue:@(event.count) forKey:@"count"]; [obj setValue:@(event.timestamp) forKey:@"timestamp"]; [[CountlyDB sharedInstance] saveContext]; return; } } CountlyEvent *event = [CountlyEvent new]; event.key = key; event.count = count; event.timestamp = time(NULL); [[CountlyDB sharedInstance] createEvent:event.key count:event.count sum:event.sum segmentation:event.segmentation timestamp:event.timestamp]; } }
線程是任務分解成不一樣的工做單元分配給線程去執行,解決了多任務併發執行的問題,提升了系統性能。在現代交互式系統中,還存在大量的未知不肯定的異步事件,這時候線程是一直是出於等待狀態的,直到有事件發生纔會喚醒線程去執行,執行完成後系統又恢復到之前的等待狀態。如何控制線程在等待和執行任務狀態間無縫切換,就引入了RunLoop的概念。
RunLoop稱爲事件循環,能夠理解爲系統中對各類事件源不間斷的循環的處理。應用在運行過程當中會產生大量的系統和用戶事件,包括定時器事件,用戶交互事件(鼠標鍵盤觸控板操做),模態窗口事件,各類系統Source事件,應用自定義的Source事件等等,每種事件都會存儲到不一樣的FIFO先進先去的隊列,等待事件循環依次處理。
被RunLoop管理的線程在掛起時,不會佔用系統的CPU資源,能夠說RunLoop是很是高效的線程管理技術。
線程和RunLoop是一一對應,系統會將線程和它的RunLoop對象實例以key/value字典形式存儲到全局字典中統一管理和訪問。獲取線程的RunLoop時,若是字典中不存在或新建一個並將這個RunLoop存儲到字典。
每一個應用啓動後系統默認生成一個RunLoop對象,也能夠稱爲主線程的RunLoop,由它完成主線程運行期間各類事件調度控制。
RunLoop中包括3大核心組件,定時器,輸入源Input Sources和觀察者Observer,後面會逐一介紹。
Modes是一組事件類型的集合,每一個事件是註冊關聯到一個或多個Mode中,RunLoop在每一個時刻運行在一個特定的模式。
RunLoop在運行時,只處理註冊到當前Mode模式下的事件和通知模式相關的觀察者,當前模式運行期發生的其它模式的事件不會被處理,只能被存儲到消息隊列等到RunLoop下一次切換到對應的Mode時才能處理。
舉個簡單的例子假如一個定時器註冊到Default缺省模式下,若是當前發生了高優先級的系統事件Touch點擊事件RunLoop會切換到NSEventTrackingRunLoopMode模式處理,若是定時器到時timeout的事件正好發生就不會處理了。
註冊模式的原則:若是不是高優先級須要實時處理的事件,能夠採用默認模式。若是RunLoop運行在任何模式都須要處理這個事件就註冊在NSRunLoopCommonModes/kCFRunLoopCommonModes Common模式。
Cocoa中使用NSRunLoop,CoreFoundation中使用CFRunLoopRef來管理RunLoop對象。
NSRunLoop不是線程安全的,不能跨線程使用;CFRunLoopRef是線程安全的。
1.獲取當前線程的RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; 或 CFRunLoopRef runLoop = CFRunLoopGetCurrent() ;
2.運行RunLoop
//無條件運行 -(void)run; //運行到指定時間爲止 -(void)runUntilDate:(NSDate *)limitDate; //在指定模式下,在指定的時間點以前一直運行 -(BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
3.中止RunLoop
手工使用CFRunLoopStop來中止RunLoop或者運行前指定一個時間,到期後RunLoop自動中止
CFRunLoopStop(CFRunLoopGetCurrent());
CFRunLoopActivity定義了RunLoop在運行中不一樣的活動狀態,這些狀態能夠經過觀察者Observer跟蹤。
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), 開始進入runloop kCFRunLoopBeforeTimers = (1UL << 1), 定時器即將到時 kCFRunLoopBeforeSources = (1UL << 2),Source源事件即將觸發 kCFRunLoopBeforeWaiting = (1UL << 5),即將進入睡眠 kCFRunLoopAfterWaiting = (1UL << 6),即將喚醒 kCFRunLoopExit = (1UL << 7),退出 kCFRunLoopAllActivities = 0x0FFFFFFFU };
註冊RunLoop的觀察者Observer,能夠只註冊某個狀態,也能夠註冊所有的狀態,能夠靈活按位邏輯OR來根據需求組合。
CFRunLoopAddObserver函數第一個入參位RunLoop對象,下面的例子中是獲取主線程的RunLoop
第三個參數爲Modes,能夠根據須要指定
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { NSRunLoop *loop = [NSRunLoop currentRunLoop]; NSLog(@"mode %@ activity %ld",[loop currentMode],activity); } CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL}; observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context); CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
1.NSTimer
建立NSTimer,將其關聯到當前線程RunLoop的Mode。設置爲Common模式相對與默認模式,能夠防止其它用戶高優先級Mode事件影響定時器的運行。
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(startTimerAction:) userInfo:nil repeats:YES]; // 將定時器添加到NSRunLoopCommonModes類型的定時器中去,防止用戶其它操做時,定時器不執行 [[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes]; -(void)startTimerAction:(NSTimer *)timer { if (timeCount == 0) { return } int timeCount = self.timeLabel.text.intValue; self.timeLabel.text = [NSString stringWithFormat:@"%d", --timeCount]; }
2.GCD的Timer
經過GCD方式建立的timer不受runloop的影響。iOS平臺能夠經過UITextView內初始化一段文字,在定時器倒計時顯示數字Lable期間,快速滑動文字內容測試這個結論,能夠發現GCD的Timer不受滑動事件的影響。而經過NSTimer建立的timer若是加入RunLoop指定爲缺省的Mode快速滑動時定時器倒計時就會中止。
self.queue = dispatch_get_main_queue(); self.timer2 = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.queue); // 每隔2秒執行一次 uint64_t interval = (uint64_t)(2.0 * NSEC_PER_SEC); dispatch_source_set_timer(self.timer2, 0, interval, 0); dispatch_source_set_event_handler(self.timer2, ^(){ int timeCount = self.timeLabel2.text.intValue; if(timeCount==0){ dispatch_cancel(self.timer2); return ; } dispatch_async(dispatch_get_main_queue(), ^ int timeCount = self.timeLabel2.text.intValue; timeCount = timeCount-1; NSLog(@"timeCount%d",timeCount); self.timeLabel2.text = [NSString stringWithFormat:@"%d",timeCount]; }) } ); dispatch_resume(self.timer2);
RunLoop中有3種Source:
1)基於Port的Source,基於Port的Source是Cocoa系統內部2個線程間相似TCP/IP 以端口方式通信的一種機制。
2)Perform Selector Sources
在指定的線程執行Perform Selector 方法,是線程間通信的重要方式。Perform Selector 方法會被順序存儲到線程隊列依次執行。未被執行的Selector方法能夠取消執行。
@interface NSObject (NSDelayedPerforming) -(void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes; -(void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay; + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument; + (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget; @end @interface NSRunLoop (NSOrderedPerform) -(void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes; -(void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(id)arg; -(void)cancelPerformSelectorsWithTarget:(id)target; @end
3)用戶自定義的Source
這裏咱們主要描述用戶自定義Source處理過程和實現方法。
1.自定義Source源
主要定義3個回調函數,將Source添加到線程的runLoop,最後就是等待Signal信號觸發事件喚醒runLoop,最終執行回調函數RunLoopSourcePerformRoutine中的處理方法。
@class XXXRunLoopInputSource; @protocol XXXRunLoopInputSourceDelegate <NSObject> @optional -(void)source:(XXXRunLoopInputSource*)source command:(NSInteger)command; @end @interface XXXRunLoopInputSource : NSObject { CFRunLoopSourceRef _runLoopSource; NSMutableArray* _commands; } @property(weak) id <XXXRunLoopInputSourceDelegate> delegate; //增長source到runloop -(void)addToCurrentRunLoop; //刪除source -(void)invalidate; //接收到source事件 -(void)sourceFired; //提供給外部的command操做接口 -(void)addCommand:(NSInteger)command withData:(id)data; //command喚醒runloop -(void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop; @end //source 和 runloop關聯的上下文對象 @interface XXXRunLoopContext : NSObject @property (nonatomic,assign) CFRunLoopRef runLoop; @property (nonatomic,strong) XXXRunLoopInputSource* source; -(instancetype)initWithSource:(XXXRunLoopInputSource *)runLoopInputSource runLoop:(CFRunLoopRef)runLoop; @end
#import "AppDelegate.h" #import "XXXRunLoopInputSource.h" //註冊source的回調 void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode) { XXXRunLoopInputSource* obj = (__bridge XXXRunLoopInputSource *)info; AppDelegate* del = [AppDelegate sharedAppDelegate]; XXXRunLoopContext* theContext = [[XXXRunLoopContext alloc] initWithSource:obj runLoop:rl]; [del performSelectorOnMainThread:@selector(registerSource:) withObject:theContext waitUntilDone:NO]; } //source喚醒runloop後的回調 void RunLoopSourcePerformRoutine (void *info) { XXXRunLoopInputSource* obj = (__bridge XXXRunLoopInputSource*)info; [obj sourceFired]; } //刪除source回調 void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode) { XXXRunLoopInputSource* obj = (__bridge XXXRunLoopInputSource*)info; AppDelegate* del = [AppDelegate sharedAppDelegate]; XXXRunLoopContext* theContext = [[XXXRunLoopContext alloc] initWithSource:obj runLoop:rl]; [del performSelectorOnMainThread:@selector(removeSource:) withObject:theContext waitUntilDone:YES]; } @implementation XXXRunLoopInputSource -(id)init { self = [super init]; if(self) { //初始化source上下文,註冊3個回調函數 CFRunLoopSourceContext context = {0, (__bridge void *)(self), NULL, NULL, NULL, NULL, NULL, &RunLoopSourceScheduleRoutine, RunLoopSourceCancelRoutine, RunLoopSourcePerformRoutine}; //建立source _runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context); _commands = [[NSMutableArray alloc] init]; } return self; } -(void)addToCurrentRunLoop { CFRunLoopRef runLoop = CFRunLoopGetCurrent(); //source加入到runloop 而且設置爲缺省Mode CFRunLoopAddSource(runLoop, _runLoopSource, kCFRunLoopDefaultMode); } -(void)invalidate { CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFRunLoopRemoveSource(runLoop, _runLoopSource, kCFRunLoopDefaultMode); } -(void)sourceFired { NSLog(@"sourceFired"); if ([self.delegate respondsToSelector:@selector(source:command:)]) { if([_commands count]>0){ NSInteger command = [_commands[0] integerValue]; [self.delegate source:self command:command]; [_commands removeLastObject]; } } } -(void)addCommand:(NSInteger)command withData:(id)data { [_commands addObject:@(command)]; } //喚醒休眠的runloop -(void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop { CFRunLoopSourceSignal(_runLoopSource); CFRunLoopWakeUp(runloop); } @end @implementation XXXRunLoopContext -(instancetype)initWithSource:(XXXRunLoopInputSource *)runLoopInputSource runLoop:(CFRunLoopRef)runLoop { self = [super init]; if (self) { _source = runLoopInputSource; _runLoop = runLoop; } return self; } @end
2.Source對應的處理線程
線程建立Source事件源並將其綁定到當前的runLoop,while循環中執行運行RunLoop到超時後退出當前RunLoop。因爲沒有註冊到RunLoop的事件發生,線程被掛起休眠等待事件觸發。
#import "XXXRunLoopInputSourceThread.h" #import "XXXRunLoopInputSource.h" @interface XXXRunLoopInputSourceThread ()<XXXRunLoopInputSourceDelegate> @property(nonatomic,strong)XXXRunLoopInputSource *source; @end @implementation XXXRunLoopInputSourceThread -(void)main { @autoreleasepool { NSLog(@"XXXRunLoopInputSourceThread Enter"); //獲取線程的runloop NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; self.source = [[XXXRunLoopInputSource alloc] init]; self.source.delegate = self; //增建source並將其加入到runloop [self.source addToCurrentRunLoop]; while (!self.cancelled) { NSLog(@"Enter Run Loop"); [self doOtherWork]; [currentRunLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; NSLog(@"Exit Run Loop"); } NSLog(@"XXXRunLoopInputSourceThread Exit"); } } -(void)doOtherWork { NSLog(@"Begin Do OtherWork"); NSLog(@"-------------------"); NSLog(@"End Do OtherWork"); } #pragma mark- XXXRunLoopInputSourceDelegate -(void)source:(XXXRunLoopInputSource*)source command:(NSInteger)command { NSLog(@"command =%ld ",command); } @end
3.AppDelegate
啓動線程後,創建Source,Source創建的回調通知AppDelegate當前正在註冊source,AppDelegate將其保存在屬性sources中。
UI界面建立按鈕綁定事件到fireInputSource方法,用戶點擊按鈕當即觸發一次自定義的source事件。
#import "XXXRunLoopInputSource.h" @interface AppDelegate : NSObject <NSApplicationDelegate> + (AppDelegate*)sharedAppDelegate; @end @interface AppDelegate (RunLoop) -(void)registerSource:(XXXRunLoopContext *)sourceContext; -(void)removeSource:(XXXRunLoopContext *)sourceContext; -(void)simulateInputSourceEvent; @end
#import "AppDelegate.h" #import "XXXRunLoopInputSourceThread.h" @interface AppDelegate (){ } @property(nonatomic,strong)NSMutableArray *sources; @property (weak) IBOutlet NSWindow *window; @end @implementation AppDelegate -(void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Insert code here to initialize your application [self startInputSourceRunLoopThread]; } -(void)applicationWillTerminate:(NSNotification *)aNotification { // Insert code here to tear down your application } -(void)startInputSourceRunLoopThread { XXXRunLoopInputSourceThread *thread = [[XXXRunLoopInputSourceThread alloc] init]; [thread start]; } -(IBAction)fireInputSource:(id)sender { [self simulateInputSourceEvent]; } +(AppDelegate*)sharedAppDelegate { return [NSApplication sharedApplication].delegate; } @end @implementation AppDelegate (RunLoop) -(void)registerSource:(XXXRunLoopContext *)sourceContext { if (!self.sources) { self.sources = [NSMutableArray array]; } [self.sources addObject:sourceContext]; } -(void)removeSource:(XXXRunLoopContext *)sourceContext { [self.sources enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { XXXRunLoopContext *context = obj; if ([context isEqual:sourceContext]) { [self.sources removeObject:context]; *stop = YES; } }]; } -(void)simulateInputSourceEvent { XXXRunLoopContext *runLoopContext = [self.sources objectAtIndex:0]; XXXRunLoopInputSource *inputSource = runLoopContext.source; NSInteger command = random() % 100; [inputSource addCommand:command withData:nil]; [inputSource fireAllCommandsOnRunLoop:runLoopContext.runLoop]; } @end
1.通知觀察者即將進入runloop處理
2.若是存在即將發生的定時器事件,通知全部的觀察者。
3.若是存在即將發生的非port的source事件,在事件發生前,通知全部的觀察者。
4.若是存在即將發生的非port的source事件,在事件發生後,通知全部的觀察者。
5.若是存在基於port的事件等待處理,當即處理轉9
6.通知觀察者,線程即將休眠
7.線程休眠一直等到下面任意事件之一發生:
1)基於port的事件發生
2)定時器超時
3)runloop設置的超時時間到期
4)顯式的喚醒runloop
8.通知觀察者,線程即將被喚醒
9.處理等待的事件
1)若是是定時器事件,執行定時器處理函數從新start runloop, 轉2
2)若是是用戶定義的source 執行對應的事件處理方法
3)若是runloop被顯式的喚醒而且沒有超時,從新start runloop, 轉2
1.定時器事件
建立定時器,將其加入指定的模式,運行在缺省的模式下的定時器會受到用戶交互事件的影響而延遲執行。
2.多個線程間經過Perform Seletor方法通信
3.input source事件觸發的任務
GCD跟RunLoop沒有直接的關係,主線程自動綁定到一個RunLoop,因此推出一個結論:GCD中提交到主線程隊列的會在主線程RunLoop 循環週期內調用。
1.SDWebImage
start方法中建立connection,運行RunLoop
-(void)start { @synchronized (self) { if (self.isCancelled) { self.finished = YES; [self reset]; return; } self.executing = YES; self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; self.thread = [NSThread currentThread]; } [self.connection start]; if (self.connection) { if (self.progressBlock) { self.progressBlock(0, NSURLResponseUnknownLength); } dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self]; }); if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false); } else { CFRunLoopRun(); } 。。。 }
connection完成方法中CFRunLoopStop 中止RunLoop
-(void)connectionDidFinishLoading:(NSURLConnection *)aConnection { SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock; @synchronized(self) { CFRunLoopStop(CFRunLoopGetCurrent()); self.thread = nil; self.connection = nil; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil]; }); } 。。。 }
2.AFNetwork
建立了NSMachPort對象,加入到當前runLoop。runLoop中若是存在port對象就不會退出,一直循環執行。
+(void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } }
將connection,outputStream對象加入到runLoop的模式(Common)運行
-(void)operationDidStart { [self.lock lock]; if (![self isCancelled]) { self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; for (NSString *runLoopMode in self.runLoopModes) { [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; } [self.outputStream open]; [self.connection start]; } [self.lock unlock]; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self]; }); }
3.ASI
建立單例線程
+(NSThread *)threadForRequest:(ASIHTTPRequest *)request { if (networkThread == nil) { @synchronized(self) { if (networkThread == nil) { networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequests) object:nil]; [networkThread start]; } } } return networkThread; }
-(void)startSynchronous { #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING ASI_DEBUG_LOG(@"[STATUS] Starting synchronous request %@",self); #endif [self setSynchronous:YES]; [self setRunLoopMode:ASIHTTPRequestRunLoopMode]; [self setInProgress:YES]; if (![self isCancelled] && ![self complete]) { [self main]; while (!complete) { [[NSRunLoop currentRunLoop] runMode:[self runLoopMode] beforeDate:[NSDate distantFuture]]; } } [self setInProgress:NO]; }
4.GCDAsyncSocket
讀寫流都運行在RunLoop的DefaultMode
+(void)scheduleCFStreams:(GCDAsyncSocket *)asyncSocket
{
LogTrace();
NSAssert([NSThread currentThread] == cfstreamThread, @"Invoked on wrong thread"); CFRunLoopRef runLoop = CFRunLoopGetCurrent(); if (asyncSocket->readStream) CFReadStreamScheduleWithRunLoop(asyncSocket->readStream, runLoop, kCFRunLoopDefaultMode); if (asyncSocket->writeStream) CFWriteStreamScheduleWithRunLoop(asyncSocket->writeStream, runLoop, kCFRunLoopDefaultMode); }
5.POP中使用CADisplayLink
CADisplayLink是幀計數器,默認每秒運行60次
#if TARGET_OS_IPHONE _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render)]; _displayLink.paused = YES; [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; #else CVDisplayLinkCreateWithActiveCGDisplays(&_displayLink); CVDisplayLinkSetOutputCallback(_displayLink, displayLinkCallback, (__bridge void *)self); #endif
利用Observer對RunLoop的狀態監控,若是長時間處於工做態kCFRunLoopBeforeSources或kCFRunLoopAfterWaiting,就認爲主線程被阻塞,這對於UI界面卡頓不流暢提供了一種實現思路。