ios 多线程间通讯

1、什么是多线程

一个iOS程序就像一个圆,不断循环,直到将它切断。一个运行着的程序就是一个进程或者叫作一个任务,一个进程至少包含一个线程,线程就是程序的执行流。iOS中的程序启动,建立好一个进程的同时,一个线程便开始运行,这个线程叫主线程。主线程在程序中的地位和其余线程不一样,它是其余线程最终的父线程,且全部界面的显示操做即AppKit或UIKit的操做必须在主线程进行。 系统中的每个进程都有本身独立的虚拟内存空间,而同一个进程中的多个线程则共用进程的内存空间。每建立一个新的线程,都须要一些内存和消耗必定的CPU时间。另外当多个线程对同一个资源出现争夺的时候须要注意线程安全问题。安全

2、建立线程

1. 使用NSThread,建立一个NSThread的对象,调用其start方法。
// 建立线程
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:) object:@"http://b.png"];

//线程名字
thread.name = @"下载线程";

// 启动线程(调用self的download方法)
[thread start];
2. 使用+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument这个类方法建立一个线程而且会自动启动线程。
3. 使用NSObject 其实NSObject直接就加入了多线程的支持,容许对象的某个方法在后台运行。如:
//(调用self的download方法)
[self performSelectorInBackground:@selector(download:) withObject:@"http://c.gif"];

3、线程安全

多个线程可能会访问同一块资源很容易引起数据错乱和数据安全问题服务器

解决方法:多线程

互斥锁使用格式并发

@synchronized(锁对象) { // 须要锁定的代码 }框架

OC在定义属性时有nonatomic和atomic两种选择async

atomic:原子属性,为setter方法加锁(默认就是atomic)oop

nonatomic:非原子属性,不会为setter方法加锁测试

atomic加锁原理大数据

@property (assign, atomic) int age;

 - (void)setAge:(int)age
 { 

     @synchronized(self) { 
        _age = age;
     }
 }

atomic:线程安全,须要消耗大量的资源ui

nonatomic:非线程安全,适合内存小的移动设备

iOS开发的建议:

1 .全部属性都声明为nonatomic

2 .尽可能避免多线程抢夺同一块资源

3 .尽可能将加锁、资源抢夺的业务逻辑交给服务器端处理,减少移动客户端的压力

4、线程间的通讯

  • 线程间通讯的体现
1 .一个线程传递数据给另外一个线程

2 .在一个线程中执行完特定任务后,转到另外一个线程继续执行任务
  • 线程间通讯经常使用的方法
1. `NSThread`能够先将本身的当前线程对象注册到某个全局的对象中去,这样相互之间就能够获取对方的线程对象,而后就可使用下面的方法进行线程间的通讯了,因为主线程比较特殊,因此框架直接提供了在主线程执行的方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);
用法以下:

//点击屏幕开始执行下载方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self performSelectorInBackground:@selector(download) withObject:nil];
}

//下载图片
- (void)download
{    
    // 1.图片地址
    NSString *urlStr = @"http://d.jpg";

    NSURL *url = [NSURL URLWithString:urlStr];

    // 2.根据地址下载图片的二进制数据

    NSData *data = [NSData dataWithContentsOfURL:url];
    NSLog(@"---end");

    // 3.设置图片
    UIImage *image = [UIImage imageWithData:data];

    // 4.回到主线程,刷新UI界面(为了线程安全)
    [self performSelectorOnMainThread:@selector(downloadFinished:) withObject:image waitUntilDone:NO];

   // [self performSelector:@selector(downloadFinished:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];


}

- (void)downloadFinished:(UIImage *)image
{
    self.imageView.image = image;

    NSLog(@"downloadFinished---%@", [NSThread currentThread]);
}
2. `GCD`一个线程传递数据给另外一个线程,如:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

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

        // 1.子线程下载图片
        NSURL *url = [NSURL URLWithString:@"http://d.jpg"];

        NSData *data = [NSData dataWithContentsOfURL:url];

        UIImage *image = [UIImage imageWithData:data];

        // 2.回到主线程设置图片
        dispatch_async(dispatch_get_main_queue(), ^{

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

            [self.button setImage:image forState:UIControlStateNormal];
        });
    });
}
  • 线程中延迟调用某个方法
//线程延迟调用 通讯
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        NSLog(@"## 在主线程延迟5秒调用 ##");
    });
  • 线程休眠几秒的方法
    sleep(6); : 这里是休眠6秒

  • 经常使用的线程通讯方法有如下几种:(GCD)

    1. 须要更新UI操做的时候使用下面这个GCD的block方法

      //回到主线程更新UI操做
      dispatch_async(dispatch_get_main_queue(), ^{
           //数据执行完毕回调到主线程操做UI更新数据
      });
    2. 有时候省去麻烦,咱们使用系统的全局队列:通常用这个处理遍历大数据查询操做

      DISPATCH_QUEUE_PRIORITY_HIGH  全局队列高优先级
      DISPATCH_QUEUE_PRIORITY_LOW 全局队列低优先级
      DISPATCH_QUEUE_PRIORITY_BACKGROUND  全局队里后台执行队列
      // 全局并发队列执行处理大量逻辑时使用 
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      
      });
    3. 当在开发中遇到一些数据须要单线程访问的时候,咱们能够采起同步线程的作法,来保证数据的正常执行

      //当咱们须要执行一些数据安全操做写入的时候,须要同步操做,后面全部的任务要等待当前线程的执行 dispatch_sync(dispatch_get_global_queue(0, 0), ^{ //同步线程操做能够保证数据的安全完整性 });//当咱们须要执行一些数据安全操做写入的时候,须要同步操做,后面全部的任务要等待当前线程的执行 dispatch_sync(dispatch_get_global_queue(0, 0), ^{ //同步线程操做能够保证数据的安全完整性 });

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

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

    //数据请求完毕回调到主线程,更新UI资源信息 waitUntilDone 设置YES ,表明等待当前线程执行完毕
     [self performSelectorOnMainThread:@selector(dothing:) withObject:@[@"1"] waitUntilDone:YES];
  2. 若是须要执行到后台线程,则直接前日后台线程执行便可

    //将当前的逻辑转到后台线程去执行
    [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 来保持对象的引用
- (void)entryThreadPoint{

    @autoreleasepool {
        [NSThread currentThread].name = @"自定义线程名字";
        NSRunLoop * runloop = [NSRunLoop currentRunLoop];
        [runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        [runloop run];


    }
}
- (void)handleMutiData{

    NSLog(@"## 我是跑在runloop的线程 ##");
}

最后测试截图以下,看下咱们的线程是否是已经加入runloop 了


这个就是最后的执行了