iOSiOS技术研究gcd

谈谈iOS面试常提及到的线程间的通信

2016-05-25  本文已影响2600人  Raybon_lee
程序猿思考问题时的样子

我们看图片只是乐呵一下,程序猿思考问题差不多就是这个样子,
今天同事在线程通信这一块有点疑问,我们下面来分析一下,系统都提供给我们那些,其实我们都知道,但是很少去关注这些API,也正是这些API,来回在APP中去执行各种不同的线程和队列


一、常见的线程间通信 GCD

我们先来看一个系统的例子:

    //开启一个全局队列的子线程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            //1. 开始请求数据
            //...
            // 2. 数据请求完毕
            //我们知道UI的更新必须在主线程操作,所以我们要从子线程回调到主线程
        dispatch_async(dispatch_get_main_queue(), ^{

                //我已经回到主线程更新
        });

    });

如上所述,我们下面进行一个测试:

     //自定义队列,开启新的子线程
    dispatch_queue_t    custom_queue = dispatch_queue_create("concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
    for (int i=0; i<10; i++) {
        dispatch_async(custom_queue, ^{
            NSLog(@"## 并行队列 %d ##",i);
                //数据更新完毕回调主线程 线程之间的通信
            dispatch_async(dispatch_get_main_queue(), ^{

                NSLog(@"### 我在主线程 通信 ##");

            });


        });
    }

    //线程延迟调用 通信
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        NSLog(@"## 在主线程延迟5秒调用 ##");
    });

// 全局并发队列执行处理大量逻辑时使用
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

});
    ```
3.   当在开发中遇到一些数据需要单线程访问的时候,我们可以采取同步线程的做法,来保证数据的正常执行

    ```
//当我们需要执行一些数据安全操作写入的时候,需要同步操作,后面所有的任务要等待当前线程的执行
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        //同步线程操作可以保证数据的安全完整性
});

####  二、了解一下NSObject中的对象线程访问模式

 
1.   我们介绍简单的perfermselecter选择器实现线程通信

    ```
        //数据请求完毕回调到主线程,更新UI资源信息  waitUntilDone    设置YES ,代表等待当前线程执行完毕
    [self performSelectorOnMainThread:@selector(dothing:) withObject:@[@"1"] waitUntilDone:YES];
  1. 如果需要执行到后台线程,则直接前往后台线程执行即可
```
  //将当前的逻辑转到后台线程去执行
[self performSelectorInBackground:@selector(dothing:) withObject:@[@"2"]];
3.  自己定义线程,将当前数据转移到指定的线程内去通信操作
    
 //支持自定义线程通信执行相应的操作
NSThread * thread = [[NSThread alloc]init];
[thread start];
    //当我们需要在特定的线程内去执行某一些数据的时候,我们需要指定某一个线程操作
[self performSelector:@selector(dothing:) onThread:thread withObject:nil waitUntilDone:YES];
//支持自定义线程通信执行相应的操作
NSThread * thread = [[NSThread alloc]initWithTarget:self selector:@selector(testThread) object:nil];
[thread start];
   //当我们需要在特定的线程内去执行某一些数据的时候,我们需要指定某一个线程操作
[self performSelector:@selector(dothing:) onThread:thread withObject:nil waitUntilDone:YES];

* 上面几种方法就是我们常用的对象调用常用的线程间通信,我们可以根据不同的情况,采取不同的线程执行状态.

##### 增加一个特殊的线程常驻RunLoop 的做法

*  需求: 我们经常要用到常驻线程来请求数据,但是请求有时候在线程会退出,这个时候我们需要用一下方法:

//有时候需要线程单独跑一个RunLoop 来保证我们的请求对象存在,不至于会被系统释放掉

NSThread * runLoopThread = [[NSThread alloc]initWithTarget:self selector:@selector(entryThreadPoint) object:nil];
[runLoopThread start];
[self performSelector:@selector(handleMutiData) onThread:runLoopThread withObject:nil waitUntilDone:YES];

