iOS中的网络和多线程编程(一)

简介: iOS中的网络和多线程编程(一)

在移动互联网时代,几乎所有的应用程序都需要使用到网络请求,只有通过网络和外界进行数据交换、数据更新,应用程序才能保持新鲜与活力。网络编程是实时更新应用程序数据的最常用手段之一。而为了编写高效的网络请求模块,开发者必须能够灵活运用多线程的各种操作。


iOS网络编程与多线程基础



1.NSThread

NSThread是封装程度最小、最轻量级的多线程编程接口,它使用更灵活,但要手动管理线程的生命周期、线程同步和线程加锁等,开销较大。


NSThread的使用比较简单,可以动态创建并初始化NSThread对象,对其进行设置然后启动;也可以通过NSThread的静态方法快速创建并启动新线程;此外NSObject基类对象还提供了隐式快速创建NSThread线程的performSelector系列扩展工具类方法,和一些静态工具接口来控制当前线程以及获取当前线程的一些信息。


下面以在一个UIViewController中为例展示NSThread的使用方法。代码如下:

//NSTread静态工具方法
//1.是否开启了多线程
BOOL isMultiThreaded = [NSThread isMultiThreaded];
//2.获取当前线程
NSThread *currentThread = [NSThread currentThread];
//3.获取主线程
NSThread *mainThread = [NSThread mainThread];
//4.睡眠当前线程
//线程睡眠5s
[NSThread sleepForTimeInterval:5];
//线程睡眠到指定时间,效果同上
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]];
//5.退出当前线程,注意不要用在主线程调用,防止主线程被kill掉
[NSThread exit];
//Thread线程对象的基本创建,target为入口方法所在的对象,selector为线程入口方法
//1.线程实例对象创建与设置
NSThread *newThread = [[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];
//设置线程优先级threadPriority(0~1.0),该属性即将被抛弃,将使用qualityOfService代替
//newThread.threadPriority = 1.0;
newThread.qualityOfService = NSQualityOfServiceUserInteractive;
//开启线程
[newThread start];
//2.静态方法快速创建并开启新线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
[NSThread detachNewThreadWithBlock:^{
    NSLog(@"block run...");
}];
//NSObject基类隐式创建线程的一些静态工具方法
//1.在当前线程上执行方法,延迟2s
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
//2.在指定线程上执行方法,不等待当前线程
[self performSelector:@selector(run) onThread:newThread withObject:nil waitUntilDone:NO];
//3.后台异步执行方法
[self performSelectorInBackground:@selector(run) withObject:nil];
//4.在主线程上执行方法
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];


2.GCD


GCD(Grand Central Dispatch),又叫大中央调度,它对线程操作进行了封装,加入了很多新的特性,内部进行了效率优化,提供了简洁的C语言接口,使用更加简单高效,也是苹果公司推荐的方式。


这里对GCD进行简单提炼,整理出如下必会内容,可以帮助读者快速掌握使用。

1)同步dispatch_sync与异步dispatch_async任务派发。

2)串行队列与并发队列dispatch_queue_t。

3)dispatch_once_t只执行一次。

4)dispatch_after延后执行。

5)dispatch_group_t组调度。


(1)串行与并发(Serial和Concurrent)

这个概念在创建操作队列的时候有宏定义参数,用来指定创建的是串行队列还是并行队列。串行指队列内任务一个接一个地执行,任务之间要依次等待不可重合,且添加的任务按照先进先出(FIFO)的顺序执行,但并不是指这就是单线程,只是同一个串行队列内的任务需要依次等待排队执行避免出现竞态条件,仍然可以创建多个串行队列并行地执行任务。也就是说,串行队列内是串行的,串行队列之间仍然是可以并行的,同一个串行队列内的任务的执行顺序是确定的(FIFO),且可以创建任意多个串行队列。


并行指同一个队列先后添加的多个任务可以同时并列执行,任务之间不会相互等待,且这些任务的执行顺序和执行过程不可预测。


(2)同步和异步任务派发(Synchronous和Asynchronous)


GCD多线程编程时经常会使用dispatch_async和dispatch_sync函数向指定队列中添加任务块,区别就是同步和异步。


同步指阻塞当前线程,即要等添加的耗时任务块block完成后,函数才能返回,后面的代码才可以继续执行。如果在主线上,那么会发生阻塞,用户会感觉应用不响应,这是要避免的。有时需要使用同步任务的原因是想保证先后添加的任务要按照编写的逻辑顺序依次执行。


