ios 多线程间通信

来源:互联网 发布:网络电视遥控器失灵 编辑:程序博客网 时间:2024/06/05 14:06

一、什么是多线程

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

二、创建线程

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"];

三、线程安全

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

解决方法:

互斥锁使用格式

@synchronized(锁对象) { // 需要锁定的代码 }

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

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

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

atomic加锁原理

 @property (assign, atomic) int age; - (void)setAge:(int)age {      @synchronized(self) {         _age = age;     } }

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

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

iOS开发的建议:

1 .所有属性都声明为nonatomic

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

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

四、线程间的通信

  • 线程间通信的体现
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), ^{   //同步线程操作可以保证数据的安全完整性});

二、了解一下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 了


这个就是最后的执行了



原创粉丝点击