开发中为了更好的用户体验我们会用到多线程。
主要讨论三中创建多线程的方法:NSThread,GCD,NSOperation 。
NSThread
从命名来看这是一个封装好的类,它的生命周期需要我们手动管理。常用的创建方法
1、通过类方法创建并自动启动:
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;
2、通过实例方法创建手动启动:
NSThread *newThread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[newThread setName:@"threadName"];//自定义线程名称
[newThread start];//启动
[newThread cancel];//取消
关于NSThread我们实际开发中常用的方法是:
+ (NSThread *)currentThread;//获取当前线程信息
+ (NSThread *)mainThread;//获取主线程信息
+ (void)sleepForTimeInterval:(NSTimeInterval)time;//调试时用的睡眠
GCD
GCD为Grand Central Dispatch首字母缩写,是Apple开发的一个多核编程的解决方案。起源于Mac OS X 10.6,在iOS4.0被引入。GCD会自动管理线程的生命周期,采用C语言实现,通过Block将执行体传入。
1、GCD中有三种队列类型:
①the main queue:
获取方式dispatch_queue_t mainQueue = dispatch_get_main_queue();提交到该队列中的任务会在主线程执行,为一个串行队列。
②the global queue:
获取方式 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);全局队列是并发队列,并由整个进程共享,第一个参数为选择队列,参数可选为
#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//后台
③the user defined queue:
获取方式:第一个参数为名称,debug时会显示
dispatch_queue_t serialQueue =dispatch_queue_create("com.serial.queue",NULL);//串行
dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);//串行
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);//并行
2、应用:
①关于dispatch_async---添加任务到一个队列并不等待任务完成,而是立即继续其他任务。
//首先相对当前语句所在的线程来说是异步提交,将任务提交到default级别的全局队列中返回,block等待default队列FIFO顺序执行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self goDoSomethingLongTask];//在default队列中执行完,此时不会继续往下执行,除非当前语句块执行完成
dispatch_async(dispatch_get_main_queue(), ^{//异步提交任务到main_queue
[textField setStringValue:@"Done doing something long and involved"];//在main中执行当前语句块
});
});
②关于dispatch_sync---添加任务到一个队列并等待直到任务完成,用于等待提交任务的结果,才能继续执行下面的代码块的情况。
dispatch_sync一般应用在并发队列:这才是做同步工作的好选择。
在主线程中执行如下代码块:
//首先相对当前语句所在的线程来说是同步提交,将任务提交到main queue中,等待main queue执行该block-----这里会造成阻塞,因为sync已经阻塞当前线程,等待block在所提交的线程执行完成后才放开阻塞的线程,即A被阻塞等待任务执行完,任务又等A的调度执行。
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog("sync - %@", NSThread.currentThread());
});
③关于dispatch_group
演示一个应用:
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);//获得一个异步队列
dispatch_group_t group = dispatch_group_create();//新建一个group队列
for(idobj in array){//for循环遍历 每次异步方式提交任务block到queue,
dispatch_group_async(group, queue, ^{
[selfdoSomethingIntensiveWithbj];
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);//group进入永久阻塞等待,直到之前任务完成
dispatch_release(group);
[selfdoSomethingWith:array];//group完成后,执行此
优化:将最后的任务放到异步线程中执行
dispatch_queue_t queue = dispatch_get_global_qeueue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_group_t group = dispatch_group_create();
for(idobj in array){//近乎平行执行
dispatch_group_async(group, queue, ^{
[selfdoSomethingIntensiveWithbj];
});
}
//之前任务完成后会被notify通知执行
dispatch_group_notify(group, queue, ^{
[selfdoSomethingWith:array];
});
dispatch_release(group);
④关于dispatch_apply。
同步执行dispath_apply代码块,调用单一block多次,并平行运算,然后等待所有运算结束,需保证block中线程安全
dispatch_apply(array.count, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
[NSThread sleepForTimeInterval:1];
NSLog(@"---%@",array[index]);
});
[self say];//最后执行
⑤关于dispatch_barrier。在并发队列中扮演一个串行式的瓶颈作用。
能够确保提交的block在被执行的特定时间上是指定队列上唯一被执行的条目!由于队列是FIFO的,换句话说所有先于该barrier block的条目一定能再这个block执行前完成。等完成该block后队列恢复默认状态。
应用--读写问题:
线程不能保证安全
- (void)addPhoto:(Photo *)photo
{
if (photo) {
[_photosArray addObject:photo];
dispatch_async(dispatch_get_main_queue(), ^{
[self postContentAddedNotification];
}
修改成一个线程安全的写操作
-(void)addPhoto:(Photo *)photo
{
if (photo) { // 1判断非空
dispatch_barrier_async(self.concurrentPhotoQueue, ^{ // 2 barrier方式异步提交代码块到queue中
[_photosArray addObject:photo]; // 3等之前提交的都执行完后,只有当前代码块在queue中执行,barrier提交确保线程安全的!
dispatch_async(dispatch_get_main_queue(), ^{ // 4通知
[self postContentAddedNotification];
});
});
}
}
读操作
不能提供任何保护来对抗当一个线程调用读方法的同时另一个线程调用写方法
- (NSArray *)photos
{
return [NSArray arrayWithArray:_photosArray];
}
修改为:
- (NSArray *)photos
{
__block NSArray *array; // 1
dispatch_sync(self.concurrentPhotoQueue, ^{ // 2同步提交等待,block代码块在queue中执行那个完成,因为写操作是barrier提交保证了执行时只有写操作自己在queue中!
array = [NSArray arrayWithArray:_photosArray]; // 3等待queue调度执行代码块
});
return array;
}
⑥dispatch_once,常用于创建线程安全的单例。
+ (TransHistoryInstance *)sharedInstance
{
static TransHistoryInstance *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
⑦dispatch_after 用于延迟操作
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//code to be executed after a specified delay
});
⑧ dispatch_semaphore信号量 阻塞方式的一种,为0等待,否则减一继续
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);//初始化为1,保证不会一开始被阻塞。
NSMutableArray *array = [NSMutableArray array];
for (int index = 0; index < 100000; index++) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^(){
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//如果semaphore计数大于等于1.计数-1,返回,程序继续向下运行。如果计数为0,则等待
NSLog(@"addd :%d", index);
[array addObject:[NSNumber numberWithInt:index]];
// Increment the counting semaphore.
dispatch_semaphore_signal(semaphore);//加1});
}
NSOperation And NSOperationQueue
1、NSOperation 是一个抽象类,首先继承NSOperation 重写main函数 在main中创建一个autoreleasepool 在autoreleasepool中添加代码。
①start开始:通常不重载该方法,如果重载,必须关注像isExecuting, isFinished, isConcurrent, and isReady这些属性。
将一个operation添加到NSOperationQueue队列后,队列会自动调用该operation的start方法,执行完一些准备工作后,执行main函数。
但是,如果手动触发start方法,并没有添加到NSOperationQueue队列中,该operation将会在主线程中运行。
②dependency依赖:两个operation之间可以建立依赖关系。
NSBlockOperation *downloadOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"---new1-:%@",[NSThread currentThread]);
}];
NSBlockOperation *storeOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"---new1-:%@",[NSThread currentThread]);
}];
[storeOperation addDependency:downloadOperation];//store依赖download 等到download操作的isFinished 为YES时,才开始执行 [storeOperation removeDependency:downloadOperation];//移除依赖关系
③priority优先级: 当你增加一个操作到队列,在调用他们的“start”之前,NSOperationQueue会查找所有的操作,优先级高的操作优先执行。同级的操作按照提交到队列的顺序执行(FIFO).
[downloadOperation setQueuePriority:NSOperationQueuePriorityHigh];//设置优先级
④completion block完成后的响应block
[blockOperation setCompletionBlock:^{
//code不一定在主线程中
}];
2、NSOperationQueue 是一个队列。一个Queue队列中可以有多个线程,不同的线程并发的执行。队列中并发操作的数目小于等于所设置的最大运行数。要定期检查(昂贵操作前后)isCancelled,确保尽快终止操作。
举个小栗子:
@implementation myOperation
//一般不需要重载 如果重载,必须关注像isExecuting, isFinished, isConcurrent, and isReady这些属性。
//- (void)start{
//
//}
- (void)main
{
@autoreleasepool {
NSLog(@"isMainThread-%d",[NSThread isMainThread]);
if (self.isCancelled)
return;
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:@"http://images.freeimages.com/images/previews/3a2/fall-in-ontario-1056162.jpg"]];
if (self.isCancelled) {
imageData = nil;
return;
}
// do something about imageData
if (self.isCancelled)
return;
// report to somebody on main thread
}
}
使用:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"my Queue";
queue.maxConcurrentOperationCount = 3;//同时执行的最大并发数
for(int i = 0; i < 100;i++){
myOperation *operation = [[myOperation alloc] init];
[queue addOperation:operation];//不一定会立即触发
}
关于GCD和NSOperation各有所长,以具体需求取舍吧。