iOS多线程

  1. 多线程概述
     1> 程序、进程和进程的概念
    程序:由源代码生成的可执行应用。(例如:QQ.app)

进程:一个正在运行的程序可以看做一个进程。(例如:正在运行的QQ就是一个进程),进程拥有独立运行所需的全部资源。

线程:程序中独立运行的代码段。(例如:接收QQ消息的代码)

一个进程是由一或多个线程组成。进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。

 2> 单线程
每个正在运行的程序(即进程),至少包含一个线程,这个线程叫主线程。

主线程在程序启动时被创建,用于执行main函数。

只有一个主线程的程序,称作单线程程序。

在单线程程序中,主线程负责执行程序的所有代码(UI展现以及刷新,网络请求,本地存储等等)。这些代码只能顺序执行,无法并发执行。

 3> 多线程
拥有多个线程的程序,称作多线程程序。

iOS允许用户自己开辟新的线程,相对于主线程来讲,这些线程,称作子线程。

可以根据需要开辟若干子线程

子线程和主线程 都是 独立的运行单元,各自的执行互不影响,因此能够并发执行。

 4> 单线程和多线程区别
单线程程序:只有一个线程,即主线程,代码顺序执行,容易出现代码阻塞(页面假死)。

多线程程序:有多个线程,线程间独立运行,能有效的避免代码阻塞,并且提高程序的运行性能。

注意:iOS中关于UI的添加和刷新必须在主线程中操作。

  1. NSThread与NSObject创建多线程
     1> NSThread创建方法
      

NSThread手动开辟子线程
复制代码
// 参数1:target
// 参数2:方法
// 参数3:传递的参数
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(sayHi) object:nil];

// 开启线程
[thread start];

复制代码
NSThread自动开辟子线程
  // 线程自动开启
[NSThread detachNewThreadSelector:@selector(sayHi) toTarget:self withObject:nil];
 2> NSThread其他常用属性和方法
  头文件中其他常用属性和方法的声明:

复制代码
// 获取当前的线程
+ (NSThread *)currentThread;

// 主线程休眠ti秒
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

// 直接将线程退出
+ (void)exit;

// 判断是否为主线程
+ (BOOL)isMainThread;

// 获取主线程
+ (NSThread *)mainThread;

// 取消线程,不是真正的取消,只是给线程发送一个信号,通过这份信号进行取消
- (void)cancel;

// 手动开辟子线程时启动线程
- (void)start;
复制代码
  代码:

复制代码
// 获取当前线程
NSLog(@”currentThread == %@”, [NSThread currentThread]);

// 获取主线程
NSLog(@"mainThread == %@", [NSThread mainThread]);

// 判断当前线程是否为主线程
NSLog(@"isMainThread == %d", [NSThread isMainThread]);

复制代码
 3> NSObject开启子线程
复制代码
- (void)viewDidLoad {
[super viewDidLoad];

// 使用performSelectorInBackground开辟子线程
// 参数1:selector
// 参数2:方法传递的参数
[self performSelectorInBackground:@selector(sayHi) withObject:nil];
self.view.backgroundColor = [UIColor yellowColor];

}

// 子线程执行方法
- (void)sayHi
{

NSLog(@"Hello World");

NSLog(@"currentThread == %@", [NSThread currentThread]);

NSLog(@"mainThread == %@", [NSThread mainThread]);

// 回到主线程修改当前背景颜色
// 参数1:selector
// 参数2:传的参数
// 参数3:是否等待子线程执行完成之后进入主线程
[self performSelectorOnMainThread:@selector(mainThreadChangeColor) withObject:nil waitUntilDone:YES];

}

// 主线程执行方法
- (void)mainThreadChangeColor
{
self.view.backgroundColor = [UIColor cyanColor];

NSLog(@"currentThread == %@", [NSThread currentThread]);

NSLog(@"mainThread == %@", [NSThread mainThread]);

}
复制代码
 4> 线程结束
  使用NSThread和NSObject实现的开辟线程,系统会自动释放,关不关都可以

  结束线程的两种方式:

// 取消线程
[thread cancel]; // 不是真正的取消,只是给线程发送一个信号,通过这份信号进行取消

// 直接将线程退出
[NSThread exit];

3. NSOperationQueue
 1> 概述
NSOperation类,在MVC中属于M,是用来封装单个任务相关的代码和数据的抽象类

因为它是抽象的,不能够直接使用这个类,而是使用子类( NSInvocationOperation或NSBlockOperation )来执行实际任务。

NSOperation(含子类),只是一个操作,本身无主线程、子线程之分,可在任意线程中使用。通常与NSOperationQueue结合使用。

 2> NSInvocationOperation
NSInvocationOperation是NSOperation的子类

封装了执行操作的target和要执行的action。

复制代码
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];

// 在单独使用NSOperation的子类去创建线程的时候,一定要启动才行

[invocationOperation start];
  • (void)test
    {
    NSLog(@”��”);

    NSLog(@”currentThread Invocation == %@”, [NSThread currentThread]);
    NSLog(@”mainThread Invocation == %@”, [NSThread mainThread]);
    }
    复制代码
     3> NSBlockOperation
    NSBlockOperation是NSOperation的子类

