美文网首页
主题七《多线程》

主题七《多线程》

作者: 东方奇迹 | 来源:发表于2020-09-20 11:15 被阅读0次

1、概念

进程:正在运行的一个应用程序
线程:在进程中真正执行任务的
关系:包含(进程中至少有一条线程)
串行:针对一个线程中有多个任务>按顺序执行
并行:多个线程的执行情况>同时执行

2、进程和线程的区别

  • 进程是CPU分配资源的最小单位
  • 线程是CPU执行任务的最小单位
  • 一个应用至少要有一个进程
  • 一个进程至少要有一个线程
  • 同一个进程内的线程共享进程的资源

3、多线程的原理(假象)

  • 同一时间CPU只能处理1条线程
  • 多线程并发其实就是CPU快速的在多条线程之间切换

4、多线程的优缺点

  • 优点:
    1、防止线程阻塞、增加运行效率
    2、提高资源利用率(CPU、内存利用率)

  • 缺点:
    1、占空间(默认情况下,主线程占用1M,子线程占用512KB)、花时间(创建线程大约需要90毫秒的创建时间)
    2、如果开启大量的线程,会降低程序的性能(消耗大量的CPU资源,CPU会在N多条线程之间调度,每条线程被调度执行的频次会降低),(建议3-5条线程,推荐3条)
    3、程序设计复杂(线程之间的通信、多线程的数据共享)

5、主线程、子线程

  • 主线程:一个iOS程序运行后,默认会开启一条线程,称为"主线程"或"UI线程"。作用:1、显示、刷新UI界面;2、处理UI事件(比如点击事件、滚动事件、拖拽事件等)。

  • 子线程:除了主线程的线程称为子线程,又称"后台线程"或"非主线程"。作用:执行耗时操作。

    //01 获得主线程
    NSThread *mainThread = [NSThread mainThread];
    NSLog(@"%@",mainThread);
    
    //02 获得当前线程(执行当前任务的线程)
    NSThread *currentThread = [NSThread currentThread];
    NSLog(@"%@",currentThread);

    //常识:通常情况,几乎多有的任务(方法)都是在主线程中执行,除非认为创建
    //03 判断线程是否是主线程
    //03.1 打印 number == 1 主线程,否则就是子线程
    //03.2 通过类方法判断
    NSLog(@"%d",[NSThread isMainThread]);
    //03.3 通过对象方法判断
    NSLog(@"%d",[currentThread isMainThread]);
    
    //主线程注意点:
    //01 耗时操作不能放在主线程中执行,应该开子线程执行
    //02 UI相关的操做都必须放在主线程中执行

6、多线程的实现方案

WechatIMG13.jpeg
  • pthread


    WechatIMG14.jpeg
  • NSThread

/**
 知识点说明
 1、线程创建的几种方法
    01、直接创建子线程 [[NSThread alloc]initWithTarget:                      优缺点:代码量更大、能够拿到线程对象
    02、分离出一条子线程 [NSThread detachNewThreadSelector:    优缺点:无法拿到线程对象进行详细设置
    03、开启后台线程 [self performSelectorInBackground:                 优缺点:无法拿到线程对象进行详细设置
 
 2、设置线程的属性(名称、优先级)
     01、设置线程名称  .name
     02、设置线程的优先级  .threadPriority  范围0.0(最低) ~ 1.0(最高) ,默认0.5,优先级跟高的线程被CPU调度到的概率会更高
 
 3、线程的生命周期:当线程内部的任务执行完毕的时候,线程对象会被自动释放
 
 */

- (void)createThead1 {
    //01 创建线程对象
    
    /* 参数说明
     * 第一个参数:目标对象(传什么要看方法选择器属于谁)
     * 第二个参数:方法选择器 要执行的任务(方法)
     * 第三个参数:调用函数需要传递的参数
     **/
    
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run:) object:@"哈哈"];
    
    //02 启动线程
    
    [thread start];
}

- (void)createThead2 {
    
    //分离出一条子线程
    [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:@"哈哈"];
    
}

- (void)createThead3 {
    
    //开启后台线程
    [self performSelectorInBackground:@selector(run:) withObject:@"哈哈"];
    
}

- (void)run:(NSString *)str {
    
    NSLog(@"run%@======str%@",[NSThread currentThread],str);
    
}

1、线程的状态和控制线程状态

截屏2020-07-09 下午9.14.44.png 截屏2020-07-09 下午9.18.18.png

2、多线程的安全隐患(多个线程同一时间访问同一资源)

截屏2020-10-30 下午1.52.50.png 截屏2020-10-30 下午1.53.12.png

安全隐患分析

截屏2020-07-09 下午9.30.31.png

安全隐患解决

截屏2020-07-09 下午9.32.29.png 截屏2020-07-09 下午9.54.58.png 截屏2020-07-09 下午9.41.46.png

3、原子和非原子属性

截屏2020-07-09 下午10.02.02.png

4、线程间通信

