iOS开发 - 在实战中挖掘之线程间的通信方式

简介: iOS开发 - 在实战中挖掘之线程间的通信方式

技术这行怎么变的高大上呢?那一定是抛出一个个的专有名词,很多时候,我们在谈论技术的时候,往往忘记了技术本身的作用是什么?我不能说这有多不好,但多少还是会有失偏颇。


今天我们要说的内容是线程间通信,说得直白的点就是子线程完成任务后回主线程刷新UI,或者是多个线程共享数据,产生依赖关系。


我相信这种通信方式但凡是个程序猿就一定在项目中用过,但当换个名字问的时候很多人就一时不知道说的是什么,这让我想起前几天有人问我快捷开发,搞的我一愣,后来了解才知道就是所谓的敏捷开发,什么是敏捷开发?说实话我个人也参与过所谓敏捷开发的项目,还真没感觉和平时的项目有什么区别,最多开发的快一点?哈哈......


言归正传,什么是线程间通信呢?


首先我们要先知道什么是线程,线程分为四种:  


1.pthread:即POSIX Thread,缩写称为Pthread,是线程的POSIX标准,是一套通用的多线程API,可以在Unix/Linux/Windows等平台跨平台使用。iOS中基本不使用。


2.NSThread:苹果封装的面向对象的线程类,可以直接操作线程,比起GCDNSThread效率更高,由程序员自行创建,当线程中的任务执行完毕后,线程会自动退出,程序员也可手动管理线程的生命周期。使用频率较低。


3.GCD:全称Grand Central Dispatch,由C语言实现,是苹果为多核的并行运算提出的解决方案,CGD会自动利用更多的CPU内核,自动管理线程的生命周期,程序员只需要告诉GCD需要执行的任务,无需编写任何管理线程的代码。GCD也是iOS使用频率最高的多线程技术。


4.NSOperation:基于GCD封装的面向对象的多线程技术,常配合NSOperationQueue使用,面向对象,提供了一些GCD所没有的能力,使用频率较高。


除了第一种,余下三种算是我们都比较常用的,我们先来说说简单的线程通信:


1.子线程完成任务后,回到主线程刷新UI


1)NSThread

- (void)viewDidLoad
{
    NSURL *url = [NSURL URLWithString:@"xxxxxxx"];
    开启线程,在后台执行图片下载方法
   [self performSelectorInBackground:@selector(downloadImg:) withObject:url];
}
- (void)downloadImg:(NSURL *)url
{
    NSData *data = [NSData dataWithContentsOfURL:url];
    UIImage *image = [UIImage imageWithData:data];
    // 回到主线程中执行图片赋值的方法
    [self performSelector:@selector(showImg:) onThread:[NSThread mainThread] withObject:image waitUntilDone:YES];
}
-(void)showImg:(UIImage *)image
{
    // 刷新UI
    self.imageView.image = image;
}

2)GCD

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
        NSURL *url = [NSURL  URLWithString:@"xxxxxxxxxx"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        /*
            异步函数调用, 先执行完其他代码, 再更新UI
            同步函数调用, 先更新UI, 再执行其它代码
            根据具体需要来选择
        */
        dispatch_sync(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            NSLog(@"Refreshed UI");
        });
        NSLog(@"我位置靠后");
    });

3)NSOperation

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperationWithBlock:^{
        NSURL *url  = [NSURL URLWithString:@"xxxxxxx"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        //回主线程刷新UI
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            self.imageView.image = image;
        }];
    }];


2.线程之间相互依赖


有依赖的场景经常会发生在GCD和NSOperation上面,不是说NSThread不行,只是这两种方式更好使用,下面讲讲这两者怎么添加相互依赖:


1)GCD


GCD的依赖一般是一个线程依赖于另一个线程中的某些数据,我们常说的线程安全,比如读写,也可以认为是一种依赖关系,我们一般通过三种方式来做:


ddispatch_group_notify

dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[WebImageManager sharedManager]downloadImageWithURL:@"xxxxxxxxxx" completed:^(UIImage *image, NSError *error, ImageCacheType cacheType, BOOL finished) {
    dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[WebImageManager sharedManager]downloadImageWithURL:@"xxxxxxxxxx" completed:^(UIImage *image, NSError *error, ImageCacheType cacheType, BOOL finished) {
    dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
});

dispatch_group_enter(group);和dispatch_group_leave(group);都是成对出现的,一个进,就要一个出,这里也可以选择普通dispath_async来做,总之notify的作用就是形成一个栅栏,把栅栏之前的代码围堵起来执行完毕后再执行栅栏和栅栏之后的代码。

  • 信号量


AFNetworking中就有这么一段运用,创建信号量,执行任务,dispatch_semaphore_signal信号量+1,这时候任务已经在执行,在没有执行完毕时,信号量大于0,所以其他任务无法进入,dispatch_semaphore_wait设置等待时长,当前线程被阻塞,当任务执行完毕后,信号总量-1,此时,其他任务可以进入。


信号量可以用来做并发控制,设置最多允许的可访问临界区域的数量就可以,dispatch_semaphore_create(0),0可以修改。

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }
        dispatch_semaphore_signal(semaphore);
    }];
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    return tasks;
}

NSOperation


NSOperation中有一种添加依赖的方式,我们来看看代码:

//创建一个队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//下载第一张图片
__block UIImage *img1 = nil;
NSBlockOperation *opb1 = [NSBlockOperation blockOperationWithBlock:^{
    NSURL *url  = [NSURL URLWithString:@"xxxxx"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    img1 = [UIImage imageWithData:data];
}];
//下载第二张图片
__block UIImage *img2 = nil;
NSBlockOperation *opb2 = [NSBlockOperation blockOperationWithBlock:^{
    NSURL *url  = [NSURL URLWithString:@"xxxxx"];
    NSData *data = [NSData dataWithContentsOfURL:url];
    img2 = [UIImage imageWithData:data];
}];
//image1和image2合成
NSBlockOperation *opb3 = [NSBlockOperation blockOperationWithBlock:^{
    UIGraphicsBeginImageContext(CGSizeMake(400, 200));
    [img1 drawInRect:CGRectMake(0, 0, 200, 200)];
    [img2 drawInRect:CGRectMake(200, 0, 200, 200)];
    UIImage *ads = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    //回到主线程更新UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        self.imageView.image = ads;
    }];
}];
//添加依赖
[opb3 addDependency:opb1];
[opb3 addDependency:opb2];
//添加到队列
[queue addOperation:opb1];
[queue addOperation:opb2];
[queue addOperation:opb3];

简单的线程通信我们说完了,接下来就来说说稍微复杂点的,苹果官方文档给出的线程间通信的方式有以下几种:


Table 1-3  Communication mechanisms

Mechanism

Description

Direct messaging

Cocoa applications support the ability to perform selectors directly on other threads. This capability means that one thread can essentially execute a method on any other thread. Because they are executed in the context of the target thread, messages sent this way are automatically serialized on that thread. For information about input sources, see Cocoa Perform Selector Sources.  

262

Global variables, shared memory, and objects

Another simple way to communicate information between two threads is to use a global variable, shared object, or shared block of memory. Although shared variables are fast and simple, they are also more fragile than direct messaging. Shared variables must be carefully protected with locks or other synchronization mechanisms to ensure the correctness of your code. Failure to do so could lead to race conditions, corrupted data, or crashes.  

Conditions

Conditions are a synchronization tool that you can use to control when a thread executes a particular portion of code. You can think of conditions as gate keepers, letting a thread run only when the stated condition is met. For information on how to use conditions, see Using Conditions.  

Run loop sources

A custom run loop source is one that you set up to receive application-specific messages on a thread. Because they are event driven, run loop sources put your thread to sleep automatically when there is nothing to do, which improves your thread’s efficiency. For information about run loops and run loop sources, see Run Loops.  