封装了需要执行的代码块

复制代码
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{

    NSLog(@"Block");

    NSLog(@"currentThread Block == %@", [NSThread currentThread]);
    NSLog(@"mainThread Block == %@", [NSThread mainThread]);
}];

// 只有启动的时候才会调用Block的内容
[blockOperation start];

复制代码
   注:在使用NSOperation的子类去创建线程的时候,实际上没有真正意义上创建成功

  上图中输出的地址都是同一个地址,说明子线程没有创建成功

 4> NSOperationQueue
NSOperationQueue是操作队列,他用来管理一组Operation对象的执行,会根据需要自动为Operation开辟合适数量的线程,以完成任务的并行执行

其中NSOperation可以调节它在队列中的优先级(使用addDependency:设置依赖关系)

最大并发数maxConcurrentOperationCount

  当值设置为1时,可以叫串行:即顺序执行

  当值设置大于1时,叫做并行:多条通道同时进行各自的任务

将 2> 和 3> 中创建的子线程添加到NSOperationQueue的操作队列
// 需要把上边的两个线程,放到操作队列中
// addOperation一旦将创建的对象加入到队列中,就不能使用start方法,否则或造成奔溃
NSOperationQueue *operationQueue = [NSOperationQueue new];

[operationQueue addOperation:invocationOperation];
[operationQueue addOperation:blockOperation];

  注:addOperation一旦将创建的对象加入到队列中,就不能使用start方法,否则或造成奔溃

  崩溃信息为:reason: ‘* -[NSOperationQueue addOperation:]: operation is finished and cannot be enqueued

  请大家注意!

  1. GCD
     1> 概述

Grand Central Dispatch (GCD)是Apple开发的一种多核编程技术。主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。

GCD提供函数实现多线程开发,性能更高,功能也更加强大。

它首次发布在Mac OS X 10.6 ,iOS 4及以上也可用。

 2> 核心概念

任务:具有一定功能的代码段。一般是一个block或者函数

分发队列:GCD以队列的方式进行工作,FIFO

GCD会根据分发队列的类型,创建合适数量的线程执行队列中的任务

 3> GCD中的队列

串行队列(SerialQueue):一次只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。SerialQueue能实现线程同步

并行队列(Concurrent):可以并发地执行多个任务,虽然遵守FIFO(先进先出),但由于各个任务被分配到不同的线程执行,因此其完成时间有可能不同,即:后分配的任务有可能先执行完成;并发队列一定需要和异步执行的任务(使用dispatch_async())结合起来使用才有意义。

主队列:主队列也是一个串行队列,主队列中的任务都在主线程中执行。
 4> 串行队列(SerialQueue)

第一种:系统提供的创建串行队列的方法,实际上主队列的创建方法
复制代码
// 第一种获取方式:里面的任务是在主线程依次去执行,dispatch_get_main_queue()获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
// 往队列里面添加任务
dispatch_async(queue, ^{
NSLog(@”这是第一个任务,当前线程是:%@, 是否主线程 :%d “, [NSThread currentThread], [[NSThread currentThread] isMainThread]);
});
复制代码
第二种:自己创建的队列
复制代码
// 参数1:队列的名字(苹果推荐使用反向域名去命名)
// 参数2:队列的类型(串行队列、并行队列),这种方式创建的队列,它会自己去开辟一个子线程去完成队列里面的任务
dispatch_queue_t queue = dispatch_queue_create(“com.zf.mySerialQueue”, DISPATCH_QUEUE_SERIAL);

dispatch_async(queue, ^{

NSLog(@”这是第一个任务,当前线程是:%@, 是否主线程 :%d “, [NSThread currentThread], [[NSThread currentThread] isMainThread]);
});
复制代码
 5> 并行队列

第一种:系统提供的创建并行队列的方法
复制代码
// 参数1:优先级PRIORITY(有四个,没有明显的区别)
// 参数2:系统保留字,苹果预留的参数为了以后去使用,目前没有用到,填写0
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

dispatch_async(oneQueue, ^{

NSLog(@”这是第一个任务。。。线程是:%@, 是否主线程:%d”, [NSThread currentThread], [[NSThread currentThread] isMainThread]);
});
复制代码
第二种:自己创建的队列
  使用的方法同串行队列,只有创建函数参数2给定队列的类型的时候,将DISPATCH_QUEUE_SERIAL替换为DISPATCH_QUEUE_CONCURRENT

复制代码
// 参数1:队列的名字(苹果推荐使用反向域名去命名)
// 参数2:队列的类型(串行队列、并行队列),这种方式创建的队列,它会自己去开辟一个子线程去完成队列里面的任务
dispatch_queue_t queue = dispatch_queue_create(“com.zf.mySerialQueue”, DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

NSLog(@”这是第一个任务,当前线程是:%@, 是否主线程 :%d “, [NSThread currentThread], [[NSThread currentThread] isMainThread]);
});
复制代码
 6> GCD功能函数