截屏2020-07-11 上午11.55.34.png
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    //创建子线程(3种方法)
    [NSThread detachNewThreadSelector:@selector(downLoad) toTarget:self withObject:nil];
    
}
- (void)downLoad {
    NSLog(@"downLoad---%@",[NSThread currentThread]); 
    
    /**
     App Transport Security Settings
     Allow Arbitrary Loads  YES
     */
    
    /**
     计算代码块执行时间的方法:
     第一种:
     NSDate *start = [NSDate date];
     NSDate *end = [NSDate date];
     NSLog(@"%f",[end timeIntervalSinceDate:start]);
     第二种:
     CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
     CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
     NSLog(@"%f",end - start);
     */

    //01 确定URL地址
    NSURL *url = [NSURL URLWithString:@"https://ns-strategy.cdn.bcebos.com/ns-strategy/upload/fc_big_pic/part-00182-626.jpg"];
        
    //02 把图片的二进制数据下载到本地
    NSData *data = [NSData dataWithContentsOfURL:url];
            
    //03 把图片的二进制格式转换为UIImage
    UIImage *img = [UIImage imageWithData:data];
    
    //报错:把和UI相关的操做放在后台线程中处理
    //04 回到主线程显示图片
    /**
     参数说明
     第一个参数:方法选择器 回到主线程要做什么(方法)
     第二个参数:调用函数需要传递的参数
     第三个参数:是否等待该方法执行完毕才继续往下执行 YES:等待
     */
    //第一种方法
    //[self performSelectorOnMainThread:@selector(showImge:) withObject:img waitUntilDone:YES];
    //第二种方法
    [self performSelector:@selector(showImge:) onThread:[NSThread mainThread] withObject:img waitUntilDone:NO];
    //简便方法
    //[self.imgV performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:img waitUntilDone:YES];
    
    NSLog(@"____end____");
}
- (void)showImge:(UIImage *)image {
    
    NSLog(@"%@",[NSThread currentThread]);
    
    for (int i = 0; i < 100; i++) {
        NSLog(@"___%d___",i);
    }
    
    self.imgV.image = image;

}

  • GCD
截屏2020-07-11 下午12.40.58.png 截屏2020-07-11 下午1.23.17.png 截屏2020-07-11 下午1.34.08.png 截屏2020-07-11 下午1.58.12.png

1、GCD核心概念

任务:要做什么
队列:存放任务

2、GCD使用步骤

(1)创建队列
(2)封装任务,把任务添加到队列。使用函数(同步函数|异步函数)封装任务。

封装任务的函数:
(1)同步函数:dispatch_sync
区别:
1)、不具备开线程的能力,不能开线程
2)、执行任务的方式:同步

(2)异步函数:dispatch_async
区别:
1)、具备开线程的能力,可以开线程
2)、执行任务的方式:异步

GCD中的队列:
(1)并发队列:任务可以同时执行。(只要第一个任务取出来之后,不用等待执行完,就可以接着取第二个任务)

  • 1)自己创建dispatch_queue_create DISPATCH_QUEUE_CONCURRENT
  • 2)全局并发队列dispatch_get_global_queue

(2)串行队列:任务必须一个接着一个执行。(第一个任务取出来之后,必须等待该任务执行完,才可以接着取第二个任务)

  • 1)自己创建dispatch_queue_create DISPATCH_QUEUE_SERIAL
  • 2)主队列dispatch_get_main_queue