Ports and sockets

Port-based communication is a more elaborate way to communication between two threads, but it is also a very reliable technique. More importantly, ports and sockets can be used to communicate with external entities, such as other processes and services. For efficiency, ports are implemented using run loop sources, so your thread sleeps when there is no data waiting on the port. For information about run loops and about port-based input sources, see Run Loops.  

Message queues

The legacy Multiprocessing Services defines a first-in, first-out (FIFO) queue abstraction for managing incoming and outgoing data. Although message queues are simple and convenient, they are not as efficient as some other communications techniques. For more information about how to use message queues, see Multiprocessing Services Programming Guide.

Cocoa distributed objects

Distributed objects is a Cocoa technology that provides a high-level implementation of port-based communications. Although it is possible to use this technology for inter-thread communication, doing so is highly discouraged because of the amount of overhead it incurs. Distributed objects is much more suitable for communicating with other processes, where the overhead of going between processes is already high. For more information, see Distributed Objects Programming Topics.  

按照技术复杂度由低到高排列,其中后两种只能在OS X中使用,我们暂且忽略:


Direct messaging:其实这就是大家最熟悉的perforSelector。


Global variables...:通过全局变量、共享内存等方式,但这种方式会造成资源抢夺,涉及到线程安全问题,所以上面列出的notify和信号量就可以用来解决这种方式。


Conditions:一种特殊的锁--条件锁,当使用条件锁使一个线程等待(wait)时,该线程会被阻塞并进入休眠状态,在另一个线程中对同一个条件锁发送信号(single),则等待中的线程会被唤醒继续执行任务,有点像信号量?


Run loop sources:通过自定义Run loop sources来实现,在AF中就曾使用添加port的方式来使线程保活。


Ports and sockets:通过端口和套接字来实现线程间通讯。


关于NSPort,NSPort有3个子类,NSSocketPort、NSMessagePort、NSMachPort,NSMachPort和NSMessagePort只允许本地(在一样的机器上)通信。NSSocketPort允许本地和远程两种通讯,但是对于本地通信,NSSocketPort会更加耗费资源。创建NSPort对象,可以使用allocWithZone:或port,NSMachPort对象创建例外。所以综上所述,在iOS下只有NSMachPort可用。


使用的方式为接收线程中注册的NSMachPort,在另外的线程中使用此port发送消息,则被注册线程会收到相应消息,最终在主线程里调用某个回调函数。


可以看到,使用NSMachPort的结果为调用了其它线程的1个函数,而这正是performSelector所做的事情,这时候你会发现,NSThread的使用突然高大上起来,由此可见,NSMachPort就有些鸡肋,但这并不妨碍你使用。如果你要线程保活的话,当然还是推荐使用port来做,实际上,port什么都没做,只是起到唤醒runloop的作用。


总结:到这里,线程间通信的方式基本上算是讲全了,只是碍于业务发展,有些并不适合,也并不常用,选择适合业务场景的才是最好的,不要盲目使用,反而会影响性能。

