UI一揽子计划 22 (多线程概述、NSThread、NSOperationQueue 、GCD、多线程管理)

一.多进程概述:

     进程 : 一个正在运行的程序 叫做一个进程
    多进程 : 多个程序正在运行 叫做多线程
   
线程 : 一个进程 都会有一个或多个线程 如果只有一个 叫做主线程   主线程负责用户能看见的任务 例如 添加控件 刷新页面
    除了主线程以外 都叫子线程   线程之间是独立的 并没有任何联系 . 子线程一般负责用户不直接看到的任务 例如加载图片的过程 , 下载视频等
    线程要明确的 : 只要用户看得见的 或者跟用户看得见有关的 都使用主线程 进行操作. 因为开启子线程 进行一些操作的时候 为了更好的用户体验 用户体验最直接的表现为 看到的或者点击的流畅
    线程是耗费资源的 , 虽然可以多线程操作 提高用户体验 但是不建议进行 很多线程同时进行操作


二.NSThread

NSThread是一个轻量级的多线程. 有两种创建方法:
1. - (id)initWithTarget:(id)target selector:(SEL )selector object:(id) argument;
2. + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)aTarget withObject:(id)argument;
3. start
4. cancel

 // 创建一个线程 就是给它一个方法去执行
#pragma mark -- NSThread
    // 创建一个子线程 专门打印用
  
// self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(actionPrint:) object:nil];
   
// 开启子线程
  
// [self.thread start];
   
// 线程操作的时候
   
// 在主线程的时候系统自动给了一个自动释放池 那么咱们在操作子线程的时候也要添加一个自动释放池
   
   
// 如果线程开辟的比较多 代码 比较乱    阅读性 不高
   
    // 一般方法中 添加了自动释放池 一般都是线程的方法

三.NSOperation

1). NSOperation 概述
NSOperation 类 在MVC中属于M, 是用来封装单个任务相关的代码和数据的抽象类
因为它是抽象类 不能直接使用该类, 只能使用子类(NSInVocationOperation / NSBlockOperation) 来执行实际任务.
NSOperation 包括其子类, 只是一个操作 , 本身无主线程和子线程之分, 可在任意线程使用,通常与NSOperationQueue 结合使用
NSOperationQueue 是操作队列,它用来管理一组Operation对象的执行,会根据需要自动为Operation开辟合适数量的线程,以完成任务的并发执行
其中NSOperation 可以调节它在队列中的优先级 
最大并发数为1 时,能实现线程同步
#pragma mark -- NSOperation
    // NSOperation 也是一个抽象类 没有实现具体功能
   
// NSBlockOperation  block中操作 (相当于任务)
   
// NSInvocationOperation  调用操作 (相当于任务)
   
// NSOperationQueue 线程队列
 
   
// 创建任务1
   
NSInvocationOperation *invocation1 = [[NSInvocationOperation  alloc]initWithTarget:self selector:@selector(invocation1) object:nil];
   
// 创建任务2
   
   
// block块儿中就相当于 添加的任务
   
NSBlockOperation *block2 = [NSBlockOperation blockOperationWithBlock:^{
        [
self block];
    }];
   
   
// 创建一个队列
   
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
   
   
// 依赖性 (串行)
   
/**
     * 
注意 : 添加任务前 要先设置 依赖关系
     */

   
// 前面的完事了 后面的才能开始
    [invocation1
addDependency:block2];
   
   
// 把任务添加进队列当中
    [queue
addOperation:invocation1];
    [queue
addOperation:block2];
   
   
// 设置最大并发数
    queue.maxConcurrentOperationCount = 2;

- (void)invocation1
{
   
// 子线程中 添加一个自动释放池
   
@autoreleasepool {
       
// 是不是主线程
       
// 当前线程的信息
       
NSLog(@"我是 invocation1 %@,  %d", [NSThread currentThread],[NSThread isMainThread]);
    }
}
// 创建任务2 的方法
- (
void)block
{
   
// 先添加释放池
   
@autoreleasepool {
       
// 是不是主线程
       
// 当前线程的信息
       
NSLog(@"%@,  %d", [NSThread currentThread],[NSThread isMainThread]);
 
       
/*
         {number = 2, name = (null)},  0
        
打印出来的 number 是线程的个数 表示当前是第几个
         */

    }
}

