iOS底层系列24 -- 多线程的实现

  • 本文主要探索NSThreadGCDNSOperation这三种实现多线程的方式;

NSThread

  • NSthread是苹果官方提供面向对象的线程操作技术,是对thread的上层封装,比较偏向于底层,简单方便,可以直接操作线程对象,使用频率较少;
NSThread的初始化创建
【第一种】:alloc方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //第一种方式: alloc 需手动开启
    YYThread *thread = [[YYThread alloc]initWithTarget:self selector:@selector(task:) object:@"thread_name_01"];
    [thread start];
}
- (void)task:(NSObject *)obj{
    NSLog(@"%@ - %@", obj, [NSThread currentThread]);
}
  • LLDB调试结果:
Snip20210323_24.png
  • YYThread自定义线程继承自NSThread;
  • 会创建子线程执行任务(task:方法);
  • 需要手动的启动,手动调用start方法;
【第二种】:detachNewThreadSelector类方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [NSThread detachNewThreadSelector:@selector(task:) toTarget:self withObject:@"thread_name_02"];
}
- (void)task:(NSObject *)obj{
    NSLog(@"%@ - %@", obj, [NSThread currentThread]);
}
  • LLDB调试结果:
Snip20210323_25.png
  • detachNewThreadSelector类方法会生成子线程执行任务(task:方法);
【第三种】:performSelector方法
  • 调用performSelectorOnMainThread方法,若当前线程是主线程,会在主线程中立刻执行selector任务方法;
- (void)viewDidLoad {
    [super viewDidLoad];
    //当前线程为主线程
    [self performSelectorOnMainThread:@selector(task_perform:) withObject:@"thread_name_04" waitUntilDone:YES];
}
- (void)task_perform:(NSObject *)obj{
    NSLog(@"%@ - %@", obj, [NSThread currentThread]);
}
Snip20210323_26.png
  • 调用performSelectorOnMainThread方法,若当前线程是子线程,waitUntilDone = YES时,会阻塞当前子线程,当主线程执行完selector任务方法时,解除子线程的阻塞,继续执行子线程中的任务;
- (void)viewDidLoad {
    [super viewDidLoad];
    YYThread *thread = [[YYThread alloc]initWithTarget:self selector:@selector(task:) object:@"thread_name_01"];
    [thread start];
}

- (void)task:(NSObject *)obj{
    NSLog(@"%@ - %@", obj, [NSThread currentThread]);
    //当前线程为子线程
    [self performSelectorOnMainThread:@selector(task_perform:) withObject:@"thread_name_04" waitUntilDone:YES];
}

- (void)task_perform:(NSObject *)obj{
    NSLog(@"%@ - %@", obj, [NSThread currentThread]);
}
Snip20210323_27.png
  • task方法中的执行完打印之后,子线程阻塞,当主线程执行完task_perform之后,子线程解除阻塞,然后继续执行,子线程任务执行完,子线程销毁;
  • 调用performSelectorOnMainThread方法,若当前线程是子线程,waitUntilDone = NO时,不会阻塞当前子线程,子线程中任务继续执行,selector任务方法在主线程中执行;
  • 将上面的代码,waitUntilDone入参改成NO,LLDB结果如下:
Snip20210323_28.png
  • task方法中的执行完打印之后,子线程不会阻塞,继续执行,子线程任务执行完成后直接销毁;
  • 主线程执行task_perform方法;
  • 总结:
    • 若当前线程为子线程,performSelectorOnMainThread:withObject:waitUntilDone方法中的参数waitUntilDone决定了是否阻塞当前子线程,即同步/异步;
    • 调用performSelectorInBackground方法,开启新的线程在后台执行test方法任务;
    • 调用performSelector:onThread方法,在指定的线程执行任务;
