IOS 多线程的四种创建方案及比较

简单的整理了一下,多线程的创建方式,它们之间的优缺点,以及在项目中我们在什么样的场景下选择哪一种方式。水平有限,写得比较浅显易懂,太深入的可以自己去学习、去钻研,没有什么是学不会的,前提只要你肯学。废话不多说,let's go!!!

创建线程的的方案有pthread,NSThread,GCD,NSOperation,那么我就依次说一下每种方案有什么优缺点,及它们是怎样创建线程的。

1.0 pthread

a. 简介

pthread(POSIX thread)表示跨平台的的线程接口,适用于Unix\Linux\Windows等系统,是跨平台和可移植的,使用的语言是C语言,线程的生命周期是由程序员管理的,适用难度很大,所以几乎是不用的。

b. 创建子线程

所用函数:
int pthread_create(pthread_t * __restrict, const pthread_attr_t * __restrict,
        void *(*)(void *), void * __restrict);

参数:
参数1:线程的编号(地址)
参数2:线程属性,传入指向线程属性的指针地址。
参数3:新线程要执行的函数(任务),传入函数地址,即函数名。
参数4: 给调用用的函数传递的参数。
返回值:
返回int类型的值,0表示创建新线程成功,反之,创建新线程失败,返回失败的编号。
很多C语言框架里面并不是非零即真原则;因为他们认为成功的结果只有一个,但是失败的原因有很多。

demo
- (void)pthreadDemo {
    
    pthread_t ID;
    int returnValue = pthread_create( &ID, NULL, pthreadAction, NULL);
    if (returnValue == 0) {
        NSLog(@"线程创建成功");
    } else {
        NSLog(@"线程创建失败");
    }
}
void *pthreadAction(void *param) {
    NSLog(@"%@",[NSThread currentThread]);
    return NULL;
}

打印结果

2018-10-18 11:09:41.738469+0800 多线程Demo[8228:2385086] 线程创建成功
2018-10-18 11:09:41.738770+0800 多线程Demo[8228:2385731] <NSThread: 0x600000268840>{number = 3, name = (null)}
2018-10-18 11:09:41.903999+0800 多线程Demo[8228:2385086] 线程创建成功
2018-10-18 11:09:41.904245+0800 多线程Demo[8228:2385735] <NSThread: 0x600000260bc0>{number = 4, name = (null)}

从上述结果看出,创建新线程成功。

c.还有一点我们需要注意的是__bridge使用,例:

NSString *ocString = @"tianyao";
int returnValue = pthread_create( &ID, NULL, pthreadAction, (__bridge void *)(ocString));

在混合开发的时候,在C和OC或者swift之间传递数据,需要使用__bridge进行桥接,它的目的就是告诉编译器怎样管理内存。因为在ARC开发模式下,OC和swift编译器在编译的时候,根据代码的结构,自动的添加retain/release/autorelease。但是,ARC 只负责管理 OC 部分的内存管理,而不负责C语言代码的内存管理。因此,如果使用的 C 语言框架出现 retain/create/copy/new 等字样的函数,大多都需要 release,否则会出现内存泄漏。
像上述代码,在c的函数里面传递OC的字符串,就需要用到__bridge,因为c的内存需要手动释放,而OC的出了作用域就会自动释放,这样就会出问题,所以使用__bridge就是告诉编译器C函数中的OC代码的内存管理交给C处理了,你不用管了。

2.0 NSThread

a. 简介

NSThread的使用更加面向对象,简单易用,可以直接操作线程对象,使用的语言是OC,但是线程的生命周期是由程序员自行管理的,所以偶尔会使用。

b. 创建线程的三种方式

  • 对象方法创建
    手动开启线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo:) object:@"tianyao"]
// 手动开启线程
[thread start]
  • 类方法创建
    自动开启线程,这样的话就无法获取线程对象
[NSThread detachNewThreadSelector:@selector(demo:) toTarget:self withObject:@"tianyao"];
  • NSObject(NSThreadPerformAdditions) 的分类创建
    方便任何继承自NSObject的对象,都可以很容易的调用线程方法,也是无法获取线程对象
[self performSelectorInBackground:@selector(demo:) withObject:@"tianyao"];

