原文地址:http://d3caifu.com/ebook/MultiThread.html
隨著計算機/移動設備不斷發展,提供了越來越強大的計算能力和資源,從硬件層面來看首先是CPU的芯片集成度越來越高,提供更快的處理能力;其次是多核化多CPU發展;最后剝離一些圖片動畫渲染等到獨立的協處理器處理,硬件的提升促進了操作系統軟件OS的不斷進化,更好的利用硬件能力和資源,提高系統的性能,多線程多任務處理就是最為關鍵的技術。
線程分為主線程和后臺線程2種:一個應用只有唯一的一個主線程,其它線程都是后臺線程。主線程主要負責UI界面更新和響應用戶事件。
OSX系統提供了多種線程編程相關技術,我們主要討論NSThread,NSOperationQueue,GCD,Run Loop等相關概念和編程要點。
GCD
傳統的多線程編程技術,為了并行處理增強性能往往采用手段是創建更多線程去并行處理,但這帶來一個問題:究竟創建多少個線程是合適的最優的?線程的創建準備需要耗費一定的系統內存資源,到一定數量后,性能的提升并不一定是跟線程的多少成線性增長的,這樣對多線程編程提出了很高的要求,如何控制保持合理數量的線程保證性能最優?
GCD是內核級的多線程并發技術,不需要關注線程創建,只需要把執行的程序單元體檢到GCD的分發隊列即可,GCD線程技術特點:
1)創建線程是系統自動完成,簡化了傳統的編程模式
2)內核級,創建線程不會占用應用程序的內存空間
2)基于C語言函數級的接口編程,性能優
3)GCD的分發隊列按先進先出的順序執行線程任務
Dispatch Queue
GCD的多線程任務都是通過Dispatch Queue來管理,有多種類型的分發隊列:
1.Serial串行隊列
每個串行隊列,順序執行,先進先出,每次只允許執行一個任務,任務執行完成后才能執行隊列中下一個任務。
可以創建多個串行隊列,多個串行隊列之間仍然是并行調度。
串行隊列的單任務調度特點,使得它特別適合管理保護存在沖突訪問的資源,比如文件讀寫,全局數據的訪問等。
除了主線程隊列,任務其它串行隊列都需要代碼手工去創建。
2.Concurrent并行隊列
并行隊列,每次可以按照順序執行一個或多個任務單元,并發的任務數由系統根據計算資源自動的動態分配控制。
系統默認提供四種不同高低優先級的并行隊列,可以按需獲取。除此之外也可以通過代碼創建自己的并行隊列。
3.Main dispatch queue主線程隊列
每個應用中自動生成,唯一的全局的串行隊列,無須手工創建,用于在應用的主線程上執行任務。UI界面元素的數據更新必須在主線程隊列執行。
GCD多線程編程
創建隊列
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];
}
);
});
線程組Groups
將多個任務加入到一個組,當組內所有任務都執行完畢后收到通知。
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來對任務做分隔保護,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);
}
}
GCD 實際使用的例子
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]);
}
}];
Dispatch Sources
NSOperationQueue操作隊列
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
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
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
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執行完的回調
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
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類中關鍵方法和屬性
獲取當前線程和主線程相關方法:
//獲取當前運行的線程
+(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;
NSObject線程擴展方法
在主線程執行的方法
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的概念。
RunLoop稱為事件循環,可以理解為系統中對各種事件源不間斷的循環的處理。應用在運行過程中會產生大量的系統和用戶事件,包括定時器事件,用戶交互事件(鼠標鍵盤觸控板操作),模態窗口事件,各種系統Source事件,應用自定義的Source事件等等,每種事件都會存儲到不同的FIFO先進先去的隊列,等待事件循環依次處理。
被RunLoop管理的線程在掛起時,不會占用系統的CPU資源,可以說RunLoop是非常高效的線程管理技術。
線程和RunLoop是一一對應,系統會將線程和它的RunLoop對象實例以key/value字典形式存儲到全局字典中統一管理和訪問。獲取線程的RunLoop時,如果字典中不存在或新建一個并將這個RunLoop存儲到字典。
每個應用啟動后系統默認生成一個RunLoop對象,也可以稱為主線程的RunLoop,由它完成主線程運行期間各種事件調度控制。
RunLoop中包括3大核心組件,定時器,輸入源Input Sources和觀察者Observer,后面會逐一介紹。
RunLoop的Modes
Modes是一組事件類型的集合,每個事件是注冊關聯到一個或多個Mode中,RunLoop在每個時刻運行在一個特定的模式。
RunLoop在運行時,只處理注冊到當前Mode模式下的事件和通知模式相關的觀察者,當前模式運行期發生的其它模式的事件不會被處理,只能被存儲到消息隊列等到RunLoop下一次切換到對應的Mode時才能處理。
舉個簡單的例子假如一個定時器注冊到Default缺省模式下,如果當前發生了高優先級的系統事件Touch點擊事件RunLoop會切換到NSEventTrackingRunLoopMode模式處理,如果定時器到時timeout的事件正好發生就不會處理了。
注冊模式的原則:如果不是高優先級需要實時處理的事件,可以采用默認模式。如果RunLoop運行在任何模式都需要處理這個事件就注冊在NSRunLoopCommonModes/kCFRunLoopCommonModes Common模式。
RunLoop類方法說明
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());
RunLoop的活動狀態
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中的Input Source
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
RunLoop事件處理流程
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
RunLoop使用場景
1.定時器事件
創建定時器,將其加入指定的模式,運行在缺省的模式下的定時器會受到用戶交互事件的影響而延遲執行。
2.多個線程間通過Perform Seletor方法通訊
3.input source事件觸發的任務
GCD與RunLoop
GCD跟RunLoop沒有直接的關系,主線程自動綁定到一個RunLoop,所以推出一個結論:GCD中提交到主線程隊列的會在主線程RunLoop 循環周期內調用。
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
使用RunLoop監控主線程阻塞
利用Observer對RunLoop的狀態監控,如果長時間處于工作態kCFRunLoopBeforeSources或kCFRunLoopAfterWaiting,就認為主線程被阻塞,這對于UI界面卡頓不流暢提供了一種實現思路。
文章列表