2).利用NSOperation 同步加载一张图片 不出现卡顿的现象

NSBlockOperation *blockOP = [NSBlockOperation blockOperationWithBlock:^{
       
       
// 在子线程里面添加自动释放池
       
@autoreleasepool {
           
// 任务: 加载图片
           
NSURL *url = [NSURL URLWithString:kPictureUrl];
           
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
            [request
setHTTPMethod:@"GET"];
           
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
           
// 以上是同步请求不加线程 应该卡
           
// 回到主线程去显示图片
            [
self performSelectorOnMainThread:@selector(imageViewWithData:) withObject:data waitUntilDone:YES];
        }
    }];
   
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperation:blockOP];


// 回到主线程 显示图片
- (
void)imageViewWithData:(NSData *)data
{
   
NSLog(@"%d", [NSThread isMainThread]);
   
   
// 注意: 子线程加载完 一定要回答主线程 刷新界面
   
self.imageView.image = [UIImage imageWithData:data];

}
四.NSObject

NSObject 中存在了一个最简单的后台执行方法
- (void)performSelectorInBackGround:(SEL)selector withObject:(id) arguement;

#pragma mark -- NSObject
    // NSObject 也提供了 开启子线程的方法 也可以解决程序卡死的问题
   // [self performSelectorInBackground:@selector(actionPrint:) withObject:nil];


五.GCD(Grand Central Dispatch)使用

Grand Central Dispatch是苹果公司开发的技术, 以优化应用程序支持多核心处理器和其他的对称多处理系统的系统.
GCD 属于函数级的多线程, 性能更高,功能更强大.
任务: 具有一定功能的代码段, 一般是一个Block 或者是 函数.
分发队列: GCD 以队列的方式进行工作.FIFO.(first in first out)
GCD 会根据分发队列的类型, 创建合适数量的线程执行队列中的任务.

GCD 的部分功能
1). dispatch_async() 往队列中添加任务  任务会排队执行
2). dispatch_after() 往队列里添加任务 还会延迟的时间点执行
3). dispatch_apply()   往队列里添加任务 任务会重复执行n次
4). dispatch_group_async()  将任务添加到队列中,并添加分组标记
5). dispatch_group_notify() 将任务添加到队列中, 当某个分组的所有任务都执行完成后 此任务才会执行
6).dispatch_barrier_async() 将任务添加到队列中,此任务执行的时候,其他任务停止执行
7).dispatch_once() 只执行一次

