iOS多线程(NSThread、NSOperation、GCD)编程

释放双眼,带上耳机,听听看~!

一、 什么是多线程

多线程是一个比较轻量级的方法来实现单个应用程序内多个代码执行路径。

  1. 进程: 一个具有一定独立功能的程序关于某个数据集合的一次运行活动。可以理解成一个运行中的应用程序。
  2. 线程: 程序执行流的最小单元,线程是进程中的一个实体。
  3. 同步: 只能在当前线程按先后顺序依次执行,不开启新线程。
  4. 异步: 可以在当前线程开启多个新线程执行,可不按顺序执行。
  5. 队列: 装载线程任务的队形结构。
  6. 并发: 线程执行可以同时一起进行执行。
  7. 串行: 线程执行只能依次逐一先后有序的执行。

注意:

  • 一个进程可有多个线程。
  • 一个进程可有多个队列。
  • 队列可分并发队列和串行队列。

二、iOS多线程对比

1. NSThread

每个NSThread对象对应一个线程,真正最原始的线程。
1)优点:NSThread 轻量级最低,相对简单。
2)缺点:手动管理所有的线程活动,如生命周期、线程同步、睡眠等。

2. NSOperation

自带线程管理的抽象类。
1)优点:自带线程周期管理,操作上可更注重自己逻辑。
2)缺点:面向对象的抽象类,只能实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。

3. GCD

Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法。
1)优点:最高效,避开并发陷阱。
2)缺点:基于C实现。

4. 选择小结

1)简单而安全的选择NSOperation实现多线程即可。
2)处理大量并发数据,又追求性能效率的选择GCD。
3)NSThread本人选择基本上是在做些小测试上使用,当然也可以基于此造个轮子。

三、场景选择

  1. 图片异步加载。这种常见的场景是最常见也是必不可少的。异步加载图片有分成两种来说明一下。
    第一种,在UI主线程开启新线程按顺序加载图片,加载完成刷新UI。
    第二种,依然是在主线程开启新线程,顺序不定地加载图片,加载完成个字刷新UI。
  2. 创作工具上的异步。 这个跟上边任务调度道理差不多,只是为了丰富描述,有助于“举一反三”效果。如下描述的是app创作小说。
    场景一,app本地创作10个章节内容未同步服务器,接着同时发表这10个章节将产生的一系列动作,其中上传内容,获取分配章节Id,如果后台没有做处理最好方式做异步按顺序执行。
    场景二,app本地创作列表中有3本小说要发表,如果同时发表创作列表中的3本小说,自然考虑并行队列执行发表。

四、使用方法

第三标题场景选择内容实现先留下一个悬念。具体实现还是先熟知一下各自的API先。

1. NSThread

1.1)三种实现开启线程方式:
①.动态实例化

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImageSource:) object:self.imgUrl];
thread.threadPriority = 1;// 设置线程的优先级(0.0 - 1.0,1.0最高级)
[thread start];

②.静态实例化

[NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:self.imgUrl];

③.隐式实例化

[self performSelectorInBackground:@selector(loadImageSource:) withObject:self.imgUrl];

1.2)使用这三种方式编写代码:

//动态创建线程
-(void)dynamicCreateThread{
    [self.imageView setImage:nil];
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(loadImageSource:) object:self.imgUrl];
    thread.threadPriority = 1;// 设置线程的优先级(0.0 - 1.0,1.0最高级)
    [thread start];
}
//静态创建线程
-(void)staticCreateThread{
    [self.imageView setImage:nil];
    [NSThread detachNewThreadSelector:@selector(loadImageSource:) toTarget:self withObject:self.imgUrl];
}
//隐式创建线程
-(void)implicitCreateThread{
    [self.imageView setImage:nil];
    [self performSelectorInBackground:@selector(loadImageSource:) withObject:self.imgUrl];
}
-(void)loadImageSource:(NSString *)url{
    NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
    UIImage *image = [UIImage imageWithData:imgData];
    if (imgData!=nil) {
        [self performSelectorOnMainThread:@selector(refreshImageView:) withObject:image waitUntilDone:YES];
    }else{
        NSLog(@"there no image data");
    }
}
-(void)refreshImageView:(UIImage *)image{
    [self.imageView setImage:image];
}

1.3)看先效果图

1.4)NSThread的拓展认识

①获取当前线程
    NSThread *current = [NSThread currentThread];
②获取主线程
    NSThread *main = [NSThread mainThread];
③暂停当前线程
    [NSThread sleepForTimeInterval:2];
④线程之间通信
    //在指定线程上执行操作
    [self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];
    //在主线程上执行操作
    [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
    //在当前线程执行操作
    [self performSelector:@selector(run) withObject:nil];

2. NSOperation

主要的实现方式:结合NSOperation和NSOperationQueue实现多线程编程。

  • 实例化NSOperation的子类,绑定执行的操作。
  • 创建NSOperationQueue队列,将NSOperation实例添加进来。
  • 系统会自动将NSOperationQueue队列中检测取出和执行NSOperation的操作。

2.1)使用NSOperation的子类实现创作线程。

①.NSInvocationOperation创建线程。
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:self.imgUrl];
    //[invocationOperation start];//直接会在当前线程主线程执行
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:invocationOperation];
②.NSBlockOperation创建线程
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        [self loadImageSource:self.imgUrl];
    }];
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:blockOperation];
③.自定义NSOperation子类实现main方法
- (void)main {
    if (self.isCancelled) return;
    NSURL *url = [NSURL URLWithString:self.imgUrl];
    NSData *imageData = [NSData dataWithContentsOfURL:url];
    if (self.loadDelegate!=nil&&[self.loadDelegate respondsToSelector:@selector(loadImageFinish:)]) {
        [(NSObject *)self.loadDelegate performSelectorOnMainThread:@selector(loadImageFinish:) withObject:image waitUntilDone:NO];
    }
}