//异步函数 + 并行队列:会开启多条子线程,所有的任务并发执行
//注意;开几条线程并不是有任务的数量决定的,是由GCD内部自动决定的
- (void)asyncConcurrent {
    //01 创建队列
    /**
     参数说明
     第一个参数:C语言的字符串 给队列起个名字(建议:com.520it.www.DownloadQueue)
     第二个参数:类型
     DISPATCH_QUEUE_CONCURRENT 并发队列
     DISPATCH_QUEUE_SERIAL: 串行队列
     */
    //dispatch_queue_t queue = dispatch_queue_create("com.520it.www.DownloadQueue", DISPATCH_QUEUE_CONCURRENT);
    /**
     GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建
     全局并发队列和创建出来的并发队列没有太大区别,用哪个都可以
     */
    //DISPATCH_QUEUE_PRIORITY_DEFAULT == 0 默认优先级
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    //02 封装任务,把任务添加到队列
    dispatch_async(queue, ^{
        NSLog(@"1----%@",[NSThread currentThread]);
        
    });
    dispatch_async(queue, ^{
        NSLog(@"2----%@",[NSThread currentThread]);
        
    });
    dispatch_async(queue, ^{
        NSLog(@"3----%@",[NSThread currentThread]);
        
    });

    
    
}
//异步函数 + 串行队列:会开启一条子线程,所有的任务都在该子线程中串行执行
- (void)asyncSerial {
    //01 创建队列
    /**
     参数说明
     第一个参数:C语言的字符串 给队列起个名字(建议:com.520it.www.DownloadQueue)
     第二个参数:类型
     DISPATCH_QUEUE_CONCURRENT 并发队列
     DISPATCH_QUEUE_SERIAL: 串行队列
     */
    dispatch_queue_t queue = dispatch_queue_create("com.520it.www.DownloadQueue", DISPATCH_QUEUE_SERIAL);
    
    //02 封装任务,把任务添加到队列
    dispatch_async(queue, ^{
        NSLog(@"1----%@",[NSThread currentThread]);
        
    });
    dispatch_async(queue, ^{
        NSLog(@"2----%@",[NSThread currentThread]);
        
    });
    dispatch_async(queue, ^{
        NSLog(@"3----%@",[NSThread currentThread]);
        
    });

    
    
}
//同步函数 + 并行队列:不会开启子线程,所有的任务在当前线程中串行执行
- (void)syncConcurrent {
    //01 创建队列
    /**
     参数说明
     第一个参数:C语言的字符串 给队列起个名字(建议:com.520it.www.DownloadQueue)
     第二个参数:类型
     DISPATCH_QUEUE_CONCURRENT 并发队列
     DISPATCH_QUEUE_SERIAL: 串行队列
     */
    dispatch_queue_t queue = dispatch_queue_create("com.520it.www.DownloadQueue", DISPATCH_QUEUE_CONCURRENT);
    
    //02 封装任务,把任务添加到队列
    dispatch_sync(queue, ^{
        NSLog(@"1----%@",[NSThread currentThread]);
        
    });
    dispatch_sync(queue, ^{
        NSLog(@"2----%@",[NSThread currentThread]);
        
    });
    dispatch_sync(queue, ^{
        NSLog(@"3----%@",[NSThread currentThread]);
        
    });

    
    
}
//同步函数 + 串行队列:不会开启子线程,所有的任务在当前线程中串行执行
- (void)syncSerial {
    //01 创建队列
    /**
     参数说明
     第一个参数:C语言的字符串 给队列起个名字(建议:com.520it.www.DownloadQueue)
     第二个参数:类型
     DISPATCH_QUEUE_CONCURRENT 并发队列
     DISPATCH_QUEUE_SERIAL: 串行队列
     */
    dispatch_queue_t queue = dispatch_queue_create("com.520it.www.DownloadQueue", DISPATCH_QUEUE_SERIAL);
    
    //02 封装任务,把任务添加到队列
    dispatch_sync(queue, ^{
        NSLog(@"1----%@",[NSThread currentThread]);
        
    });
    dispatch_sync(queue, ^{
        NSLog(@"2----%@",[NSThread currentThread]);
        
    });
    dispatch_sync(queue, ^{
        NSLog(@"3----%@",[NSThread currentThread]);
        
    });

    
    
}
//异步函数 + 主队列:不会开线程,所有的任务都在主线程中串行执行
- (void)asyncMain {
    //01 获得主队列
    
    //特殊的串行队列(和主线程相关联),放到主队列中的任务都会放到主线程中去执行
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    //02 封装任务,把任务添加到队列
    dispatch_async(queue, ^{
        NSLog(@"1----%@",[NSThread currentThread]);
        
    });
    dispatch_async(queue, ^{
        NSLog(@"2----%@",[NSThread currentThread]);
        
    });
    dispatch_async(queue, ^{
        NSLog(@"3----%@",[NSThread currentThread]);
        
    });

    
}
//同步函数 + 主队列:死锁
- (void)syncMain {
    //01 获得主队列
    
    //特殊的串行队列(和主线程相关联),放到主队列中的任务必须在主线程中去执行
    /**
     当主队列中有任务的时候,主队列就会安排主线程来执行该任务,
     但是在调度之前会先检查主线程的状态(是否在忙),如果主线程当前在忙,
     那么就暂停调度,知道主线程空闲为止
     */
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    //02 封装任务,把任务添加到队列
    dispatch_sync(queue, ^{
        NSLog(@"1----%@",[NSThread currentThread]);
        
    });
    dispatch_sync(queue, ^{
        NSLog(@"2----%@",[NSThread currentThread]);
        
    });
    dispatch_sync(queue, ^{
        NSLog(@"3----%@",[NSThread currentThread]);
        
    });

    
}

任务的执行方式:
1) 同步执行:必须等当前任务执行完毕,才能执行后面的任务
2) 异步执行:不须等当前任务执行完毕,就能执行后面的任务
各种组合情况:
同步函数 + 串行队列:不会开启子线程,所有的任务在当前线程中串行执行
同步函数 + 并发队列:不会开启子线程,所有的任务在当前线程中串行执行
同步函数 + 主队列:死锁
异步函数 + 串行队列:会开启一条子线程,所有的任务都在该子线程中串行执行
异步函数 + 并发队列:会开启N条子线程,所有的任务都在该子线程中并发执行(⚠️线程的数量 != 任务的数量)
异步函数 + 主队列:不会开启子线程,所有的任务在主线程中串行执行

⚠️使用sync函数往当前串行队列中添加任务会卡住当前的串行队列(产生死锁)!

3、GCD线程间的通信

截屏2020-07-11 下午4.48.52.png
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    //01 创建队列 (串行队列有2种|并发队列有2种类)
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    //02 封装下载图片任务,并且添加到队列中
    dispatch_async(queue, ^{
        NSLog(@"Download---%@",[NSThread currentThread]);
        
        //03 创建URL
        NSURL *url = [NSURL URLWithString:@"https://ns-strategy.cdn.bcebos.com/ns-strategy/upload/fc_big_pic/part-00182-626.jpg"];

        //04 下载图片的二进制数据到本地
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        //05 转换格式
        UIImage *image = [UIImage imageWithData:data];
        
        //04 回到主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"UI---%@",[NSThread currentThread]);

            //设置图片
            self.imgV.image = image;
        });
        
    });



}

4、GCD常用函数

(1)、一次性代码

//一次性代码:整个程序运行过程中只会执行一次 + 本身是线程安全
//应用:单例模式
- (void)once {
    
    static dispatch_once_t onceToken;
    
    NSLog(@"onceToken----%ld",onceToken);
    
    //内部实现原理:判断onceToken的值 == null,来决定是否执行block中的任务
    dispatch_once(&onceToken, ^{
        
        NSLog(@"once-----");
        
    });
}

(2)、延迟