//给线程增加一个run loop 来保持对象的引用
}

}

最后测试截图如下,看下我们的线程是不是已经加入runloop 了

![这个就是最后的执行了](http:https://img.haomeiwen.com/i680076/1b93a55de8dbee86.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

********
#####  补充1:
*  有个地方需要补充以下:  请先看完下面的之后,再回头看这个地方。
需求: 如何动态的调整队列的优先级,执行层级,我们这里多加一个函数
      ```
//  设置目标队列  queue 既是target , object 是我们将要指定的dispatch 对象, 第一个参数要用自定义的队列,不能使用系统的全局队列,系统的队列层级我们无法修改
void
dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

这个就是我们指定一个队列执行在目标队列上,和目标队列一个层级
我们看下代码如何调用:
```

dispatch_queue_t queue1 = dispatch_queue_create("queue1", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", NULL);
dispatch_set_target_queue(queue1, queue2);
dispatch_async(queue2, ^{
    NSLog(@"## 我是queue2 ----0###");
});
dispatch_async(queue1, ^{
    NSLog(@"## 我是queue1 ------1##");
});
dispatch_async(queue2, ^{
    NSLog(@"## 我是queue2  ----2 ##");
});
dispatch_async(queue1, ^{
    NSLog(@"## 我是queue1 ------3 ##");
});
dispatch_async(queue1, ^{
    NSLog(@"## 我是queue1 ------4 ##");
});
dispatch_async(queue2, ^{
    NSLog(@"## 我是queue2    ---5 ##");
});
*  我们可以先猜想一下输出结果,会按照我们想要的结果显示么
答案:

2016-06-01 11:53:36.333 RunTimeModify[41214:6432344] ## 我是queue2 ----0###
2016-06-01 11:53:36.339 RunTimeModify[41214:6432344] ## 我是queue1 ------1##
2016-06-01 11:53:36.341 RunTimeModify[41214:6432344] ## 我是queue1 ------3 ##
2016-06-01 11:53:36.343 RunTimeModify[41214:6432344] ## 我是queue1 ------4 ##
2016-06-01 11:53:36.344 RunTimeModify[41214:6432344] ## 我是queue2 ----2 ##
2016-06-01 11:53:36.344 RunTimeModify[41214:6432344] ## 我是queue2 ---5 ##

相信大家发现问题了。
我们调整一下Target 的顺序:

dispatch_set_target_queue(queue2, queue1);

我们再来看一下输出:

2016-06-01 11:57:30.483 RunTimeModify[41297:6435226] ## 我是queue2 ----0###
2016-06-01 11:57:30.486 RunTimeModify[41297:6435226] ## 我是queue2 ----2 ##
2016-06-01 11:57:30.486 RunTimeModify[41297:6435226] ## 我是queue2 ---5 ##
2016-06-01 11:57:30.487 RunTimeModify[41297:6435226] ## 我是queue1 ------1##
2016-06-01 11:57:30.487 RunTimeModify[41297:6435226] ## 我是queue1 ------3 ##
2016-06-01 11:57:30.487 RunTimeModify[41297:6435226] ## 我是queue1 ------4 ##

以上只是简单的使用,我们不做什么的测试,了解就行:
以上这个地方,大家还会有个小疑问:连接在这里,[stack 上找的How does dispatch_set_target_queue work?](http://stackoverflow.com/questions/23112547/answer/submit)
大家看到这个或许就明白怎么工作的了

##### 补充2:
    
  >  谢谢 @激动的马 问题 ,还要增加一种通信方法

*     NSOperationQueue  系统自带的更新UI的方法 ,这种我不经常用,推荐GCD
//注意点 : 设计到UI的更新在主队列
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
    NSLog(@"###  我是在主队列执行的block  ####");
}];

//这个只是用作判断当前线程是否是主线程
if ([[NSThread currentThread] isMainThread]) {
NSLog(@"## 我是主线程 ##");
}else{
NSLog(@"## 我是子线程 ###");
}

#####  补充三、 
* GCD中阻塞线程之用法

//主要涉及到下面这个API,该API是保证自己的block会等待当前队列之前的block全部执行完毕,在执行自己的block,最后在执行后面的任务
void
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

我们用代码简单的看一下调用顺序:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"## 全局队列异步执行 第1个任务 ##");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"## 全局队列异步执行 第2个任务 ##");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"## 全局队列异步执行 第4个任务 ##");
});
dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"## 异步阻塞当前线程,会等待当前线程执行完毕  ###");
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    NSLog(@"### 我在全局队列线程内执行  第3个任务###");
});
    * 我们看下控制台输出:
       ```
    2016-06-01 14:04:13.973 RunTimeModify[42377:6489044] ## 全局队列异步执行 第1个任务 ##
2016-06-01 14:04:13.973 RunTimeModify[42377:6489051] ## 全局队列异步执行 第2个任务 ##
2016-06-01 14:04:13.973 RunTimeModify[42377:6489063] ## 全局队列异步执行 第4个任务 ##
2016-06-01 14:04:13.973 RunTimeModify[42377:6489064] ## 异步阻塞当前线程,会等待当前线程执行完毕  ###
2016-06-01 14:04:13.973 RunTimeModify[42377:6489065] ### 我在全局队列线程内执行  第3个任务###
      ```