目录
相关文章
|
6天前
|
IDE Android开发 iOS开发
探索Android与iOS开发的差异:平台选择对项目成功的影响
【9月更文挑战第27天】在移动应用开发的世界中,Android和iOS是两个主要的操作系统平台。每个系统都有其独特的开发环境、工具和用户群体。本文将深入探讨这两个平台的关键差异点,并分析这些差异如何影响应用的性能、用户体验和最终的市场表现。通过对比分析,我们将揭示选择正确的开发平台对于确保项目成功的重要作用。
|
14天前
|
开发框架 数据可视化 Java
iOS开发-SwiftUI简介
iOS开发-SwiftUI简介
|
2天前
|
开发框架 移动开发 Android开发
安卓与iOS开发中的跨平台解决方案:Flutter入门
【9月更文挑战第30天】在移动应用开发的广阔舞台上,安卓和iOS两大操作系统各自占据半壁江山。开发者们常常面临着选择:是专注于单一平台深耕细作,还是寻找一种能够横跨两大系统的开发方案?Flutter,作为一种新兴的跨平台UI工具包,正以其现代、响应式的特点赢得开发者的青睐。本文将带你一探究竟,从Flutter的基础概念到实战应用,深入浅出地介绍这一技术的魅力所在。
18 7
|
6天前
|
开发框架 前端开发 Android开发
安卓与iOS开发中的跨平台解决方案
【9月更文挑战第27天】在移动应用开发的广阔天地中,安卓和iOS两大操作系统如同双子星座般耀眼。开发者们在这两大平台上追逐着创新的梦想,却也面临着选择的难题。如何在保持高效的同时,实现跨平台的开发?本文将带你探索跨平台开发的魅力所在,揭示其背后的技术原理,并通过实际案例展示其应用场景。无论你是安卓的忠实拥趸,还是iOS的狂热粉丝,这篇文章都将为你打开一扇通往跨平台开发新世界的大门。
|
12天前
|
前端开发 iOS开发 开发者
探索iOS开发中的SwiftUI框架
【9月更文挑战第21天】在iOS应用开发的广阔天地中,SwiftUI框架如一股清新之风,为开发者带来了声明式语法的便捷与高效。本文将深入探讨SwiftUI的核心概念、布局方式及数据绑定机制,同时通过实例演示如何运用SwiftUI构建用户界面,旨在引领读者领略SwiftUI的魅力,并激发其对iOS开发新趋势的思考与实践。
30 6
|
12天前
|
安全 Swift iOS开发
探索iOS开发之旅:Swift语言的魅力与挑战
【9月更文挑战第21天】在这篇文章中,我们将一起潜入iOS开发的海洋,探索Swift这门现代编程语言的独特之处。从简洁的语法到强大的功能,Swift旨在让开发者能够以更高效、更安全的方式构建应用程序。通过实际代码示例,我们会深入了解Swift如何简化复杂任务,并讨论它面临的挑战和未来的发展方向。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角和知识。
28 4
|
22天前
|
IDE 开发工具 Android开发
安卓与iOS开发对比:平台选择对项目成功的影响
【9月更文挑战第10天】在移动应用开发的世界中,选择正确的平台是至关重要的。本文将深入探讨安卓和iOS这两大主要移动操作系统的开发环境,通过比较它们的市场份额、开发工具、编程语言和用户群体等方面,为开发者提供一个清晰的指南。我们将分析这两个平台的优势和劣势,并讨论如何根据项目需求和目标受众来做出最佳选择。无论你是初学者还是有经验的开发者,这篇文章都将帮助你更好地理解每个平台的特性,并指导你做出明智的决策。
|
21天前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
23天前
|
开发框架 Android开发 iOS开发
探索安卓与iOS开发的差异:构建未来应用的指南
在移动应用开发的广阔天地中,安卓与iOS两大平台各占半壁江山。本文将深入浅出地对比这两大操作系统的开发环境、工具和用户体验设计,揭示它们在编程语言、开发工具以及市场定位上的根本差异。我们将从开发者的视角出发,逐步剖析如何根据项目需求和目标受众选择适合的平台,同时探讨跨平台开发框架的利与弊,为那些立志于打造下一个热门应用的开发者提供一份实用的指南。
47 5
|
23天前
|
开发工具 Android开发 iOS开发
安卓与iOS开发:平台选择的艺术与科学
在移动应用开发的广阔天地中,安卓与iOS两大平台如同东西方哲学的碰撞,既有共通之处又各具特色。本文将深入探讨这两个平台的设计理念、开发工具和市场定位,旨在为开发者提供一份简明扼要的指南,帮助他们在这场技术与商业的博弈中找到自己的道路。通过比较分析,我们将揭示每个平台的优势与局限,以及它们如何影响应用的性能、用户体验和市场接受度。无论你是初涉江湖的新手,还是经验丰富的老手,这篇文章都将为你的选择提供新的视角和思考。
31 5
下一篇
无影云桌面