- (void)delay {
    
    NSLog(@"-----delay----");
    //延迟执行
//    [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
//    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:NO];
    
    //GCD中的延迟运行
    /**
     参数说明
     第一个参数:设置时间(GCD中的时间单位是纳秒)
     第二个参数:队列(决定block中的任务在哪个线程中执行,如果是主队列就是主线程,否则就在子线程)
     第三个参数:设置任务
     原理:哪个简单就是哪个
     A 先把任务提交到队列 ,然后等待2秒再执行     错误
     B 先等2秒,然后再把任务提交到队列                 正确
     
     */

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        NSLog(@"-----GCD----");

    });
}

(3)、快速迭代

截屏2020-07-11 下午8.40.17.png 截屏2020-07-11 下午8.39.07.png

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    /**
     参数说明
     第一个参数:遍历的次数
     第二个参数:队列
     */
    dispatch_apply(10, queue, ^(size_t index) {
       
        NSLog(@"%zu----%@----",index,[NSThread currentThread]);
        
    });

(4)、栅栏函数

- (void)barrier {
    
    //需求:有四个任务,要求开启多条线程来执行这些任务
    //新增需求:新的任务++++++,要求在12执行之后执行,要保证该任务执行完之后才能执行后面的34任务
    
    //栅栏函数:前面的任务并发执行,后边的任务也是并发执行
    //当前边的任务执行完毕之后执行栅栏函数中的任务,等该任务执行完毕之后执行后边的任务
    //⚠️不能使用全局并发队列,否则栅栏无效
    
    //01 获得队列
    dispatch_queue_t queue = dispatch_queue_create("com.520it.wwww.test", DISPATCH_QUEUE_CONCURRENT);
    
    //02 封装任务,并且添加到队列
    dispatch_async(queue, ^{
        
        NSLog(@"1----%@",[NSThread currentThread]);
        
    });
    dispatch_async(queue, ^{
        
        NSLog(@"2----%@",[NSThread currentThread]);
        
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"++++++");
    });
    
    dispatch_async(queue, ^{
        
        NSLog(@"3----%@",[NSThread currentThread]);
        
    });
    dispatch_async(queue, ^{
        
        NSLog(@"4----%@",[NSThread currentThread]);
        
    });

    
}

4、GCD队列组的基本使用

- (void)group {
    
    //00 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    //01 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("text", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("hehe", DISPATCH_QUEUE_CONCURRENT);
    
    //02 封装任务,添加到队列并监听任务的执行情况
    /**
     
     //01 封装任务
     //02 将任务添加到队列中
     //03 监听任务的执行情况
    dispatch_group_async(group, queue, ^{
        
    });
     //01 封装任务
     //02 将任务添加到队列中
    dispatch_async(queue, ^{
                
    });
     */
    
    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_async(group, queue2, ^{

        NSLog(@"4----%@----",[NSThread currentThread]);
        
    });
    dispatch_group_async(group, queue2, ^{

        NSLog(@"5----%@----",[NSThread currentThread]);
        
    });
    //03 拦截通知 当所有任务都执行完毕后,执行++++操做
    //队列:决定该block在哪个线程中处理(主队列:主线程 、 非主队列:子线程)
    //dispatch_group_notify 内部是异步执行的
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        
        NSLog(@"++++++");

    });
    
}

- (void)group {
    
    //00 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    //01 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("text", DISPATCH_QUEUE_CONCURRENT);
    
    //02 封装任务,添加到队列并监听任务的执行情况
    /**
    dispatch_group_async(group, queue, ^{
        NSLog(@"1----%@----",[NSThread currentThread]);
    });
    */
    
    //该方法后面的任务被队列组监听
    //dispatch_group_enter | dispatch_group_leave 必须成对使用
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"1----%@----",[NSThread currentThread]);
        //监听到该任务已执行完毕
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"2----%@----",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        NSLog(@"3----%@----",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    //03 拦截通知 当所有任务都执行完毕后,执行++++操做
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        
        NSLog(@"++++++");

    });
    
}

- (void)group {
    //需求:开子线程下载两张图片,合成图片,显示出来
    
    //01 创建队列组
    dispatch_group_t group = dispatch_group_create();
    
    //02 获得并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    //03 下载图片1
    __weak typeof(self) weakSelf = self;
    dispatch_group_async(group, queue, ^{
        //001 url
        NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594566524069&di=244245ccea25c321d5901722b2d61061&imgtype=0&src=http%3A%2F%2Ft7.baidu.com%2Fit%2Fu%3D3107828371%2C1763171280%26fm%3D193"];
        //002 data
        NSData *data = [NSData dataWithContentsOfURL:url];
        //003 转换
        weakSelf.img1 = [UIImage imageWithData:data];
        
        NSLog(@"download1---%@---",[NSThread currentThread]);
        
    });
    
    //04 下载图片2
    dispatch_group_async(group, queue, ^{
        //001 url
        NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1594566524069&di=78b7e72715d00004c0209ab9265a1305&imgtype=0&src=http%3A%2F%2Fn.sinaimg.cn%2Fsinacn11%2F488%2Fw533h755%2F20180331%2Ffa3a-fyssmme0013363.jpg"];
        //002 data
        NSData *data = [NSData dataWithContentsOfURL:url];
        //003 转换
        weakSelf.img2 = [UIImage imageWithData:data];
        
        NSLog(@"download2---%@---",[NSThread currentThread]);
        
    });

    //05 拦截通知合成图片
    dispatch_group_notify(group, queue, ^{
        
        //001 开启上下文
        UIGraphicsBeginImageContext(CGSizeMake(300, 300));
        
        //002 画图1、2
        [weakSelf.img1 drawInRect:CGRectMake(0, 0, 150, 300)];
        [weakSelf.img2 drawInRect:CGRectMake(150, 0, 150, 300)];

        NSLog(@"Combie---%@---",[NSThread currentThread]);
        //003 根据上下文得到图片
        UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
        
        //004 关闭上下文
        UIGraphicsEndImageContext();
        
        //06 显示图片(线程间通信)
        dispatch_async(dispatch_get_main_queue(), ^{
             
            //主线程刷新UI
            weakSelf.imgV.image = img;
            
            NSLog(@"UI---%@---",[NSThread currentThread]);
            
        });
    });

}