大概可以看一下阻塞这个API是怎么用的,可以在项目总很好的处理我们的问题

* 我们在继续看下面的循环执行的block任务:
    ```
for (int i=0; i<5; i++) {
        dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"## 我是阻塞线程执行顺序 %d ###",i);
        });

    }
    for (int i=0; i<5; i++) {

        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"### 我是全部异步队列任务 : %d  ###",i);
        });
    }

我们看下控制台打印输出的日志系统:

2016-06-01 14:07:42.534 RunTimeModify[42437:6491161] ## 我是阻塞线程执行顺序 1 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491166] ## 我是阻塞线程执行顺序 0 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491168] ## 我是阻塞线程执行顺序 2 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491179] ## 我是阻塞线程执行顺序 3 ###
2016-06-01 14:07:42.534 RunTimeModify[42437:6491180] ## 我是阻塞线程执行顺序 4 ###
2016-06-01 14:07:42.535 RunTimeModify[42437:6491181] ### 我是全部异步队列任务 : 0  ###
2016-06-01 14:07:42.535 RunTimeModify[42437:6491161] ### 我是全部异步队列任务 : 1  ###
2016-06-01 14:07:42.536 RunTimeModify[42437:6491166] ### 我是全部异步队列任务 : 2  ###
2016-06-01 14:07:42.536 RunTimeModify[42437:6491182] ### 我是全部异步队列任务 : 3  ###
2016-06-01 14:07:42.537 RunTimeModify[42437:6491168] ### 我是全部异步队列任务 : 4  ###

虽然都是异步任务,但是执行的很有规则。我们可以去尝试测试一下反过来的结果

//测试一下,是不是阻塞API之前的任务必须要执行完毕,看下有什么问题
for (int i=0; i<5; i++) {

    dispatch_barrier_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"## 我是阻塞线程执行顺序 %d ###",i);
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"### 我是全部异步队列任务 : %d  ###",i);
    });
}

*  经常遇到的问题:
    1. 网络请求到数据,但是UI没有及时更新。
    2. 在子线程的block内执行更新UI操作,控制台一般都会有错误线程输出的
    3. 如果只是逻辑的处理,无关UI,处理数据量较大,可以选择子线程处理。

本文暂时先介绍到这里,如果还想了解用信号量控制线程可以参考另外一篇博客:关于dispatch 下一遍补充了源的问题
[iOS 关于dispatch_semaphore_t 和 dispatch_group_t 的简单实用,用于多网络异步回调通知](http://www.jianshu.com/p/a4ea43179870)
上一篇 下一篇

猜你喜欢

热点阅读