NSThread常见方法和属性
  • [NSThread currentThread] 获取当前线程信息
  • NSThread sleepUntilDate: 当前线程休眠到指定时间
  • NSThread sleepForTimeInterval: 当前线程休眠多长时间
  • [NSThread exit] 当前线程退出
  • [NSThread mainThread] 获取主线程
  • [NSThread isMainThread] 判断当前线程是否时主线程
  • [NSThread mainThread] 获取主线程
  • thread.isExecuting 线程是否在执行
  • thread.isCancelled 线程是否被取消
  • thread.isFinished 线程是否完成
  • thread.isMainThread 线程是否是主线程
  • thread.threadPriority 线程的优先级,取值范围0.0-1.0,默认优先级0.5,1.0表示最高优先级,优先级高,CPU调度的频率高

GCD

dispatch_after
  • 表示队列中的block任务延迟指定的时间执行;
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"aaaaa");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"3333");
    });
}
  • 主队列中的block任务延迟3秒再执行;
dispatch_once
  • 保证在App运行期间,block中的代码只会执行一次;
//创建单例
+ (instancetype)sharedInstance{
    static dispatch_once_t onceToken;
    static id instance;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc]init];
    });
    return instance;
}
dispatch_apply
  • 将耗时任务,以指定的次数,追加到队列中,并等待任务全部执行结束;
  • 使用场景:for循环中有大量耗时任务;
【第一种解决方案】:在for循环中耗时任务,都异步开辟子线程去执行
- (void)viewDidLoad {
    [super viewDidLoad];
    //在for循环中每一次任务都放到子线程中执行
    for (int i = 0; i < 1000; i++) {
        dispatch_async(dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT), ^{
            [NSThread sleepForTimeInterval:1]; // 模拟耗时操作
            NSLog(@"%d -- %@", i, [NSThread currentThread]);
        });
    }
}
  • 从打印结果来看,1000个耗时任务能很快执行完,但是开辟了将近70个子线程来执行耗时任务,这样会耗费大量的资源,容易导致App的崩溃,所以第一种方案不可取;
【第二种解决方案】:使用dispatch_apply函数
- (void)viewDidLoad {
    [super viewDidLoad];
    
     dispatch_async(dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT), ^{
        dispatch_apply(1000, dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT), ^(size_t index) {
            [NSThread sleepForTimeInterval:1];//模拟耗时操作
            NSLog(@"%zu -- %@", index, [NSThread currentThread]);
        });
    });
}
  • 使用dispatch_apply函数将block任务循环1000次,加入并发队列中;
  • 从打印结果来看,1000个耗时任务执行完成需要耗费很长一段时间,但是只开启了五六个子线程循环执行耗时任务,不会耗费大量资源;
  • 现在定义一个数组,然后使用dispatch_apply函数循环打印数组中元素,代码实现如下所示:
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"-- begin --");
    NSArray *arr = @[@"a", @"b", @"c", @"d", @"e"];
    dispatch_queue_t queue = dispatch_queue_create("com.jarypan.gcdsummary", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_apply(arr.count, queue, ^(size_t index) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"index = %zu, str = %@ -- %@", index, arr[index], [NSThread currentThread]);
    });
    NSLog(@"-- end --");
}
  • LLDB调试结果:
Snip20210324_29.png
  • 使用dispatch_apply函数往并发队列中循环添加了5个任务;
  • 开启多个子线程去执行任务,在执行任务时会阻塞主线程,只有当dispatch_apply添加到并发队列中的任务全部执行完,才会解除主线程的阻塞,主线程才能继续执行;
  • 为了不阻塞主线程的执行,我们将dispatch_apply函数放到异步子线程中去执行,代码改造之后如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"-- begin --");
    NSArray *arr = @[@"a", @"b", @"c", @"d", @"e"];
    dispatch_queue_t queue = dispatch_queue_create("com.jarypan.gcdsummary", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"current thread -- %@", [NSThread currentThread]);
        dispatch_apply(arr.count, queue, ^(size_t index) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"index = %zu, str = %@ -- %@", index, arr[index], [NSThread currentThread]);
        });
        NSLog(@" current thread - end ");
    });
    NSLog(@"-- end --");
}
  • LLDB调试结果:
Snip20210324_30.png
  • 可以看到dispatch_apply函数会阻塞当前子线程的继续执行当追加到并发队列中任务执行完成,才会解除子线程的阻塞
  • 任务循环添加到并发队列,但是任务执行却是串行执行,并没有实现我们想要的并发执行,(为什么?),如果将上面的自定义并发队列改成全局队列就可以实现任务的并发执行
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"-- begin --");
    NSArray *arr = @[@"a", @"b", @"c", @"d", @"e"];
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSLog(@"current thread -- %@", [NSThread currentThread]);
        dispatch_apply(arr.count, queue, ^(size_t index) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"index = %zu, str = %@ -- %@", index, arr[index], [NSThread currentThread]);
        });
        NSLog(@" current thread - end ");
    });
    NSLog(@"-- end --");
}
  • LLDB调试结果:
Snip20210324_31.png
dispatch_apply会造成死锁的场景
第一种:在主线程使用dispatch_apply往主队列中添加任务
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_apply(10, dispatch_get_main_queue(), ^(size_t index) {
        NSLog(@"%zu", index);
    });
}
  • dispatch_apply往主队列中添加block任务,而viewDidLoad已在主线中执行,block任务会等待viewDidLoad执行完再执行;
  • 由于dispatch_apply会阻塞主线程的继续执行,viewDidLoad会等待block任务执行完才会继续执行完成;造成viewDidLoad与block任务的相互等待形成死锁;
  • dispatch_apply可以看成dispatch_sync
第二种情况:使用dispatch_apply往异步串行队列中追加任务
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_queue_t queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{//block1
        dispatch_apply(10, queue, ^(size_t index) {//block2
            NSLog(@"%zu", index);
        });
    });
}
  • dispatch_apply看成dispatch_sync,具体原因在 iOS底层系列23 -- 多线程的函数与队列中的案例分析已经详细分析过了;
dispatch_group调度组
  • dispatch_group_asyncdispatch_group_enterdispatch_group_leavedispatch_group_notifydispatch_group_wait配合使用;
  • 使用场景:多个异步网络请求,回调统一处理;
- (void)viewDidLoad {
    [super viewDidLoad];

    self.bookId = @"11186718404761703yw";
    
    //创建队列组
    self.group = dispatch_group_create();
    //创建队列
    self.queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(self.group, self.queue, ^{
        [self loadBookView];
    });
    
    dispatch_group_async(self.group, self.queue, ^{
        [self loadBookEvalute];
    });
    
    dispatch_group_async(self.group, self.queue, ^{
        [self loadBookRecommandList];
    });
    
    long timeout = dispatch_group_wait(self.group, dispatch_time(DISPATCH_TIME_NOW, 1 *NSEC_PER_SEC));
    NSLog(@"timeout = %ld", timeout);
    if (timeout == 0) {
        NSLog(@"按时完成任务");
    }else{
        NSLog(@"超时");
    }
    
    dispatch_group_notify(self.group, self.queue, ^{
        NSLog(@"同步所有异步请求");
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"刷新UI");
        });
    });
}
- (void)loadBookView{
    ///请求一
    dispatch_group_enter(self.group);
    [YYRequestBookApi requestBookViewWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_group_leave(self.group);
    } fail:^(NSString * _Nonnull error) {
        dispatch_group_leave(self.group);
    }];
}

- (void)loadBookEvalute{
    ///请求二
    dispatch_group_enter(self.group);
    [YYRequestBookApi requestBookEvaluteWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_group_leave(self.group);
    } fail:^(NSString * _Nonnull error) {
        dispatch_group_leave(self.group);
    }];
}

