[转]多线程

原文地址: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是非常高效的线程管理技术。

EventLoop

线程和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模式。

RunLoopMode

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界面卡顿不流畅提供了一种实现思路。

 

posted @ 2016-01-27 09:36  Ethan_村长  阅读(920)  评论(0编辑  收藏  举报