iOS 面试常问之多线程
Posted 看得见的灿烂,看不见的离殇
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 面试常问之多线程相关的知识,希望对你有一定的参考价值。
本片围绕多线程全面展开叙述。
1、为什么要有多线程/多线程是用来干什么的?
2、多线程是什么?
3、如何创建多线程?
4、多线程在哪些情况下会使用/多线程使用场景?
5、三种多线程的优缺点?
1、为什么要有多线程/多线程是用来干什么的?
每个ios应用程序都有个专门用来更新显示UI界面、处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验。一般的解决方案就是将那些耗时的操作放到另外一个线程中去执行,多线程编程是防止主线程堵塞,增加运行效率的最佳方法。
2、多线程是什么?
多线程是个复杂的概念,按字面意思是同步完成多项任务,提高了资源的使用效率。
从硬件、操作系统、应用软件不同的角度去看,多线程被赋予不同的内涵,对于硬件,现在市面上多数的CPU都是多核的,多核的CPU运算多线程更为出色;
从操作系统角度,是多任务,现在用的主流操作系统都是多任务的,可以一边听歌、一边写博客;
对于应用来说,多线程可以让应用有更快的回应,可以在网络下载时,同时响应用户的触摸操作。
在iOS应用中,对多线程最初的理解,就是并发,它的含义是原来先做烧水,再摘菜,再炒菜的工作,会变成烧水的同时去摘菜,最后去炒菜。
3、如何创建多线程?
线程创建有三种方法:使用NSThread创建、使用GCD的dispatch、使用子类化的NSOperation,然后将其加入NSOperationQueue中。下面对这三种方法做详细说明:
1, 使用NSThread创建
1.1 NSThread 有两种直接创建方式:
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument + (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
说明:第一个是实例方法,第二个是类方法。
selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值
target :selector消息发送的对象
argument:传输给target的唯一参数,也可以是nil
第一种方式会直接创建线程并且开始运行线程,第二种方式是先创建线程对象,然后再运行线程操作,在运行线程操作前可以设置线程的优先级等线程信息。
PS:不显式创建线程的方法:
//用NSObject的类方法 performSelectorInBackground:withObject: 创建一个线程: [Obj performSelectorInBackground:@selector(doSomething) withObject:nil];
2, 使用子类化的NSOperation
使用 NSOperation的方式有两种:
一种是用定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。
#import "ViewController.h" #define kURL @"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadImage:) object:kURL]; NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperation:operation]; // Do any additional setup after loading the view, typically from a nib. } -(void)downloadImage:(NSString *)url{ NSLog(@"url:%@", url); NSURL *nsUrl = [NSURL URLWithString:url]; NSData *data = [[NSData alloc]initWithContentsOfURL:nsUrl]; UIImage * image = [[UIImage alloc]initWithData:data]; [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES]; } -(void)updateUI:(UIImage*) image{ self.imageView.image = image; }
- viewDidLoad方法里可以看到我们用NSInvocationOperation建了一个后台线程,并且放到NSOperationQueue中。后台线程执行downloadImage方法。
- downloadImage 方法处理下载图片的逻辑。下载完成后用performSelectorOnMainThread执行主线程updateUI方法。
- updateUI 并把下载的图片显示到图片控件中。
另一种是继承NSOperation。
如何控制线程池中的线程数?
队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。
//通过下面的代码设置: [queue setMaxConcurrentOperationCount:5]; //线程池中的线程数,也就是并发操作数。默认情况下是-1,-1表示没有限制,这样会同时运行队列中的全部的操作。
3, 使用GCD的dispatch
利用dispatch_queue_create函数创建串行Dispatch Queue (serial dispatch queue)
使用dispatch_get_global_queue函数获取全局并发Dispatch Queue (concurrent dispatch queue)
使用dispatch_get_main_queue函数获得应用主线程关联的串行dispatch queue
使用dispatch_get_current_queue函数作为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中)。在block对象之外调用这个函数会返回应用的默认并发queue。
GCD常用方法:
1. dispatch_async
为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 耗时的操作 dispatch_async(dispatch_get_main_queue(), ^{ // 更新界面 }); });
2. dispatch_group_async
dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。
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, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"group1"); }); dispatch_group_async(group, queue, ^{ [NSThread sleepForTimeInterval:2]; NSLog(@"group2"); }); dispatch_group_async(group, queue, ^{ [NSThread sleepForTimeInterval:3]; NSLog(@"group3"); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"updateUi"); }); dispatch_release(group);
dispatch_group_async是异步的方法,运行后可以看到打印结果:
2012-09-25 16:04:16.737 gcdTest[43328:11303] group1 2012-09-25 16:04:17.738 gcdTest[43328:12a1b] group2 2012-09-25 16:04:18.738 gcdTest[43328:13003] group3 2012-09-25 16:04:18.739 gcdTest[43328:f803] updateUi
每个一秒打印一个,当第三个任务执行后,upadteUi被打印。
3. dispatch_barrier_async
dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
dispatch_queue_t queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:2]; NSLog(@"dispatch_async1"); }); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:4]; NSLog(@"dispatch_async2"); }); dispatch_barrier_async(queue, ^{ NSLog(@"dispatch_barrier_async"); [NSThread sleepForTimeInterval:4]; }); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"dispatch_async3"); });
打印结果:
2012-09-25 16:20:33.967 gcdTest[45547:11203] dispatch_async1 2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_async2 2012-09-25 16:20:35.967 gcdTest[45547:11303] dispatch_barrier_async 2012-09-25 16:20:40.970 gcdTest[45547:11303] dispatch_async3
请注意执行的时间,可以看到执行的顺序如上所述。
4. dispatch_apply
执行某个代码片段N次。
dispatch_apply(5, globalQ, ^(size_t index) { // 执行5次 });
4、多线程在哪些情况下会使用/多线程使用场景?
在网络请求,读取大量文件,操作数据库,大量数据解析,单例,延迟等一些耗时操作中,都会用到多线程。
5、三种多线程的优缺点?
1. NSThread优点是比其他两种多线程方案较轻量级,更直观地控制线程对象。缺点是需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。
以上是关于iOS 面试常问之多线程的主要内容,如果未能解决你的问题,请参考以下文章