dispatch queue 分为以下两种:
1). SerialQueue: 串行的,一次执行一个任务.
- (void)serialQueue
{
   
// 串行队列分两种
   
// 1. 主线程中的串行队列(主串队列)

       
// dispatch_queue_t   GCD 中表示一个队列
   
   
// 创建一个主队列  (实际上就是把主线程取出来了)
  
// dispatch_queue_t mainQueue = dispatch_get_main_queue();
   
   
// 添加任务1
   
/**
     * 
参数1: 添加任务的队列
       
参数2: 要执行的任务
     */

//    dispatch_async(mainQueue, ^{
//       // NSLog(@"第一个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
//    });
//    // 添加任务2
//    dispatch_async(mainQueue, ^{
//       // NSLog(@"第二个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
//    });
//    // 添加任务3
//    dispatch_async(mainQueue, ^{
//       // NSLog(@"第三个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
//    });
   
   
// 2. 自定义的串行队列
   
// 创建一个自定义的线程队列
   
/**
     * 
参数1: 队列的标识符 并且是char * 类型的  一个名字随便起 千万不能加 @" "
       
参数2: 队列的执行类型( / )  DISPATCH_QUEUE_SERIAL 串行的
     */

   
dispatch_queue_t myQueue = dispatch_queue_create("com.wl.MyQueue", DISPATCH_QUEUE_SERIAL);
   
   
   
// 添加任务1
   
/**
     * 
参数1: 添加任务的队列
    
参数2: 要执行的任务
     */

   
dispatch_async(myQueue, ^{
       
NSLog(@"第一个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
    });
   
// 添加任务2
   
dispatch_async(myQueue, ^{
       
NSLog(@"第二个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
    });
   
// 添加任务3
   
dispatch_async(myQueue, ^{
       
NSLog(@"第三个任务,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
    });

   
// 延迟执行1 回到主线程
   
// delayInSeconds 延迟几秒 单位是秒
   
// code to be executed after a specified delay
   
   
// 延迟几秒后 回到主线程去执行任务
   
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       
NSLog(@"延迟5秒执行");
       
NSLog(@"延迟,所在线程:%@, 是否是主线程:%d", [NSThread currentThread],[NSThread isMainThread]);
    });
   
   
// 延迟执行 回到自定义线程
   
// ull C语言中是字面量  表示 unsigned long long
   
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 6ull * NSEC_PER_SEC), myQueue, ^{
       
NSLog(@"延迟 6秒执行");
    });
   
   
// 延迟执行一个任务
 
//  self performSelector:<#(SEL)#> withObject:<#(id)#> afterDelay:<#(NSTimeInterval)#>
}

2). Concurrent: 并行的,可以并发执行多个任务,但是遵守FIFO.
- (void)concurrentQueue
{
   
// 创建一个队列
   
/**
     * 
参数1: 队列的执行顺序 DISPATCH_QUEUE_PRIORITY_DEFAULT 默认的执行顺序
       
参数2: <#unsigned long flags#> 预留参数 一般为 0
     */

//    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//   
//    // 添加任务1
//    dispatch_async(queue, ^{
//        for (int i = 0;  i < 10; i++) {
//            NSLog(@"第一个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
//        }
//    });
//   
//    // 添加任务2
//    dispatch_async(queue, ^{
//        for (int i = 10;  i < 20; i++) {
//            NSLog(@"第二个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
//        }
//    });
//    // 添加任务3
//    dispatch_async(queue, ^{
//        for (int i = 20;  i < 30; i++) {
//            NSLog(@"第三个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
//        }
//    });
   
   
   
// 自定义并行队列
   
/**
     * 
参数1: 队列的标示符
       
参数2: / 执行顺序 DISPATCH_QUEUE_CONCURRENT 并发执行
     */

   
dispatch_queue_t myQueue = dispatch_queue_create("com.wl.www", DISPATCH_QUEUE_CONCURRENT);
   
   
// 添加任务1
   
dispatch_async(myQueue, ^{
       
for (int i = 0;  i < 10; i++) {
           
NSLog(@"第一个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
        }
    });
   
   
// 添加任务2
   
dispatch_async(myQueue, ^{
       
for (int i = 10;  i < 20; i++) {
           
NSLog(@"第二个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
        }
    });
   
// 添加任务3
   
dispatch_async(myQueue, ^{
       
for (int i = 20;  i < 30; i++) {
           
NSLog(@"第三个任务,所在线程:%@, 是否是主线程:%d ------ %d", [NSThread currentThread],[NSThread isMainThread], i);
        }
    });
   
}

六.线程之间的通信

分为两种:
1). 主线程进入子线程
2). 子线程回到主线程
GCD: dispatch_get_main_queue();
NSObject: - (void ) performSelector:(SEL)aSelector withObject:(id)arg waitUntilDown:(BOOL)wait;
// 模拟游戏开始的线程操作

  需求: 一共四个玩家 一起进入游戏 当所有人都进入游戏的时候 开始读取数据
  思路
        1. 选取什么队列 选用并行
        2. 如何实现特殊要求 把并行队列的用屏障隔开  (屏障的作用: 把队列分成上下部分 分开执行)

   