- (void)loadBookRecommandList{
    ///请求三
    dispatch_group_enter(self.group);
    [YYRequestBookApi requestBookRecommentWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_group_leave(self.group);
    } fail:^(NSString * _Nonnull error) {
        dispatch_group_leave(self.group);
    }];
}
  • dispatch_group_enterdispatch_group_leave需要配对使用;
  • dispatch_group_wait设置调度组等待的超时时间(即等多久);
  • 当三个网络请求的回调数据都回来之后,在dispatch_group_notify函数中做统一处理;
dispatch_barrier栅栏函数
  • 可实现并发队列中的任务,按照顺序执行;
  • dispatch_barrier分为两种分别为dispatch_barrier_syncdispatch_barrier_async
- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_queue_create("com.lyy.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@" start ");
    dispatch_async(queue, ^{
        sleep(5);
        NSLog(@"---1--- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"---2--- %@",[NSThread currentThread]);
    });
    
    dispatch_barrier_sync(queue, ^{
        NSLog(@" 栅栏函数 -- %@",[NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"---3--- %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"---4--- %@",[NSThread currentThread]);
    });
    NSLog(@" end ");
}
  • 并发队列中追加四个任务,任务1是耗时任务,子线程休眠5秒;dispatch_barrier_sync栅栏同步添加在四个任务的正中间;
Snip20210324_32.png
  • 可以看出dispatch_barrier_sync会阻塞主线程的继续执行,当其block执行完成时,主线程解除阻塞继续执行;
  • dispatch_barrier_sync栅栏同步将并发队列的任务执行分割开,必须等任务1(耗时任务)与任务2都执行完毕时才会执行栅栏同步函数,栅栏同步函数执行完成之后再执行任务3,任务4。
  • 将上面的dispatch_barrier_sync栅栏同步改成dispatch_barrier_async栅栏异步,调试结果如下:
Snip20210324_34.png
  • 可以看出dispatch_barrier_async栅栏异步不会阻塞主线程,end会立即执行且会开辟子线程,在子线程中执行;
  • dispatch_barrier_async栅栏异步将并发队列的任务执行分割开,必须等任务1(耗时任务)与任务2都执行完毕时才会执行栅栏异步函数,栅栏异步函数执行完成之后再执行任务3,任务4。
dispatch_semaphore_t信号量
  • 主要涉及三个函数如下:
    • dispatch_semaphore_create()创建信号量,并设定一个初始信号量值;
    • dispatch_semaphore_signal()发送信号量,信号量值+1;
    • dispatch_semaphore_wait() 等待信号量,信号量值-1;
  • 当信号量值 < 0时,会阻塞当前线程的继续执行当信号量>=0时,当前线程解除阻塞,会继续执行接下来的逻辑;
应用场景一:配合dispatch_group实现多个异步网络请求,回调统一处理,实现如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    self.bookId = @"11186718404761703yw";
    
    self.semphore = dispatch_semaphore_create(0);
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        [self loadBookView];
    });
    
    dispatch_group_async(group, queue, ^{
        [self loadBookEvalute];
    });
    
    dispatch_group_async(group, queue, ^{
        [self loadBookRecommandList];
    });
    
    dispatch_group_notify(group, queue, ^{
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"刷新UI");
        });
    });
}
- (void)loadBookView{
    ///请求一
    [YYRequestBookApi requestBookViewWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_semaphore_signal(self.semphore);
    } fail:^(NSString * _Nonnull error) {
        dispatch_semaphore_signal(self.semphore);
    }];
    dispatch_semaphore_wait(self.semphore, DISPATCH_TIME_FOREVER);
}

- (void)loadBookEvalute{
    ///请求二
    [YYRequestBookApi requestBookEvaluteWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_semaphore_signal(self.semphore);
    } fail:^(NSString * _Nonnull error) {
        dispatch_semaphore_signal(self.semphore);
    }];
     dispatch_semaphore_wait(self.semphore, DISPATCH_TIME_FOREVER);
}