c. 线程属性

  • name: 线程名称
    当线程执行的方法的内部出现异常时,可以记录当前和异常的线程。
  • stackSize:栈区大小
    可以通过[NSThread currentThread].stackSize = 512 * 1024设置栈区的大小(必须是4KB的倍数)。
  • isMainThread: 是否主线程
  • threadPriority: 线程优先级
    它是一个浮点数,范围是0~1.0,1.0表示最高优先级,0.0表示最低优先级,系统默认的优先级是0.5,线程的优先级并不能绝对的保证优先级高的线程执行完所有的代码,它只是提高了优先级高的代码执行代码的概率。
  • qualityOfService:服务质量(IOS8推出)
    NSQualityOfServiceUserInteractive - 用户交互,例如绘图或者处理用户事件
    NSQualityOfServiceUserInitiated - 用户需要
    NSQualityOfServiceUtility - 实用工具,用户不需要立即得到结果
    NSQualityOfServiceBackground - 后台
    NSQualityOfServiceDefault - 默认,介于用户需要和实用工具之间
注意

开发中最好不好修改优先级,不要相信用户交互服务质量

3.0 GCD

a. 简介(IOS4.0后推出)

GCD(Grand Central Dispatch)是苹果公司为多核的并行运算提出的解决方案,会自动利用更多的CPU内核(比如双核、四核),是纯C语言的,提供了很多强大的函数,能够自动的管理线程的生命周期(创建线程、调度任务、销毁线程),在平时的工作中经常使用。

b. GCD核心

GCD的核心就是将任务添加到队列中。
  • 队列
    串行队列:任务按顺序有序的执行。就像单行车道,车只能一辆一辆的按照顺序行驶
    并发队列:可以让多个任务并发执行。那么并发队列就可类比于多行车道。
  • 任务
    任务的执行可分为两种方式
    同步执行:在当前线程中依次执行任务。
    异步执行:创建一个新线程来执行任务。

c. 串行队列

串行同步:
- (void)serialSyncDemo {
    // 串行队列
    /*
     参数1:队列名称
     参数2:队列类型
     */
    dispatch_queue_t serialQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_SERIAL);
    for(int i = 0; i < 5; i++){
        dispatch_sync(serialQueue, ^{
            NSLog(@"%d %@", i, [NSThread currentThread]);
        });
    }
}

打印结果:

2018-10-18 15:58:53.085081+0800 多线程Demo[8994:2663401] 0 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.085400+0800 多线程Demo[8994:2663401] 1 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.086193+0800 多线程Demo[8994:2663401] 2 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.086402+0800 多线程Demo[8994:2663401] 3 <NSThread: 0x60400006cd80>{number = 1, name = main}
2018-10-18 15:58:53.086700+0800 多线程Demo[8994:2663401] 4 <NSThread: 0x60400006cd80>{number = 1, name = main}
得出结论:串行同步不具备开启新线程的能力,任务按照顺序执行。
串行异步:
- (void)serialAsyncDemo {
    dispatch_queue_t serialQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
    for(int i = 0; i < 5; i++){
        dispatch_async(serialQueue, ^{
            NSLog(@"%d %@", i, [NSThread currentThread]);
        });
    }
}

打印结果:

2018-10-18 16:13:58.676331+0800 多线程Demo[9064:2685387] 0 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.678881+0800 多线程Demo[9064:2685387] 1 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.679375+0800 多线程Demo[9064:2685387] 2 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.679847+0800 多线程Demo[9064:2685387] 3 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
2018-10-18 16:13:58.680204+0800 多线程Demo[9064:2685387] 4 <NSThread: 0x60000047ac80>{number = 3, name = (null)}
得出结论:串行异步会开启一条子线程,任务一次执行。

d. 并行队列

并行同步:
- (void)concurrentSyncDemo {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
    for(int i = 0; i < 5; i++){
        dispatch_sync(concurrentQueue, ^{
            NSLog(@"%d %@", i, [NSThread currentThread]);
        });
    }
}

打印结果:

2018-10-18 16:43:00.811969+0800 多线程Demo[9161:2713449] 0 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.812157+0800 多线程Demo[9161:2713449] 1 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.812328+0800 多线程Demo[9161:2713449] 2 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.812867+0800 多线程Demo[9161:2713449] 3 <NSThread: 0x60000007fac0>{number = 1, name = main}
2018-10-18 16:43:00.813012+0800 多线程Demo[9161:2713449] 4 <NSThread: 0x60000007fac0>{number = 1, name = main}
得出结论:并行同步不会开启新线程,任务在当前线程依次执行。
并行异步:
- (void)concurrentAsyncDemo {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
    for(int i = 0; i < 5; i++){
        dispatch_async(concurrentQueue, ^{
            NSLog(@"%d %@", i, [NSThread currentThread]);
        });
    }
}

打印结果:

2018-10-18 17:02:30.044475+0800 多线程Demo[9208:2730824] 2 <NSThread: 0x604000462380>{number = 4, name = (null)}
2018-10-18 17:02:30.044706+0800 多线程Demo[9208:2730823] 1 <NSThread: 0x604000462000>{number = 5, name = (null)}
2018-10-18 17:02:30.044829+0800 多线程Demo[9208:2730825] 0 <NSThread: 0x60000027c440>{number = 3, name = (null)}
2018-10-18 17:02:30.045005+0800 多线程Demo[9208:2730822] 3 <NSThread: 0x60000027f140>{number = 6, name = (null)}
2018-10-18 17:02:30.045009+0800 多线程Demo[9208:2730834] 4 <NSThread: 0x60000027f400>{number = 7, name = (null)}
得出结论:并行异步会开启多条线程,任务不按顺序执行。

e. 主队列

主队列是专门在主线程上面调度任务的队列,会随着程序启动一起创建,不会开启新的线程,以先进先出的方式执行任务

主队列只有当主线程空闲的时候才会调度任务,也就是说当主线程有任务正在执行时,无论主队列被添加了任何任务都不会被执行。
主队列同步
- (void)mainSyncDemo {
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    NSLog(@"start");
    dispatch_sync(mainQueue, ^{
        NSLog(@"%@", [NSThread currentThread]);
    });
    NSLog(@"end");
}

执行上面的代码会造成死锁,程序崩溃。因为主队列上面的代码只有当主线程空闲的时候才会执行,又因为是同步,代码按顺序执行,当主线程执行到主队列的代码的时候,主线程此时不是处于空闲的状态,所以没法执行主队列的代码,这就造成了主队列和主线程之间的相互等待,造成死锁。

解决死锁的办法: 就是将主队列的代码放到子线程中,不让其阻碍主线程的执行,这样等主线程空闲下来的时候,就可以去执行主队列上面的代码。
- (void)mainSyncDemo {
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    NSLog(@"start");
    dispatch_async(dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT), ^{
        dispatch_sync(mainQueue, ^{
            NSLog(@"%@", [NSThread currentThread]);
        });
    });
    NSLog(@"end");
} 

打印结果:

2018-10-18 18:03:12.375942+0800 多线程Demo[9377:2796585] start
2018-10-18 18:03:12.376192+0800 多线程Demo[9377:2796585] end
2018-10-18 18:03:12.376542+0800 多线程Demo[9377:2796585] <NSThread: 0x60400006acc0>{number = 1, name = main}
主队列异步
- (void)mainAsyncDemo {
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    NSLog(@"start");
    dispatch_async(mainQueue, ^{
        NSLog(@"%@", [NSThread currentThread]);
    });
    NSLog(@"end");
}

打印结果:

2018-10-18 18:10:35.030586+0800 多线程Demo[9435:2808199] start
2018-10-18 18:10:35.030802+0800 多线程Demo[9435:2808199] end
2018-10-18 18:10:35.031140+0800 多线程Demo[9435:2808199] <NSThread: 0x60000006ac40>{number = 1, name = main}
得出结论:主线程异步不会创建新的线程,任务在主线程上面依次执行。

f. 全局队列

全局队列又叫全局并发队列,是系统为了方便程序员开发提供的,其工作状态与并发队列一致,无论 MRC & ARC 都不需要考虑释放。