异步指将任务添加到队列后函数立刻返回,后面的代码不用等待添加的任务完成返回即可继续执行。异步提交无法确认任务的执行顺序。


通过下面的代码可以比较异步和同步任务的区别。

//1.提交异步任务
NSLog(@"开始提交异步任务");
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //耗时任务
    [NSThread sleepForTimeInterval:10];
});
NSLog(@"异步任务提交成功!");
//2.提交同步任务
NSLog(@"开始提交同步任务:");
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //耗时任务
    [NSThread sleepForTimeInterval:10];
});
NSLog(@"同步任务提交成功!");


打印结果为:

2021-07-23 17:20:31.367201+0800 网络与多线程编程[47185:2875294] 开始提交异步任务
2021-07-23 17:20:31.367352+0800 网络与多线程编程[47185:2875294] 异步任务提交成功!
2021-07-23 17:20:31.367443+0800 网络与多线程编程[47185:2875294] 开始提交同步任务:
2021-07-23 17:20:41.368782+0800 网络与多线程编程[47185:2875294] 同步任务提交成功!


通过打印结果的时间可以看出,异步任务提交后立即就执行下一步打印提交成功了,不会阻碍当前线程,提交的任务会在后台去执行;而提交同步任务要等到提交的后台任务结束后才可以继续执行当前线程的下一步。此处在主线程上添加的同步任务就会阻塞主线程,导致后面界面的显示要延迟,影响用户体验。


(3)dispatch_queue_t

GCD队列有两种类型:并发队列和串行队列。它们的区别上面已经提到,具体创建的方法很简单,要提供两个参数,一个是标记该自定义队列的唯一字符串;另一个是指定串行队列还是并发队列的宏参数。

//创建一个并发队列
dispatch_queue_t concurrent_queue = dispatch_queue_create("demo.gcd.concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
//创建一个串行队列
dispatch_queue_t serial_queue = dispatch_queue_create("demo.gcd.serial_queue", DISPATCH_QUEUE_SERIAL);


另外,GCD还提供了几个常用的全局队列以及主队列,其中全局队列本质是并发队列,主队列本质是串行队列,获取方法如下:

//获取主队列(在主线程上执行)
dispatch_queue_t main_queue = dispatch_get_main_queue();
//获取不同优先级的全局队列(优先级从高到低)
dispatch_queue_t queue_high = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t queue_default = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue_low = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t queue_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);


(4)dispatch_once_t


这个函数控制指定代码只会被执行一次,常用来实现单例模式。这里以单例模式实现的模板代码为例展示dispatch_once_t的用法,其中的实例化语句只会被执行一次。

+(instancetype)sharedInstance {
    static dispatch_once_t once = 0;
    static id sharedInstance = nil;
    dispatch_once(&once, ^{
        //只实例化一次
        sharedInstance = [[self alloc]init];
    });
    return sharedInstance;
}


(5)dispatch_after


通过该函数可以让要提交的任务在从提交开始后的指定时间执行,也就是定时延迟执行提交的任务,使用方法很简单。

//定义延迟时间:3s
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
dispatch_after(delay, dispatch_get_main_queue(), ^{
    //要执行的任务...
});


(6)dispatch_group_t


组调度可以实现等待一组操作都完成后执行后续操作。典型的例子是大图片的下载,例如可以将大图片分成几块同时下载,等各部分都下载完再后续将图片拼接起来,提高下载的效率。使用方法如下:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
    //操作1
});
dispatch_group_async(group, queue, ^{
    //操作2
});
dispatch_group_async(group, queue, ^{
    //操作3
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    //后续操作...
});


(7)同步代码到主线程


对于UI的更新代码,必须要在主线程上执行才会及时有效,当当前代码不在主线程时,需要将UI更新的部分代码单独同步到主线程。同步的方法有3种,可以使用NSThread类的performSelectorOnMainThread方法或者NSOperationQueue类的mainQueue主队列来进行同步,但推荐直接使用GCD方法。

dispatch_async(dispatch_get_main_queue(), ^{
    //UI更新代码...
});


3.NSOperation


NSOperation是基于GCD的一个抽象基类,将线程封装成要执行的操作,不需要管理线程的生命周期和同步,但比GCD可控性更强,例如可以加入操作依赖(addDependency)控制操作执行顺序,设置操作队列最大可并发执行的操作个数(setMaxConcurrentOperationCount),取消操作(cancel)等。