- (void)loadBookRecommandList{
    ///请求三
    [YYRequestBookApi requestBookRecommentWithBookId:self.bookId success:^(id  _Nonnull response) {
        dispatch_semaphore_signal(self.semphore);
    } fail:^(NSString * _Nonnull error) {
        dispatch_semaphore_signal(self.semphore);
    }];
     dispatch_semaphore_wait(self.semphore, DISPATCH_TIME_FOREVER);
}
应用场景二:实现异步并发(串行)队列中的任务,依次顺序执行,如果是队列中的任务是网络请求,也是可以控制网络请求回调的顺序执行
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_semaphore_t sem = dispatch_semaphore_create(0);
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(1);
        NSLog(@"任务1:%@",[NSThread currentThread]);
        dispatch_semaphore_signal(sem);//2
    });
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//1
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        sleep(1);
        NSLog(@"任务2:%@",[NSThread currentThread]);
        dispatch_semaphore_signal(sem);//4
    });
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);//3
    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{//5
        sleep(1);
        NSLog(@"任务3:%@",[NSThread currentThread]);
    });
}
Snip20210324_35.png
  • 信号量的初始值为0,首先执行到1,等待信号量信号量减1,变成-1会阻塞当前主线程的继续执行,所以dispatch_semaphore_wait后面的代码执行不了;
  • 第一个dispatch_async函数开始执行,当执行到2时,发送信号量,信号量+1变成0,那么主线程解除阻塞继续执行,来到3等待信号量信号量减1,又变成-1,又会阻塞当前主线程,后面的代码不能执行;
  • 第二个dispatch_async函数开始执行,当执行到4时,发送信号量,信号量+1变成0,那么主线程解除阻塞继续执行,执行第三个dispatch_async函数;
应用场景三:控制异步并发的数量
- (void)viewDidLoad {
    [super viewDidLoad];
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(5);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < 20; i++) {
        dispatch_async(queue, ^{
            //等待降低信号量
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            NSLog(@"run task %d -- %@",i,[NSThread currentThread]);
            sleep(2);
            NSLog(@"complete task %d -- %@",i,[NSThread currentThread]);
            //提高信号量
            dispatch_semaphore_signal(semaphore);
        });
    }
}
  • 当信号量初始值设置为3时,LLDB调试如下:
Snip20210324_36.png
  • 当信号量初始值设置为5时,LLDB调试如下:
Snip20210324_37.png
  • 可以看到信号量的初始值,控制了异步任务的并发数,即同一时刻任务执行的数量;
dispatch_source_t
  • dispatch_source_t主要用于计时操作,计时精准度比NSTimer高,其原因是因为它创建的timer不依赖于RunLoop;
  • 常见API方法如下:
    • dispatch_source_create: 创建事件源
    • dispatch_source_set_event_handler: 设置数据源回调
    • dispatch_source_merge_data: 设置事件源数据
    • dispatch_source_get_data: 获取事件源数据
    • dispatch_resume: 继续
    • dispatch_suspend: 挂起
    • dispatch_cancle: 取消
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //1.创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    //2.创建timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    self.timer = timer;
    //3.设置timer首次执行时间,间隔,精确度
    dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 2.0*NSEC_PER_SEC, 0);
    //4.设置timer事件回调
    dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"GCDTimer");
    });
    //5.默认是挂起状态,需要手动激活
    dispatch_resume(self.timer);
}

NSOperation

  • NSOperation是基于GCD之上的更高一层封装,NSOperation需要配合NSOperationQueue来实现多线程;
NSInvocationOperation与NSBlockOperation
  • NSOperation是抽象类,在实际使用的中需要使用它的两个子类NSInvocationOperationNSBlockOperation
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //创建NSInvocationOperation实例对象 并绑定操作方法
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(task) object:nil];
    //start开始执行操作
    [operation start];
    
    //创建NSBlockOperation实例对象 在Block中添加任务操作
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:1];//模拟耗时操作
        NSLog(@"NSBlockOperation excute - %@", [NSThread currentThread]);
    }];
    //start开始执行操作
    [operation1 start];
}
- (void)task{
    NSLog(@"NSInvocationOperation excute - %@",[NSThread currentThread]);
}
  • LLDB调试结果:
Snip20210324_38.png
  • 单独使用NSInvocationOperation与NSBlockOperation时,在主线程中执行任务操作;
  • NSBlockOperation还可以调用addExecutionBlock函数,给NSBlockOperation添加其他block任务,这些任务在子线程中并发执行
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@", [NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"2---%@", [NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"3---%@", [NSThread currentThread]);
    }];

    [operation start];
}
  • LLDB调试结果:
Snip20210324_39.png
自定义NSOperation
#import 

@interface YYOperation : NSOperation

@end
#import "YYOperation.h"

@implementation YYOperation

//重写main方法
- (void)main{
    if (!self.isCancelled) {
        [NSThread sleepForTimeInterval:2];
        NSLog(@"main --- %@", [NSThread currentThread]);
    }
}

@end
- (void)viewDidLoad {
    [super viewDidLoad];
    
    YYOperation *operation = [[YYOperation alloc]init];
    [operation start];
}
  • main函数中的任务操作在主线程中执行;
NSOperationQueue的使用
  • 其使用步骤如下:
    • 1、创建任务:先将需要执行的任务封装到NSOperation对象中;
    • 2、创建队列:创建NSOperationQueue;
    • 3、将任务加入到队列中:将NSOperation操作对象添加到NSOperationQueue中;
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //1.获取主队列
    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    //2.创建操作
    //使用 NSInvocationOperation 创建操作1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
    
    //使用 NSInvocationOperation 创建操作2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];
    
    //使用 NSBlockOperation 创建操作3
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
            [NSThread sleepForTimeInterval:2]; 
            NSLog(@"3---%@", [NSThread currentThread]); 
    }];
    
    [op3 addExecutionBlock:^{
            [NSThread sleepForTimeInterval:2]; 
            NSLog(@"4---%@", [NSThread currentThread]); 
    }];
    
    //3.调用addOperation: 添加所有操作到主队列中
    [mainQueue addOperation:op1]; //[op1 start]
    [mainQueue addOperation:op2]; //[op2 start]
    [mainQueue addOperation:op3]; //[op3 start]
}
- (void)task1{
     [NSThread sleepForTimeInterval:2]; 
      NSLog(@"1---%@", [NSThread currentThread]); 
}
- (void)task2{
      [NSThread sleepForTimeInterval:2]; // 模拟耗时操作
      NSLog(@"2---%@", [NSThread currentThread]); // 打印当前线程
}
  • LLDB调试结果:
Snip20210324_42.png
  • 创建的操作添加到NSOperationQueue中,不用再调用start方法来启动执行,是由系统去启动执行操作
  • 添加到主队列中的操作都是在主线程中,依次执行,由于op3调用addExecutionBlock添加的操作会并发执行;
  • 将上面的队列改成自定义队列 NSOperationQueue *queue = [[NSOperationQueue alloc]init],LLDB调试如下:
Snip20210324_43.png
  • 可以看到操作任务添加到自定义队列中,是在子线程中并发执行;
往NSOperationQueue中直接添加block任务
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    for (int i = 0; i < 5; i++) {
        [queue addOperationWithBlock:^{
            NSLog(@"%@---%d", [NSThread currentThread], i);
        }];
    }
}
  • LLDB调试结果如下:
Snip20210324_44.png
  • NSOperationQueue调用addOperationWithBlock直接往操作队列中追加block任务;
  • 由于添加到自定义操作队列,所以所有任务并发执行;
往NSOperationQueue设置并发数
  • 在GCD中我们通过信号量dispatch_semaphore_t,控制异步并发队列的并发数;
  • 在NSOperationQueue中我们直接通过其属性maxConcurrentOperationCount,控制自定义NSOperationQueue操作队列的并发数;
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"lyy.queue";
    queue.maxConcurrentOperationCount = 2;
    
    for (int i = 0; i < 10; i++) {
        [queue addOperationWithBlock:^{ // 一个任务
            [NSThread sleepForTimeInterval:2];
            NSLog(@"%d-%@",i,[NSThread currentThread]);
        }];
    }
}
  • LLDB调试结果如下:
Snip20210324_45.png
  • 当自定义操作队列的最大并发数设置为2时,表明同一时刻只开辟两个子线程执行任务;当最大并发数设置为3时,LLDB调试结果如下:
Snip20210324_46.png
NSOperation设置依赖
  • 任务操作之间添加依赖,代码实现如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"请求token");
    }];
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着token,请求数据1");
    }];
    
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"拿着数据1,请求数据2");
    }];
    
    [op2 addDependency:op1];
    [op3 addDependency:op2];
    
    [queue addOperations:@[op1,op2,op3] waitUntilFinished:YES];
    
    NSLog(@"执行完了 -- 我要干其他事");
}
  • LLDB调试结果如下:
Snip20210324_47.png
  • op2依赖于op1,表明只有先执行op1,才能执行op2;
  • op3依赖于op2,表明只有先执行op2,才能执行op3;
NSOperation设置优先级
  • NSOperation设置优先级只会让CPU有更高的几率调用,不是说设置高就一定全部先执行;
  • 常见优先级有如下:从高到低
    • NSQualityOfServiceUserInteractive:用户交互级别:最高级别,通常用于响应用户操作的UI处理,如将图像绘制到屏幕上;
    • NSQualityOfServiceUserInitiated:用户发起级别:由用户发起的仅次于UI的任务,用户希望立即响应并在此任务完成后进行下一步操作。如远程内容载入;
    • NSQualityOfServiceUtility:工具级别:由用户或自动发起,不必要立即响应,不阻止用户交互。通常以一个可见的进度条来标示进度,例如预加载内容、上传或大量文件操作(如媒体导入);
    • NSQualityOfServiceBackground:后台级别:通常不是由用户发起、用户不可见的。例如备份、索引、数据同步等;
    • NSQualityOfServiceDefault:将从其他来源推测QoS,如果无法推断,将使用 UserInitiated 到 Utility中的一个;
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
//            sleep(1);
            NSLog(@"第一个操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    
    //设置最低优先级
    op1.qualityOfService = NSQualityOfServiceBackground;
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    //设置最高优先级
    op2.qualityOfService = NSQualityOfServiceUserInteractive;
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:op1];
    [queue addOperation:op2];
}
  • LLDB调试结果如下:
Snip20210324_50.png
  • 由于op2的优先级比op1优先级高,所以先执行op2;
  • 若在op1中加入睡眠一秒的操作,且设置op1的优先级最高,op2优先级最低,代码如下所示:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            sleep(1);
            NSLog(@"第一个操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    
    //设置最高优先级
    op1.qualityOfService = NSQualityOfServiceUserInteractive;
    
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread]);
        }
    }];
    //设置最低优先级
    op2.qualityOfService = NSQualityOfServiceBackground;
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:op1];
    [queue addOperation:op2];
}
  • LLDB调试结果如下:
Snip20210324_51.png
  • 看到即使op1的优先级最高,op2优先级最低,由于op1有阻塞操作,最终op2首先执行,表明NSOperation设置优先级只会让CPU有更高的几率调用,不是说设置高就一定先执行,还要考虑其他因素;
NSOperationQueue实现线程间通信
  • 代码实现如下:
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.name = @"lyy.queue";
    [queue addOperationWithBlock:^{
        //子线程执行耗时操作
        NSLog(@"耗时操作 -- %@", [NSThread currentThread]);
        //切换到主线程刷新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            NSLog(@"刷新UI -- %@", [NSThread currentThread]);
        }];
    }];
}
  • LLDB调试结果如下:
Snip20210324_49.png

你可能感兴趣的:(iOS底层系列24 -- 多线程的实现)