- (void)gameStart
{
    // 创建队列
   
dispatch_queue_t myQueue = dispatch_queue_create("com.wl.www", DISPATCH_QUEUE_CONCURRENT);
   
dispatch_async(myQueue, ^{
       
NSLog(@"第一名玩家正在进入... ...  所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
    });
   
dispatch_async(myQueue, ^{
       
NSLog(@"第二名玩家正在进入... ...  所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
    });
   
dispatch_async(myQueue, ^{
       
NSLog(@"第三名玩家正在进入... ...  所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
    });
   
   
// 增加屏障
   
dispatch_barrier_async(myQueue, ^{
       
NSLog(@"都进入了,可以开始 读取玩家的信息了");
    });
   
dispatch_async(myQueue, ^{
       
NSLog(@"第一名玩家正在读取信息... ...  所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
    });
   
dispatch_async(myQueue, ^{
       
NSLog(@"第二名玩家正在读取信息... ...  所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
    });
   
dispatch_async(myQueue, ^{
       
NSLog(@"第三名玩家正在读取信息... ...  所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread]);
    });
   
   
// 主线程 进入游戏 需要刷新界面
   
// 再加一个屏障 进入主线程 刷新界面
   
dispatch_barrier_async(myQueue, ^{
       
// 回到主线程
      
dispatch_async(dispatch_get_main_queue(), ^{
          
NSLog(@"回到主线程 ,可以刷新页面,开始游戏.....敌军还有30秒到达战场.所在线程:%@, 是否是主线程:%d ", [NSThread currentThread],[NSThread isMainThread
]);
       });
    });
   
   
}

七. 线程互斥

指的是某一资源同时只允许一个访问者对其进行访问,具有唯一性和排他性
互斥无法限制访问者的访问顺序 即 访问时无顺序的,因为需要加上互斥锁来进行顺序访问.

NSLock类
NSLock *lock = [[NSLock alloc] init];
锁:      [lock lock];
解锁: [lock unLock];
// 买火车票的线程锁实现

- (void)tickets
{
   
// 给个票数的初值
    self.totalTickets = 100;
   
    // 剩余票数
   
self.subTickets = 100;
   
   
// 初始化线程锁
   
self.lock = [[NSLock alloc] init];
   
   
// 先创建出两个 并行队列
   
/**
     * 
一个队列是火车站
       
一个队列是12306
     */

   
   
dispatch_queue_t queue1 = dispatch_queue_create("trainStation", DISPATCH_QUEUE_CONCURRENT);
   
   
// 给火车站添加一个卖票的
   
dispatch_async(queue1, ^{
      
// sale tickets
        [
self saleTickets:queue1];
    });
   
   
// 添加一个12306队列
   
dispatch_queue_t queue2 = dispatch_queue_create("com.12306.www", DISPATCH_QUEUE_CONCURRENT);
   
// 给队列添加一个卖票的任务
   
dispatch_async(queue2, ^{
      
// sale tickets
        [
self saleTickets:queue2];
    });
   
   
// 添加一个售票机队列
   
dispatch_queue_t queue3 = dispatch_queue_create("售票机", DISPATCH_QUEUE_CONCURRENT);
   
// 给队列添加一个卖票的任务
   
dispatch_async(queue3, ^{
       
// sale tickets
        [
self saleTickets:queue3];
    });
  
}

// 卖票的方法
- (
void)saleTickets:(dispatch_queue_t)queue
{
   
// 卖票循环

   
while (self.subTickets > 0) {
       
// 添加互斥锁
        [
self.lock lock];
       
// const 一个变量不允许被改变 静态变量
       
const char *label = dispatch_queue_get_label(queue);
       
NSString *str = [NSString stringWithUTF8String:label];
       
NSLog(@"%@ 卖出的,剩余 %ld .", str, self.subTickets--);
       
// 接触互斥锁  在自动释放池使用的方法差不多 中间添加的部分是锁的内容
        [
self.lock unlock];
    }
}

你可能感兴趣的:(iOS-UI教程)