- (void)other {
    
    //01 队列
    dispatch_queue_t queue = dispatch_queue_create("Test", DISPATCH_QUEUE_CONCURRENT);
    
    //02 封装任务
    
    //封装任务block
    dispatch_async(queue, ^{

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

    });
    dispatch_async(queue, ^{

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

    });

    //封装任务函数
    dispatch_async_f(queue, "hahaha", run);
    dispatch_async_f(queue, "hehehe", run2);

}
//(*)|参数
void run(void *str) {
    
    NSLog(@"3--%p--%@",str,[NSThread currentThread]);

    
}
void run2(void *str) {
    
    NSLog(@"4--%p--%@",str,[NSThread currentThread]);
    
}

5、GCD中全局并发队列和create函数创建的并发队列主要区别

1.全局并发队列在整个应用程序中本身是默认存在的,并且有高级 默认 低级 后台四个优先级并发队列。而create函数创建的使我们自己实打实创建的
2.ios6.0前,GCD中使用create及retain的函数 最后都要一次release操作。而主队列 全局并发队列不需要我们手动release
3.栅栏函数只有在自己create的并发队列中使用 而不能与全局并发队列使用

6、单例模式

截屏2020-07-19 下午12.41.17.png

//
//  YSTool.h
//  CeshiProj
//
//  Created by YanShuang Jiang on 2020/7/19.
//  Copyright © 2020 WuHanOnePointOne. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface YSTool : NSObject<NSCopying,NSMutableCopying>

//提供类方法,方便外界访问
+(instancetype)shareTool;

@end

NS_ASSUME_NONNULL_END

//
//  YSTool.m
//  CeshiProj
//
//  Created by YanShuang Jiang on 2020/7/19.
//  Copyright © 2020 WuHanOnePointOne. All rights reserved.
//

#import "YSTool.h"

@implementation YSTool

//01、提供一个全局的静态变量(对外界隐藏)
static YSTool *_instance;

//02、重写alloc方法,保证永远只分配一次存储空间
//alloc -> allocWithZone(分配存储空间)
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    
    /**
    @synchronized (self) {
        if (_instance == nil) {
            _instance = [super allocWithZone:zone];
        }
    }
     */
    
    //只执行一次+线程安全
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super allocWithZone:zone];
    });
    
    return _instance;
}

//03、提供类方法
+ (instancetype)shareTool {
    return [[self alloc] init];
}

//04、重写copy方法
- (id)copyWithZone:(NSZone *)zone {
    return _instance;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    return _instance;
}

@end


//
//  YSTool.h
//  CeshiProj
//
//  Created by YanShuang Jiang on 2020/7/19.
//  Copyright © 2020 WuHanOnePointOne. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface YSTool : NSObject

//提供类方法,方便外界访问
+(instancetype)shareTool;

@end

NS_ASSUME_NONNULL_END


//
//  YSTool.m
//  CeshiProj
//
//  Created by YanShuang Jiang on 2020/7/19.
//  Copyright © 2020 WuHanOnePointOne. All rights reserved.
//

#import "YSTool.h"

@implementation YSTool

+ (instancetype)shareTool {
    
    //1、提供静态变量
    static YSTool *_instance;
    
    //2、一次性代码
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [self new];
    });
    
    //3、返回单例对象
    return _instance;
}

@end

  • NSOperation
截屏2020-07-19 下午1.21.58.png 截屏2020-07-19 下午1.22.22.png

1、操作队列的基本使用

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    //GCD队列:并发(自己创建|全局并发)|串行(自己创建|主队列)
    //操做队列:自定义队列|主队列
    /**
     自定义队列:[[NSOperationQueue alloc]init]
     特点:默认并发队列,但是可以控制让它变成一个串行队列
     主队列:[NSOperationQueue mainQueue]
     特点:串行队列,和主线程相关(凡是放到主队列中的任务都在主线程中执行)
     */
    
}

- (void)changeSerialQueue {
    
    //01、创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02、封装操做对象
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"1=====%@",[NSThread currentThread]);
        
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"2=====%@",[NSThread currentThread]);

    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"3=====%@",[NSThread currentThread]);

    }];
    
    //设置最大并发数对于任务数量大于1的操做是无效的
    //当操做中的任务数量>1的时候,会开启多条子线程和当前线程一起工作
    [operation3 addExecutionBlock:^{
        
        NSLog(@"4=====%@",[NSThread currentThread]);

    }];
    [operation3 addExecutionBlock:^{
        
        NSLog(@"5=====%@",[NSThread currentThread]);

    }];

    //设置最大并发数 == 同一时间最多有多少条线程在执行
    //maxConcurrentOperationCount == 0 不能执行任务
    //NSOperationQueueDefaultMaxConcurrentOperationCount = -1; -1指的是一个最大的值(表示不受限制)
    queue.maxConcurrentOperationCount = 1;
    
    //03、把操做添加到队列中,该方法内部会自动的调用start方法执行任务
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];

}