NSOperation作为抽象基类不具备封装开发者的操作的功能,需要使用两个它的实体子类:NSBlockOperationNSInvocationOperation,或者继承NSOperation自定义子类。NSInvocationOperationNSBlockOperation用法的主要区别是:前者执行指定的方法,后者执行代码块,相对来说后者更加灵活易用。NSOperation操作配置完成后便可调用start函数在当前线程执行,如果要异步执行避免阻塞当前线程,那么可以加入NSOperationQueue中异步执行。它们的简单用法如下:

- (void)viewDidLoad {
    [super viewDidLoad];
    //NSInvocationOperation初始化
    NSInvocationOperation *invoOperation = [[NSInvocationOperation   alloc]initWithTarget:self selector:@selector(run) object:nil];
    [invoOperation start];
    //NSBlockOperation初始化
    NSBlockOperation *blkOperation = [NSBlockOperation       blockOperationWithBlock:^{
        NSLog(@"NSBlockOperation");
    }];
    [blkOperation start];
}
- (void)run{
    NSLog(@"NSInvocationOperation");
}


另外,NSBlockOperation可以后续继续添加block执行块,操作启动后会在不同线程并发地执行这些执行块。

//NSBlockOperation初始化
NSBlockOperation *blkOperation = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"NSBlockOperationA %@",[NSThread currentThread]);
}];
[blkOperation addExecutionBlock:^{
    NSLog(@"NSBlockOperationB %@",[NSThread currentThread]);
}];
[blkOperation addExecutionBlock:^{
    NSLog(@"NSBlockOperationC %@",[NSThread currentThread]);
}];
[blkOperation start];


程序的输出结果为:

2021-07-23 18:08:32.750030+0800 网络与多线程编程[48288:2910528] NSBlockOperationB <NSThread: 0x600003456600>{number = 6, name = (null)}
2021-07-23 18:08:32.750036+0800 网络与多线程编程[48288:2910530] NSBlockOperationA <NSThread: 0x600003461740>{number = 3, name = (null)}
2021-07-23 18:08:32.750030+0800 网络与多线程编程[48288:2910397] NSBlockOperationC <NSThread: 0x60000346c200>{number = 1, name = main}


另外,NSOperation的可控性比GCD要强,其中一个非常重要的特性是可以设置各操作之间的依赖实现线程同步,例如,规定操作A要在操作B完成之后才能开始执行,称为操作A依赖于操作B。而GCD中任务默认是先进先出的,要实现线程同步需要通过组队列或信号量等方式实现。

//获取主队列(主线程)
NSOperationQueue *queue = [NSOperationQueue mainQueue];
//创建a、b、c操作
NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"OperationC");
}];
NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"OperationA");
}];
NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"OperationB");
}];
//添加操作依赖,c依赖于a和b,这样c一定会在a和b完成后才执行,即顺序为:A、B、C
[c addDependency:a];
[c addDependency:b];
//添加操作a、b、c到操作队列queue(特意将c在a和b之前添加)
[queue addOperation:c];
[queue addOperation:a];
[queue addOperation:b];


NSBlockOperationNSInvocationOperation可以满足多数情况下的编程需求,如果需求特殊,那么需要继承NSOperation类自定义子类来更加灵活地实现。

目录
相关文章
|
6天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
38 17
|
15天前
|
Linux
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
50 26
|
2月前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
221 2
|
3月前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
2月前
|
缓存 Java 调度
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
58 10
|
2月前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
2月前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
72 3
|
2月前
|
算法 调度 开发者
多线程编程核心:上下文切换深度解析
在多线程编程中,上下文切换是一个至关重要的概念,它直接影响到程序的性能和响应速度。本文将深入探讨上下文切换的含义、原因、影响以及如何优化,帮助你在工作和学习中更好地理解和应用多线程技术。
54 4
|
2月前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
3月前
|
安全 Java 开发者
Java中的多线程编程:从基础到实践
本文深入探讨了Java多线程编程的核心概念和实践技巧,旨在帮助读者理解多线程的工作原理,掌握线程的创建、管理和同步机制。通过具体示例和最佳实践,本文展示了如何在Java应用中有效地利用多线程技术,提高程序性能和响应速度。
95 1

热门文章

最新文章