dispatch_async(dispatch_queue_t queue, ^(void)block) // 异步任务,往队列中添加任务,任务会排队执行
  参数1(dispatch_queue_t queue):添加任务的队列

  参数2(^(void)block):Block,主要是在添加过程中进行一些操作,操作代码就写在Block中

dispatch_after() //往队列中添加任务,任务不但会排队,还会在延迟的时间点执行
复制代码
/*
函数
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, ^(void)block);

 // 系统封装的代码快(一般使用)
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(具体的浮点型数字 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
   延迟执行的内容
 });

 */
// 参数1:延迟的时间,使用dispatch_time()初始化
// 参数2:队列
// 参数3:Block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"3.0秒后");
});

复制代码
  dispatch_time(dispatch_time_t when, int64_t delta)对延迟时间进行初始化

  参数1:为起始时间,系统宏定义,一般使用 DISPATCH_TIME_NOW

  参数2:为延迟的具体时间类型为 int64_t,变量的类型为:具体浮点型数字 * 时间单位(系统的宏定义,可以根据实际情况选用)

dispatch_apply() //往队列中添加任务,任务会重复执行n次
复制代码
/*
dispatch_apply(size_t iterations, dispatch_queue_t queue, ^(size_t) {
code
});
*/
dispatch_queue_t queue = dispatch_queue_create(“com.zf.myQueue”, DISPATCH_QUEUE_CONCURRENT);
// 参数1:添加任务的个数
// 参数2:队列
// 参数3:Block,这个Block没有变量名,需要自己添加
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@”%zu”, index);
});
复制代码
  注:函数dispatch_apply()的参数3Block没有变量名,需要自己添加

dispatch_group_async() //将任务添加到队列中,并添加分组标记

dispatch_group_notify() //将任务添加到队列中,当某个分组的所有任务执行完之后,此任务才会执行

复制代码
// dispatch_group_t主要用于把一些不相关的任务归为一组
// 组里面放的是队列
// dispatch_group_async作用是往组里面的队列添加任务
// dispatch_group_notify作用:监听组里面的任务,等到组里面的任务全部执行完成之后,才会执行它里面的任务

//1、创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
//2、创建组
dispatch_group_t group = dispatch_group_create();
     // 3、往组里面的队列添加任务(注意:在执行notify之前最起码要向队列中放置一个任务才可以,否则,notify里面的任务不会等待小组里面的其他任务执行完才执行。)
dispatch_group_async(group, queue, ^{
     NSLog(@"这是第一个任务。。。线程是:%@, 是否主线程:%d", [NSThread currentThread], [[NSThread currentThread] isMainThread]);
});

dispatch_group_notify(group, queue, ^{
NSLog(@”我是最后一个任务,组里面的其他任务都执行完毕之后,我就会执行”);
});
复制代码
dispatch_barrier_async() //将任务添加到队列中,此任务执行的时候,其他任务停止执行
复制代码
// 数据库的读取。。。可以并发执行,通过 GCD 里面的并行队列去实现
// 数据库的写入。。。。只能串行执行,通过 GCD 里面的串行队列去实现
// 但是真正的项目,肯定是既有数据的读取,也有数据库的写入。。。如何解决该问题:dispatch_barrier_async 在它之前的任务可以并发去执行,在他之后的任务也可以去并发执行
dispatch_queue_t queue = dispatch_queue_create(“concurrentTest”,DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@”这是第一个读取数据的任务。。。线程是:%@, 是否主线程:%d”, [NSThread currentThread], [[NSThread currentThread] isMainThread]);
});

dispatch_barrier_async(queue, ^{
    NSLog(@"正在在数据库里写东西,不要打扰我");
});

dispatch_async(queue, ^{

NSLog(@”这是第二个读取数据的任务。。。线程是:%@, 是否主线程:%d”, [NSThread currentThread], [[NSThread currentThread] isMainThread]);
});
复制代码
dispatch_once() //任务添加到队列中,但任务在程序运行过程中,只执行一次
  dispatch_once: 该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整型,实际上作为BOOL使用)。它还接收一个希望在应用的生命周期内仅被调度一次的代码块。

  dispatch_once 不仅意味着代码仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题。

  运用代码(单例的完整定义)

复制代码
static MyHandle *handle = nil;

  • (MyHandle *)sharedMyHandle
    {
    // 在GCD中只执行一次,用于记录内容是否执行过
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
    handle = [MyHandle new];
    });

    return handle;
    }
    复制代码
    dispatch_sync() //同步任务 将任务添加到队列中,block不执行完,下面代码不会执行
      与 dispatch_async() 的区别:

       dispatch_async() 不等 block 体执行完,就去执行下面的代码,会在另外的线程中执行

       dispatch_sync() 会等待 block 体执行完成之后,才会去执行 block 体外面的代码,会在当前的线程中执行,当前线程有可能是主线程,也有可能是子线程

dispatch_async_f() //将任务添加到队列中,任务是函数非block
复制代码
// 函数
void function(void * str){
NSLog(@”这是一个函数,%s”,str);
}

// 第一个参数:队列
// 第二个参数:函数参数的内容
// 第三个参数:函数
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async_f(queue, @”passValue”, function);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值