- (void)blockOperationWithQueue {
    
    //01、创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02、封装操做对象
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"1=====%@",[NSThread currentThread]);
        
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"2=====%@",[NSThread currentThread]);

    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"3=====%@",[NSThread currentThread]);

    }];
    
    //03、把操做添加到队列中,该方法内部会自动的调用start方法执行任务
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    
    //简便方法:该方法内部首先会把block中的任务封装成一个操做(Operation),然后把该操作直接添加到队列
    [queue addOperationWithBlock:^{
        
        NSLog(@"4=====%@",[NSThread currentThread]);

    }];
    
}

- (void)invocationOperationWithQueue {
    
    //01、创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02、封装操做
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download1) object:nil];
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download2) object:nil];
    NSInvocationOperation *operation3 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(download3) object:nil];

    //03、把操做添加到队列
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];

    //注意:开启几条子线程并不是由操做的数量决定的
    
}
- (void)download1 {
    NSLog(@"1====%@",[NSThread currentThread]);
}
- (void)download2 {
    NSLog(@"2====%@",[NSThread currentThread]);
}
- (void)download3 {
    NSLog(@"3====%@",[NSThread currentThread]);
}

2、操作队列的开始暂停恢复取消

//
//  ViewController.m
//  CeshiProj
//
//  Created by YanShuang Jiang on 2020/7/17.
//  Copyright © 2020 WuHanOnePointOne. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) NSOperationQueue *queue;

@end

@implementation ViewController

- (IBAction)startBtnClick:(id)sender {
    //01、创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02、封装操作
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        
        for (int i = 0; i < 5000; i++) {
            NSLog(@"1=====%@",[NSThread currentThread]);
        }
        
    }];
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        
        for (int i = 0; i < 5000; i++) {
            NSLog(@"2=====%@",[NSThread currentThread]);
        }

    }];
    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        
        for (int i = 0; i < 5000; i++) {
            NSLog(@"3=====%@",[NSThread currentThread]);
        }

    }];
    

    //设置最大并发数
    queue.maxConcurrentOperationCount = 1;
    
    //03、添加到队列
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    
    self.queue = queue;
}
- (IBAction)suspendBtnClick:(id)sender {
    
    //暂停 YES
    //只能暂停当前操做后面的操做,当前操做不可分割必须执行完毕
    //操作是有状态的
    [self.queue setSuspended:YES];
    
}
- (IBAction)resumBtnClick:(id)sender {
    
    //恢复 NO
    [self.queue setSuspended:NO];

}
- (IBAction)cancelBtnClick:(id)sender {
    
    //取消所有的操做
    //只能取消队列中处于等待状态的操做
    [self.queue cancelAllOperations];
    
}

@end

3、自定义操作

//
//  YSOperation.h
//  CeshiProj
//
//  Created by YanShuang Jiang on 2020/7/19.
//  Copyright © 2020 WuHanOnePointOne. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface YSOperation : NSOperation

@end

NS_ASSUME_NONNULL_END

//
//  YSOperation.m
//  CeshiProj
//
//  Created by YanShuang Jiang on 2020/7/19.
//  Copyright © 2020 WuHanOnePointOne. All rights reserved.
//

#import "YSOperation.h"

@implementation YSOperation

//重写内部的main方法来告诉自定义的操做任务是什么
- (void)main {
    
    NSLog(@"main=====%@",[NSThread currentThread]);
    
    for (int i = 0; i < 10000; i++) {
        NSLog(@"1=====%@",[NSThread currentThread]);
    }

    //官方建议:在自定义操作的时候每执行完一个耗时操作就判断一下当前操做是否被取消,如果被取消就直接返回
    if (self.isCancelled) {
        return;
    }
    
    for (int i = 0; i < 10000; i++) {
        NSLog(@"2=====%@",[NSThread currentThread]);
    }

    //官方建议:在自定义操作的时候每执行完一个耗时操作就判断一下当前操做是否被取消,如果被取消就直接返回
    if (self.isCancelled) {
        return;
    }
    
    for (int i = 0; i < 10000; i++) {
        NSLog(@"3=====%@",[NSThread currentThread]);
    }

    
}

@end

//
//  ViewController.m
//  CeshiProj
//
//  Created by YanShuang Jiang on 2020/7/17.
//  Copyright © 2020 WuHanOnePointOne. All rights reserved.
//

#import "ViewController.h"
#import "YSOperation.h"

@interface ViewController ()

@property (nonatomic, strong) NSOperationQueue *queue;

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    

}
- (IBAction)startBtnClick:(id)sender {
    //01、创建队列
    NSOperationQueue *queue1 = [[NSOperationQueue alloc]init];

    //02、封装操做(执行任务)
    YSOperation *op1 = [[YSOperation alloc]init];

    //04、添加队列(addOperation内部会调用start方法,start内部会调用main方法)
    [queue1 addOperation:op1];
    
    //自定义操做的好处:代码复用
    self.queue = queue1;
}
- (IBAction)suspendBtnClick:(id)sender {
    
    //暂停 YES
    //只能暂停当前操做后面的操做,当前操做不可分割必须执行完毕
    //操作是有状态的
    [self.queue setSuspended:YES];
    
}
- (IBAction)resumBtnClick:(id)sender {
    
    //恢复 NO
    [self.queue setSuspended:NO];

}
- (IBAction)cancelBtnClick:(id)sender {
    
    //取消所有的操做
    //只能取消队列中处于等待状态的操做
    [self.queue cancelAllOperations];
    
}