全局队列同步:
- (void)globalSync {
    /*
    
        参数1:A quality of service 服务质量
            iOS 8.0及以后
            QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)
            QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)
            QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的)
            QOS_CLASS_UTILITY 0x11, 实用工具(专门用来处理耗时操作!)
            QOS_CLASS_BACKGROUND 0x09, 后台
            QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 适配
            iOS 7.0及以前
            DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
            DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
            DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
            DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级
        参数2:Reserved for future use 未来使用:为未来保留使用的,应该永远传入0
     
     */
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    for(int i = 0; i < 5; i++){
        dispatch_sync(globalQueue, ^{
            NSLog(@"%d %@", i, [NSThread currentThread]);
        });
    }
}

打印结果:

2018-10-19 10:56:41.119513+0800 多线程Demo[10930:3237013] 0 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.119773+0800 多线程Demo[10930:3237013] 1 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.120436+0800 多线程Demo[10930:3237013] 2 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.120701+0800 多线程Demo[10930:3237013] 3 <NSThread: 0x600000064440>{number = 1, name = main}
2018-10-19 10:56:41.120832+0800 多线程Demo[10930:3237013] 4 <NSThread: 0x600000064440>{number = 1, name = main}
得出结论:全局同步不会创建新的现场,程序在当前现场按顺序执行。
全局队列异步:
- (void)globalAsync {
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
    for(int i = 0; i < 5; i++){
        dispatch_async(globalQueue, ^{
            NSLog(@"%d %@", i, [NSThread currentThread]);
        });
    }
}

打印结果:

2018-10-19 10:58:54.635833+0800 多线程Demo[10964:3242388] 1 <NSThread: 0x604000460340>{number = 4, name = (null)}
2018-10-19 10:58:54.635987+0800 多线程Demo[10964:3242022] 0 <NSThread: 0x604000274f80>{number = 3, name = (null)}
2018-10-19 10:58:54.636380+0800 多线程Demo[10964:3242393] 2 <NSThread: 0x600000464b40>{number = 5, name = (null)}
2018-10-19 10:58:54.636981+0800 多线程Demo[10964:3242394] 3 <NSThread: 0x600000464bc0>{number = 7, name = (null)}
2018-10-19 10:58:54.637505+0800 多线程Demo[10964:3242395] 4 <NSThread: 0x60400027dc00>{number = 6, name = (null)}

得出结论:全局异步会创建多条线程,任务不按顺序执行。

g. GCD阻塞(Barrier)

应用场景:主要用于多个异步操作完成之后,统一对非线程安全的对象(例如:NSMutableArray,NSMutableDictionary等)做处理。适用于大规模数据的I/O操作。举例说明:

NSMutableArray *_imageArr = [NSMutableArray array];
- (void)barrierDemo {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
    for(int i = 0; i < 2500; i++){
        dispatch_async(concurrentQueue, ^{
            NSString *name = [NSString stringWithFormat:@"%02d.jpg",i%10 + 1];
            NSURL *url = [[NSBundle mainBundle]URLForResource:name withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *img = [UIImage imageWithData:data];
            NSLog(@"%@ %@",name,[NSThread currentThread]);
            [self.imageArr addObject:img];
        });
    }

执行上述代码程序会崩溃,原因是NSMutableArray是非线程安全的,如果出现两个线程同时向数组中添加对象,程序就会崩溃。解决的方案如下:

- (void)barrierDemo {
    dispatch_queue_t concurrentQueue = dispatch_queue_create("tianyao", DISPATCH_QUEUE_CONCURRENT);
    for(int i = 0; i < 2500; i++){
        dispatch_async(concurrentQueue, ^{
            NSString *name = [NSString stringWithFormat:@"%02d.jpg",i%10 + 1];
            NSURL *url = [[NSBundle mainBundle]URLForResource:name withExtension:nil];
            NSData *data = [NSData dataWithContentsOfURL:url];
            UIImage *img = [UIImage imageWithData:data];
            NSLog(@"%@ %@",name,[NSThread currentThread]);
            dispatch_barrier_async(concurrentQueue, ^{
                [self.imageArr addObject:img];
            });
        });
    }
}

dispatch_barrier_async可以保证同一时间内只有一条线程执行block内的代码,也就是说在其之前添加的block全部执行完毕之后,才在同一个线程顺序执行,从而保证了非线程安全的对象的正确操作。

f. GCD延迟操作

dispatch_after这个函数默认是异步执行的。
- (void)afterDemo {
    NSLog(@"start");
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 执行的任务
    });
    NSLog(@"end");
    // 拆分
    /*
     参数1 : dispatch_time_t when,表示延迟的时间
     参数2 : dispatch_queue_t queue,表示任务执行的队列
     参数3 : dispatch_block_t block,表示线程要执行的任务
     */
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_block_t block = ^ {
       // 执行的任务
    };
    dispatch_after(time, queue, block);
}