2.2)使用这三种方式编写代码

创建各个实例并添加到队列表当中

//使用子类NSInvocationOperation
-(void)useInvocationOperation{
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadImageSource:) object:self.imgUrl];
    //[invocationOperation start];//直接会在当前线程主线程执行
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:invocationOperation];
}
//使用子类NSBlockOperation
-(void)useBlockOperation{
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        [self loadImageSource:self.imgUrl];
    }];
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:blockOperation];
}
//使用继承NSOperation
-(void)useSubclassOperation{
    LoadImageOperation *imageOperation = [LoadImageOperation new];
//    imageOperation.loadDelegate = self;
//    imageOperation.imgUrl = self.imgUrl;
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:imageOperation];
}
-(void)loadImageSource:(NSString *)url{
    NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
    UIImage *image = [UIImage imageWithData:imgData];
    if (imgData!=nil) {
        [self performSelectorOnMainThread:@selector(refreshImageView1:) withObject:image waitUntilDone:YES];
    }else{
        NSLog(@"there no image data");
    }
}
-(void)refreshImageView1:(UIImage *)image{
    [self.imageView setImage:image];
}
-(void) loadImageFinish:(UIImage *)image{
    [self.imageView setImage:image];
}

3. GCD多线程

GCD是Apple开发,据说高性能的多线程解决方案。既然这样,就细说一下这个解决方案。
进过Nsthread和NSOperation的讲述和上边的基础概念,可以开始组合用起来吧。并发队列串行队列都用起来。

使用GCD的优点

  • GCD 可用于多核的并行运算
  • GCD 会自动利用更多的 CPU 内核(比如双核、四核)
  • GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码

3.1)GCD的任务和对列:

1、任务:

就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:同步执行(dispatch_sync)和异步执行(dispatch_async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

同步执行任务创建方法 dispatch_sync(queue, ^{     // 这里放同步执行任务代码 });

异步执行任务创建方法 dispatch_async(queue, ^{     // 这里放异步执行任务代码 });

2、队列:

在 GCD 中有两种队列:串行队列并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

串行队列(Serial Dispatch Queue)

  • 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)

并发队列(Concurrent Dispatch Queue)

  • 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效

3.创建队列:dispatch_queue_create()

(需要传入两个参数,第一个参数表示队列的唯一标识符,用于 DEBUG,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列)

① 串行队列的创建方法

dispatch_queue_tqueue = dispatch_queue_create(“net.bujige.testQueue”, DISPATCH_QUEUE_SERIAL);

② 并发队列的创建方法

dispatch_queue_tqueue = dispatch_queue_create(“net.bujige.testQueue”,DISPATCH_QUEUE_CONCURRENT);

③ 主队列:

主队列的获取方法 dispatch_queue_tqueue = dispatch_get_main_queue();

④全局并发队列(相当于普通的并发对列)

全局并发队列的获取方法(需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可
dispatch_queue_t queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);

4.组合形式:

虽然使用 GCD 只需两步,但是既然我们有三种队列(串行队列/并发队列/主队列),两种任务执行方式(同步执行/异步执行),那么我们就有了六种不同的组合方式。这六种不同的组合方式是:

区别 并发队列 串行队列 主队列
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务
异步(async) 有开启新线程,并发执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务
//**************** 1、同步执行 + 并发队列
//在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
/**
 * 同步执行 + 并发队列
 * 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。
 */
- (void)syncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    NSLog(@"syncConcurrent---end");
}
//**************** 2、异步执行 + 并发队列
//可以开启多个线程,任务交替(同时)执行。
/**
 * 异步执行 + 并发队列
 * 特点:可以开启多个线程,任务交替(同时)执行。
 */
- (void)asyncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    NSLog(@"asyncConcurrent---end");
}
//**************** 3、同步执行 + 串行队列
//不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
/**
 * 同步执行 + 串行队列
 * 特点:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
 */
- (void)syncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncSerial---begin");
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    NSLog(@"syncSerial---end");
}
//**************** 4、异步执行 + 串行队列
//会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务
/**
 * 异步执行 + 串行队列
 * 特点:会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。
 */
- (void)asyncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncSerial---begin");
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    NSLog(@"asyncSerial---end");
}
//**************** 5、同步执行 + 主队列
//同步执行 + 主队列在不同线程中调用结果也是不一样,在主线程中调用会出现死锁,而在其他线程中则不会。
/**
 * 同步执行 + 主队列
 * 特点(主线程调用):互等卡主不执行。
 * 特点(其他线程调用):不会开启新线程,执行完一个任务,再执行下一个任务。
 */
- (void)syncMain {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"syncMain---begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    NSLog(@"syncMain---end");
}
//**************** 6、异步执行 + 主队列
//只在主线程中执行任务,执行完一个任务,再执行下一个任务。
/**
 * 异步执行 + 主队列
 * 特点:只在主线程中执行任务,执行完一个任务,再执行下一个任务
 */
- (void)asyncMain {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程
    NSLog(@"asyncMain---begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    NSLog(@"asyncMain---end");
}

 

人已赞赏
iOS文章

ios下移动文件方法有哪些

2020-2-14 16:33:42

iOS文章

ios13 获取wifi信息

2020-2-14 17:15:43

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
有新消息 消息中心
搜索