@end

//
//  YSThread.h
//  CeshiProj
//
//  Created by YanShuang Jiang on 2020/7/19.
//  Copyright © 2020 WuHanOnePointOne. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface YSThread : NSThread

@end

NS_ASSUME_NONNULL_END

//
//  YSThread.m
//  CeshiProj
//
//  Created by YanShuang Jiang on 2020/7/19.
//  Copyright © 2020 WuHanOnePointOne. All rights reserved.
//

#import "YSThread.h"

@implementation YSThread

- (void)main {
    
    NSLog(@"main======%@",[NSThread currentThread]);
    
}

@end


//
//  ViewController.m
//  CeshiProj
//
//  Created by YanShuang Jiang on 2020/7/17.
//  Copyright © 2020 WuHanOnePointOne. All rights reserved.
//

#import "ViewController.h"
#import "YSThread.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    //自定义线程
    YSThread *thread = [[YSThread alloc]init];
    //开启线程
    [thread start];

}

@end

4、操做依赖和监听

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    //01、创建队列
    NSOperationQueue *queue1 = [[NSOperationQueue alloc]init];
    NSOperationQueue *queue2 = [[NSOperationQueue alloc]init];
    
    //02、封装操做
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"1=====%@",[NSThread currentThread]);

    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"2=====%@",[NSThread currentThread]);

    }];
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"3=====%@",[NSThread currentThread]);

    }];
    NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"4=====%@",[NSThread currentThread]);

    }];
    NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
        
        NSLog(@"5=====%@",[NSThread currentThread]);

    }];

    //监听任务执行完毕
    op4.completionBlock = ^{
        
        NSLog(@"主人,你的电影已经下载好了,快点来看我吧!");
        
    };
    
    //03、设置操作依赖:4->3->2->1->5
    //可以跨队列依赖
    //⚠️不能设置循环依赖,结果就是两个任务都不执行
    [op5 addDependency:op1];
    [op1 addDependency:op2];
    [op2 addDependency:op3];
    [op3 addDependency:op4];
    
    //04、添加队列
    [queue1 addOperation:op1];
    [queue1 addOperation:op2];
    [queue1 addOperation:op3];
    [queue1 addOperation:op4];
    [queue2 addOperation:op5];
    
}

5、操作队列实现线程间通信

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    //01、创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //02、封装操作
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        //001、url
        NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1595160858894&di=c3a360abf23cd8299c88c9d39f4563b5&imgtype=0&src=http%3A%2F%2Fimg3.doubanio.com%2Fview%2Fphoto%2Fl%2Fpublic%2Fp1557578420.jpg"];
        
        //002、data
        NSData *data = [NSData dataWithContentsOfURL:url];
        
        //003、转换
        UIImage *image = [UIImage imageWithData:data];
        
        //004、在主线程中刷新UI
        __weak typeof(self) weakSelf = self;
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            weakSelf.imgV.image = image;
        }];
        
    }];
    //03、添加队列
    [queue addOperation:op];

}

6、GCD和NSOperation简单对比

/**
 1)GCD是纯C语言API,而操作队列是OC的对象
 2)GCD中任务用block块来表示,而块是个轻量级的数据结构;
 相反操作队列中的【操作】NSOperation则是个更加重量级的OC对象
 3)具体该使用GCD还是使用NSOperation需要看具体的情况
 
 NSOperation和NSOperationQueue的好处有:
 1)NSOperationQueue可以方便的调用cancel方法来取消某个操作,而GCD中的任务是无法被取消的(安排好任务之后就不管了)。
 2)NSOperation可以方便的指定操作间的依赖关系
 3)NSOperation可以通过KVO提供对NSOperation对象的精细控制(如监听当前操作是否被取消或是否已经完成等)。
 4)NSOperation可以方便的指定操作间的优先级。操做优先级表示此操做与队列中其它操作之间的优先关系,优先级高的操作先执行,优先级低的操作后执行。
 5)通过自定义NSOperation的子类可以实现操做重用。
 */