上述代码dispatch_after里面的代码延迟一秒执行。

g. GCD一次执行

GCD的一次执行运用最多的场景就是创建单例,核心就是dispatch_once_t,它的里面有一把锁,能够保证线程的安全。

苹果公司推荐使用
单例:

一个应用程序里面有且只有一个这样的实例对象,例如:网络请求,音乐播放器等。单例一旦创建就会一直存在,直到app退出,单例存在静态区,所以不能滥用。
向我们应用程序中经常使用的好多,也都是单例。例如:

 [NSNotificationCenter defaultCenter];
 [NSUserDefaults standardUserDefaults];
 [UIApplication sharedApplication];
 [NSFileManager defaultManager];

如果不使用GCD的话,也可以这样创建单例,使用互斥锁

+ (instancetype)sharedTool {
    // 添加互斥锁
    @synchronized (self) {
        if (instance == nil) {
            instance = [[self alloc] init];
        }
    }
    return instance;
}

开发中一般不使用这种方式,因为互斥锁使用的是线程同步的原理,线程之间需要等待,相比dispatch_once效率不高。

懒汉式和饿汉式单例
static id instance;
// 懒汉式单例:使用时才会创建
+ (instancetype)sharedTool {
    static id instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

// 饿汉式单例:重写initialize这个类方法,在类第一次使用的时候就会创建
// initialize会在类第一次被使用时调用, 且调用是线程安全的
+ (void)initialize {
    instance = [[self alloc] init];
}
+ (instancetype)sharedTool {
    return instance;
}

有时候我们会看到有些代码会重写allocWithZone和copyWithZone。例如:

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
        static id instance;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            instance = [[self alloc] init];
        });
        return instance;
}

因为是担心合作开发中,别人会使用TYOnce *tools = [[TYOnce alloc] init]; [tools copy]这种方式来创建单例,但是一般正常的程序员都知道我们创建单例的方式都是使用TYOnce *tools = [TYOnce sharedTool];这种方式,所以这种顾虑基本上可以不用考虑。

h. 调度组

监听一组异步任务是否执行结束,在其执行结束后得到统一的通知。例如:监听几部电影同时下载:

- (void)groupDemo {
    //调度组
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_group_async(group, queue, ^{
        NSLog(@"下载第1部电影 %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载第2部电影 %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载第3部电影 %@",[NSThread currentThread]);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"电影全部下载完成");
    });
}

打印结果:

2018-10-19 15:20:07.473239+0800 05-调度组[11616:3513382] 下载第1首歌曲 <NSThread: 0x604000278b80>{number = 3, name = (null)}
2018-10-19 15:20:07.473239+0800 05-调度组[11616:3513384] 下载第3首歌曲 <NSThread: 0x60400027a980>{number = 4, name = (null)}
2018-10-19 15:20:07.474253+0800 05-调度组[11616:3513381] 下载第2首歌曲 <NSThread: 0x60400027be00>{number = 5, name = (null)}
2018-10-19 15:20:07.474631+0800 05-调度组[11616:3513290] 歌曲下载完成了

由打印的结果可以看出,任务是异步执行的,在三部电影全部下载完成后,可以得到统一的通知。

调度组的执行原理:实现监听一组异步任务是否执行结束
/*
    enter等于eave : 监测成功
    enter多于leave : 监测失效
    enter小于leave : 程序崩溃
 */
- (void)groupDemo {
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载第1部电影 %@",[NSThread currentThread]);
        [NSThread sleepForTimeInterval:2.0];
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载第2部电影 %@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, queue, ^{
        NSLog(@"下载第3部电影 %@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"电影全部下载完成");
    });
}

4.0 NSOperation

a. 简介(IOS2.0后推出)

NSOperation是OC语言中基于GCD的面向对象的封装,使用起来比GCD更加简单(面向对象),提供了一些用GCD不好实现的功能(例如:添加依赖),能够自动的管理线程的生命周期,在平时的工作中经常使用。