7、二级缓存结构

      /**
    二级缓存结构:
    (1)在显示图片前先检查是否有内存缓存
    (2)有内存缓存直接使用
    (3)没有内存缓存,再去检查是否有磁盘缓存
    (4)有磁盘缓存,直接使用 + 保存一份到内存中
    (5)没有磁盘缓存,下载图片并显示出来 + 保存一份到内存中 + 保存一份到磁盘中
    */

    //尝试从内存中取出图片(存在|不存在)
    UIImage *image = [self.images objectForKey:model.icon];
    if (image) {
        
        cell.imageView.image = image;
        
    }else{
        
        //获得文件的名称(得到该路径的最后一个节点)
        NSString *fileName = [model.icon lastPathComponent];
        //获得cache路径
        NSString *cache = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        //拼接全路径
        NSString *fullPath = [cache stringByAppendingPathComponent:fileName];
        
        //尝试从磁盘中取出图片(存在|不存在)
        NSData *data = [NSData dataWithContentsOfFile:fullPath];
        if (data) {
            //01 直接使用
            UIImage *image = [UIImage imageWithData:data];
            cell.imageView.image = image;
            
            //02 存一份到内存中
            [self.images setObject:image forKey:model.icon];
        }else{
            //解决图片错乱问题01
            //cell.imageView.image = nil;
            //解决图片错乱问题02 设置默认的占位图
            cell.imageView.image = [UIImage imageNamed:@"logo"];
            
            //001 创建队列(自定义)抽取成属性
            
            //对图片下载操做进行缓存
            //如果图片缓存不存在,那么先判断该图片的下载操作是否已经存在了,如果已经开始下载那么只需等待
            //如果该图片没有下载,那么再封装操做
            //先检查图片是否正在下载(检查操做缓存)
            NSBlockOperation *download = [self.operations objectForKey:model.icon];
            
            if (download) {
                
                //该图片正在下载
                
            }else{
                //002 封装操作
                NSBlockOperation *download = [NSBlockOperation blockOperationWithBlock:^{
                    
                    //01 下载图片并显示
                    NSURL *url = [NSURL URLWithString:model.icon];
                    NSData *imageData = [NSData dataWithContentsOfURL:url];
                    UIImage *image = [UIImage imageWithData:imageData];
                    
                    //02 保存一份到内存中
                    [self.images setObject:image forKey:model.icon];
                    
                    //03 保存一份到磁盘中
                    [imageData writeToFile:fullPath atomically:YES];
                    
                    //线程间通信(主线程显示图片)
                    __weak typeof(self) weakSelf = self;
                    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
                        //刷新指定行
                        [weakSelf.tabView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationTop];
                    }];
                }];
                
                //把操做保存一份
                [self.operations setObject:download forKey:model.icon];
                
                //003 添加队列
                [self.queue addOperation:download];
                
            }
        }
        
    }

8、SDWebImage框架基本使用和框架结构

截屏2020-07-20 下午1.44.42.png 截屏2020-07-20 下午1.52.38.png 截屏2020-07-20 下午1.55.34.png 截屏2020-07-20 下午4.32.00.png 截屏2020-07-20 下午5.02.08.png 截屏2020-07-20 下午2.10.27.png 截屏2020-07-20 下午4.05.54.png

9、NSCache的基本使用

截屏2020-07-20 下午5.07.53.png 截屏2020-07-20 下午5.08.19.png 截屏2020-07-20 下午5.08.44.png 截屏2020-07-20 下午5.19.24.png 截屏2020-07-20 下午5.20.25.png 截屏2020-07-20 下午5.21.23.png 截屏2020-07-20 下午5.22.59.png

作业

截屏2020-07-11 下午5.51.15.png 截屏2020-07-20 下午4.36.40.png

1、进程/线程/串行/并行/多线程/主线程/子线程的概念,多线程的原理,多线程的优缺点?

2、多线程的实现方案?

NSThread:

常用函数、创建线程、设置属性、线程状态(5种)、线程安全(前提|解决-线程同步)、线程间通信

GCD:

(1)核心概念:任务 + 队列;
(2)使用步骤:①创建队列;②封装任务,并且把任务添加到队列(函数);
(3)(同步函数、异步函数)和(串行队列、并发队列、主队列)组合使用(六种);
(4)线程间通信(嵌套);
(5)常用函数(五个:一次性函数、延时函数、快速迭代、栅栏函数、队列组);
(6)GCD中全局并发队列和create函数创建的并发队列主要区别;

  • 全局并发队列在整个应用程序中本身是默认存在的,并且有高级 默认 低级 后台四个优先级并发队列。而create函数创建的使我们自己实打实创建的
  • ios6.0前,GCD中使用create及retain的函数 最后都要一次release操作。而主队列 全局并发队列不需要我们手动release
  • 栅栏函数只有在自己create的并发队列中使用 而不能与全局并发队列使用

NSOperation:
(1)核心概念:操作 + 队列;
(2)使用步骤:(①创建队列、②封装操作、③把操做添加到队列);
(3)操作队列的最大并发数;
(4)操作队列的挂起(暂停、恢复)和取消;
(5)自定义操作(好处:代码复用);
(6)操作依赖和监听;
(7)线程间通信;
(8)GCD和NSOperation简单对比;

/**
 1)GCD是纯C语言API,而操作队列是OC的对象;
 2)GCD中任务用block块来表示,而块是个轻量级的数据结构;
 相反操作队列中的【操作】NSOperation则是个更加重量级的OC对象;
 3)具体该使用GCD还是使用NSOperation需要看具体的情况;
 
 NSOperation和NSOperationQueue的好处有:
 1)NSOperationQueue可以挂起(暂停/恢复)和取消某个操作,而GCD中的任务是无法被取消的(安排好任务之后就不管了)。
 2)NSOperation可以通过KVO提供对NSOperation对象的精细控制(如监听当前操作是否被取消或是否已经完成等)。
 3)NSOperation可以方便的指定操作间的依赖关系。
 4)NSOperation可以方便的指定操作间的优先级(操做优先级表示此操做与队列中其它操作之间的优先关系,优先级高的操作先执行,优先级低的操作后执行)。
 5)通过自定义NSOperation的子类可以实现操做重用。
 */

相关文章

网友评论

      本文标题:主题七《多线程》

      本文链接:https://www.haomeiwen.com/subject/arzyyktx.html