苹果推荐使用

NSOperation是一个抽象类,所以无法直接使用,因为它的方法只有声明没有实现。核心就是将操作添加到队列当中。
使用时其实我们是对NSOperation子类的使用,它的子类:

NSInvocationOperation;
NSBlockOperation;
// 自定义operation
NSOperation;

b. NSInvocationOperation的使用

  • 试例一
- (void)operationDemo {
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(demo) object:nil];
    [op start];
}
- (void)demo {
    NSLog(@"%@", [NSThread currentThread]);
}

打印结果:

2018-10-19 16:25:58.252280+0800 多线程Demo[11856:3583208] <NSThread: 0x604000078680>{number = 1, name = main}
得出结论: [op start]方法,会在当前线程执行selector方法。
  • 试例二
- (void)operationDemo {
    NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo) object:nil];
    // 队列: 并发队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    // 把操作添加到队列
    [queue addOperation:op];
}
- (void)demo {
    NSLog(@"%@", [NSThread currentThread]);
}

打印结果:

2018-10-19 16:32:03.564602+0800 多线程Demo[11895:3592479] <NSThread: 0x60400026dc00>{number = 3, name = (null)}
得出结论:将操作添加到队列,默认是异步执行。
  • 试例三:验证队列的并发性
- (void)operationDemo {
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    for(int i = 0;i< 5;i++){
        NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(demo) object:nil];
        [queue addOperation:op];
    }
}
- (void)demo {
    NSLog(@"%@", [NSThread currentThread]);
}

打印结果:

2018-10-19 16:36:15.437686+0800 多线程Demo[11936:3600229] <NSThread: 0x60000026c7c0>{number = 6, name = (null)}
2018-10-19 16:36:15.437641+0800 多线程Demo[11936:3600228] <NSThread: 0x604000276c80>{number = 4, name = (null)}
2018-10-19 16:36:15.437744+0800 多线程Demo[11936:3600234] <NSThread: 0x60000026c400>{number = 3, name = (null)}
2018-10-19 16:36:15.437808+0800 多线程Demo[11936:3600232] <NSThread: 0x60000026c780>{number = 5, name = (null)}
2018-10-19 16:36:15.438371+0800 多线程Demo[11936:3600308] <NSThread: 0x604000279840>{number = 7, name = (null)}
得出结论:会开启多条线程,不是顺序执行的。与GCD中并发异步执行效果一样。

c. NSBlockOperation的使用

  • 试例一
- (void)blockDemo {
    // 封装NSOperation对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    [op start];
}

得出结论:

2018-10-19 17:12:50.353660+0800 多线程Demo[12060:3633014] <NSThread: 0x600000067e80>{number = 1, name = main}
得出结论: [op start]方法,操作只在当前线程执行。
  • 试例二
- (void)blockDemo {
    // 队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    // 封装NSOperation对象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    // 把操作添加到队列
    [queue addOperation:op];
}

打印结果:

2018-10-19 17:15:00.513886+0800 多线程Demo[12079:3637107] <NSThread: 0x60400027e5c0>{number = 3, name = (null)}
得出结论:将操作添加到队列,操作默认是异步的。
  • 试例三
- (void)blockDemo {
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    for(int i = 0;i< 5;i++){
        NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
            NSLog(@"%@",[NSThread currentThread]);
        }];
        [queue addOperation:op];
    }
}

打印结果:

2018-10-19 17:19:32.740483+0800 多线程Demo[12120:3644204] <NSThread: 0x6000002698c0>{number = 5, name = (null)}
2018-10-19 17:19:32.740487+0800 多线程Demo[12120:3644205] <NSThread: 0x604000269fc0>{number = 6, name = (null)}
2018-10-19 17:19:32.740483+0800 多线程Demo[12120:3644203] <NSThread: 0x60400026b800>{number = 4, name = (null)}
2018-10-19 17:19:32.740524+0800 多线程Demo[12120:3644201] <NSThread: 0x600000269d00>{number = 3, name = (null)}
2018-10-19 17:19:32.740646+0800 多线程Demo[12120:3644202] <NSThread: 0x604000269f80>{number = 7, name = (null)}
得出结论:队列默认是并发性的。
  • 试例四
    在实际开发时,如果要使用到NSOperationQueue,可以直接定义成全局的队列
// 全局队列
@property (nonatomic, strong) NSOperationQueue *queue;
- (NSOperationQueue *)queue {
    if (_queue == nil) {
        _queue = [[NSOperationQueue alloc]init];
    }
    return _queue;
}
- (void) blockDemo {
    [self.queue addOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
}
  • 试例五
    添加操作服务质量和监听操作执行结束
    操作服务质量:解决队列里面的操作有更多的机会被队列调度执行,类似于线程优先级。
    监听操作执行结束:这个监听的回调是异步的。
- (void)OperationDemo {
    // 操作1
    NSBlockOperation *firstOp = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"firstOp == %d %@",i,[NSThread currentThread]);
        }
    }];
    // 监听操作1什么时候执行结束 : 这个监听是异步监听
    [firstOp setCompletionBlock:^{
        NSLog(@"操作1执行结束 == %@",[NSThread currentThread]);
    }];
    // 设置操作优先级 : 设置为最高
    firstOp.qualityOfService = NSQualityOfServiceUserInteractive;
    [self.queue addOperation: firstOp];
    // 操作2
    NSBlockOperation *secondOp = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 10; i++) {
            NSLog(@"secondOp == %d %@",i,[NSThread currentThread]);
        }
    }];
    // 设置操作优先级 : 设置为最低
    secondOp.qualityOfService = NSQualityOfServiceBackground;
    [self.queue addOperation: secondOp];
}
  • 试例六
    添加操作执行块
- (void)OperationDemo {
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@",[NSThread currentThread]);
    }];
    // 添加操作的执行块 : 这个执行块也是属于op对象的
    // 当添加操作执行块后, 这个执行块里面的任务就新开子线程执行
    [op addExecutionBlock:^{
        NSLog(@"ExecutionBlock %@",[NSThread currentThread]);
    }];
    // 在没有添加执行块之前,这个操作是在当前线程执行
    [op start];
}

打印结果:

2018-10-22 10:18:26.121270+0800 多线程Demo[16173:4731928] ExecutionBlock <NSThread: 0x6000002745c0>{number = 3, name = (null)}
2018-10-22 10:18:26.121270+0800 多线程Demo[16173:4730187] <NSThread: 0x600000261dc0>{number = 1, name = main}
得出结论:由打印的结果可以看出执行块里面的任务开启了新的线程执行,在没有添加执行块之前,这个操作是在当前线程执行的。

d. NSOperation的高级应用

  • 队列的最大并发数
self.queue.maxConcurrentOperationCount = 3;

设置最大并发数为三,每次只能调度三个操作。

  • 队列的暂停、继续和取消全部
// 队列暂停
self.queue.suspended = YES;

只能够暂停还没有执行的操作,正在执行的操作没有办法暂停。如果先暂停队列,再添加操作到队列,队列不会调度操作执行。

// 队列继续
self.queue.suspended = NO;
// 队列全部取消
[self.queue cancelAllOperations];

这个方法只能够取消还没有执行的操作,正在执行的操作没有办法取消。如果要取消,需要自定义NSOperation。队列取消全部操作时,会有一定的时间延迟。

  • 操作依赖
  • (void)dependencyDemo {
    // 登陆->付费->下载
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"登陆");
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"付费");
    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下载");
    }];
    // 设置操作依赖
    [op3 addDependency:op2];
    [op2 addDependency:op1];
    // [op1 addDependency:op3]; 注意1:不要搞成循环依赖
    // 不同的队列之中的操作也可以设置操作依赖
    [[NSOperationQueue mainQueue]addOperation:op1];
    // 把操作添加到队列
    [self.queue addOperations:@[op2,op3] waitUntilFinished:NO];
    }
    很多时候我们希望自己所执行的事情能按照顺序执行,比如我们我们下载一首付费的歌曲,需要我们按顺序执行登录-付费-下载的操作,由于这些操作我们放在后台去执行,所以它们执行的顺序是不确定的,看CPU怎么调度,所以我们为了达到我们的要求。就需要添加操作依赖来实现。
注意:

不能循环建立操作间依赖关系。否则,队列不调度操作执行。
操作间可以跨队列建立依赖关系。
要将操作间的依赖建立好了之后,再添加到队列中。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容