iOS 开发 多线程详解

常用的多线程开发有三种方式:

1.NSThread

2.NSOperation

3.GCD

线程状态分为isExecuting(正在执行)、isFinished(已经完成)、isCancellled(已经取消)三种。其中取消状态程序可以干预设置,只要调用线程的cancel方法即可。但是需要注意在主线程中仅仅能设置线程状态,并不能真正停止当前线程,如果要终止线程必须在线程中调用exist方法,这是一个静态方法,调用该方法可以退出当前线程。

NSThread

NSThread是轻量级的多线程开发,使用起来也并不复杂,但是使用NSThread需要自己管理线程生命周期。

NSThread有两种方法创建线程:

1、使用类方法:

+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument

直接将操作添加到线程中并启动。

2、使用对象方法

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument

创建一个线程对象,然后调用start方法启动线程。

通过NSThread的currentThread可以取得当前操作的线程,其中会记录线程名称name和编号number,需要注意主线程编号永远为1。多个线程虽然按顺序启动,但是实际执行未必按照顺序加载照片(loadImage:方法未必依次创建,可以通过在loadImage:中打印索引查看),因为线程启动后仅仅处于就绪状态,实际是否执行要由CPU根据当前状态调度。

为了简化多线程开发过程,苹果官方对NSObject进行分类扩展(本质还是创建NSThread),对于简单的多线程操作可以直接使用这些扩展方法。

在后台执行一个操作,本质就是重新创建一个线程执行当前方法。

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg

在指定的线程上执行一个方法,需要用户创建一个线程对象。

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait

在主线程上执行一个方法(前面已经使用过)。

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait

NSOperation

使用NSOperation和NSOperationQueue进行多线程开发类似于C#中的线程池,只要将一个NSOperation(实际开中需要使用其子类NSInvocationOperation、NSBlockOperation)放到NSOperationQueue这个队列中线程就会依次启动。NSOperationQueue负责管理、执行所有的NSOperation,在这个过程中可以更加容易的管理线程总数和控制线程之间的依赖关系。

NSOperation有两个常用子类用于创建线程操作:NSInvocationOperation和NSBlockOperation,两种方式本质没有区别,但是是后者使用Block形式进行代码组织,使用相对方便。

开一个线程的方法:

-(void)loadImageWithMultiThread{

/*创建一个调用操作

object:调用方法参数

*/

NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];

//创建完NSInvocationOperation对象并不会调用,它由一个start方法启动操作,但是注意如果直接调用start方法,则此操作会在主线程中调用,一般不会这么操作,而是添加到NSOperationQueue中

//    [invocationOperation start];

//创建操作队列

NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];

//注意添加到操作队后,队列会开启一个线程执行此操作

[operationQueue addOperation:invocationOperation];

}

开多个线程下载图片

#pragma mark 多线程下载图片

-(void)loadImageWithMultiThread{

int count=ROW_COUNT*COLUMN_COUNT;

//创建操作队列

NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];

operationQueue.maxConcurrentOperationCount=5;//设置最大并发线程数

//创建多个线程用于填充图片

for (int i=0; i<count; ++i) {

//方法1:创建操作块添加到队列

//        //创建多线程操作

//        NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{

//            [self loadImage:[NSNumber numberWithInt:i]];

//        }];

//        //创建操作队列

//

//        [operationQueue addOperation:blockOperation];

//方法2:直接使用操队列添加操作

[operationQueue addOperationWithBlock:^{

[self loadImage:[NSNumber numberWithInt:i]];

}];

}

}

#pragma mark 将图片显示到界面

-(void)updateImageWithData:(NSData *)data andIndex:(int )index{

UIImage *image=[UIImage imageWithData:data];

UIImageView *imageView= _imageViews[index];

imageView.image=image;

}

#pragma mark 请求图片数据

-(NSData *)requestData:(int )index{

NSURL *url=[NSURL URLWithString:_imageNames[index]];

NSData *data=[NSData dataWithContentsOfURL:url];

return data;

}

#pragma mark 加载图片

-(void)loadImage:(NSNumber *)index{

int i=[index integerValue];

//请求数据

NSData *data= [self requestData:i];

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

//更新UI界面,此处调用了主线程队列的方法(mainQueue是UI主线程)

[[NSOperationQueue mainQueue] addOperationWithBlock:^{

[self updateImageWithData:data andIndex:i];

}];

}

1、使用NSBlockOperation方法,所有的操作不必单独定义方法,同时解决了只能传递一个参数的问题。

2、调用主线程队列的addOperationWithBlock:方法进行UI更新,不用再定义一个参数实体(之前必须定义一个KCImageData解决只能传递一个参数的问题)。

3、使用NSOperation进行多线程开发可以设置最大并发线程,有效的对线程进行了控制(上面的代码运行起来你会发现打印当前进程时只有有限的线程被创建,如上面的代码设置最大线程数为5,则图片基本上是五个一次加载的)。

线程执行顺序

前面使用NSThread很难控制线程的执行顺序,但是使用NSOperation就容易多了,每个NSOperation可以设置依赖线程。假设操作A依赖于操作B,线程操作队列在启动线程时就会首先执行B操作,然后执行A。对于前面优先加载最后一张图的需求,只要设置前面的线程操作的依赖线程为最后一个操作即可。修改图片加载方法如下:

-(void)loadImageWithMultiThread{
    int count=ROW_COUNT*COLUMN_COUNT;
    //创建操作队列
    NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
    operationQueue.maxConcurrentOperationCount=5;//设置最大并发线程数

    NSBlockOperation *lastBlockOperation=[NSBlockOperation blockOperationWithBlock:^{
        [self loadImage:[NSNumber numberWithInt:(count-1)]];
    }];
    //创建多个线程用于填充图片
    for (int i=0; i<count-1; ++i) {
        //方法1:创建操作块添加到队列
        //创建多线程操作
        NSBlockOperation *blockOperation=[NSBlockOperation blockOperationWithBlock:^{
            [self loadImage:[NSNumber numberWithInt:i]];
        }];
        //设置依赖操作为最后一张图片加载操作
        [blockOperation addDependency:lastBlockOperation];

        [operationQueue addOperation:blockOperation];

    }
    //将最后一个图片的加载操作加入线程队列
    [operationQueue addOperation:lastBlockOperation];
}

GCD

GCD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法。前面也说过三种开发中GCD抽象层次最高,当然是用起来也最简单,只是它基于C语言开发,并不像NSOperation是面向对象的开发,而是完全面向过程的。这种机制相比较于前面两种多线程开发方式最显著的优点就是它对于多核运算更加有效。

GCD中也有一个类似于NSOperationQueue的队列,GCD统一管理整个队列中的任务。但是GCD中的队列分为并行队列和串行队列两类:

  • 串行队列:只有一个线程,加入到队列中的操作按添加顺序依次执行。
  • 并发队列:有多个线程,操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理。

其实在GCD中还有一个特殊队列就是主队列,用来执行主线程上的操作任务(从前面的演示中可以看到其实在NSOperation中也有一个主队列)。

串行队列

因为当前队列中只有一个线程,所以串行队列会按顺序执行。

使用串行队列时首先要创建一个串行队列,然后调用异步调用方法,在此方法中传入串行队列和线程操作即可自动执行。

#pragma mark 多线程下载图片

-(void)loadImageWithMultiThread{

int count=ROW_COUNT*COLUMN_COUNT;

/*创建一个串行队列

第一个参数:队列名称

第二个参数:队列类型

*/

dispatch_queue_t serialQueue = dispatch_queue_create("myThreadQueue1", DISPATCH_QUEUE_SERIAL);//注意queue对象不是指针类型

//创建多个线程用于填充图片

for (int i=0; i<count; ++i) {

//异步执行队列任务

dispatch_async(serialQueue, ^{

[self loadImage:[NSNumber numberWithInt:i]];

});

}

//非ARC环境请释放

//    dispatch_release(seriQueue);

}

并行队列

并发队列同样是使用dispatch_queue_create()方法创建,只是最后一个参数指定为DISPATCH_QUEUE_CONCURRENT进行创建,但是在实际开发中我们通常不会重新创建一个并发队列而是使用dispatch_get_global_queue()方法取得一个全局的并发队列(当然如果有多个并发队列可以使用前者创建)。下面通过并行队列演示一下多个图片的加载。代码与上面串行队列加载类似,只需要修改照片加载方法如下:

-(void)loadImageWithMultiThread{

int count=ROW_COUNT*COLUMN_COUNT;

/*取得全局队列

第一个参数:线程优先级

第二个参数:标记参数,目前没有用,一般传入0

*/

dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//创建多个线程用于填充图片

for (int i=0; i<count; ++i) {

//异步执行队列任务

dispatch_async(globalQueue, ^{

[self loadImage:[NSNumber numberWithInt:i]];

});

}

}

其他任务执行方法

GCD执行任务的方法并非只有简单的同步调用方法和异步调用方法,还有其他一些常用方法:

1、dispatch_apply():重复执行某个任务,但是注意这个方法没有办法异步执行(为了不阻塞线程可以使用dispatch_async()包装一下再执行)。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0), ^{

dispatch_apply(100,dispatch_queue_create("myThread",
DISPATCH_QUEUE_PRIORITY_DEFAULT), ^(size_t
index) {

NSLog(@"index == %zu and thread == %@",index,[NSThread
currentThread]);

});

});

单次执行一个任务,此方法中的任务只会执行一次,重复调用也没办法重复执行(单例模式中常用此方法)。

2、dispatch_once():

static
dispatch_once_t
__singletonToken;

static
id __singleton__;

dispatch_once( &__singletonToken, ^{

__singleton__ = [[self
alloc]
init];

} );

return __singleton__;

延迟一定的时间后执行。

3、dispatch_time():

double delayInSeconds =
1.0;

__block
SeanGCDViewController* bself =
self;

dispatch_time_t popTime =
dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(delayInSeconds *
NSEC_PER_SEC));

dispatch_after(popTime,
dispatch_get_main_queue(), ^(void){

// code

});

使用此方法创建的任务首先会查看队列中有没有别的任务要执行,如果有,则会等待已有任务执行完毕再执行;

同时在此方法后添加的任务必须等待此方法中任务执行后才能执行。

(利用这个方法可以控制执行顺序,例如前面先加载最后一张图片的需求就可以先使用这个方法将最后一张图片加载的操作添加到队列,然后调用dispatch_async()添加其他图片加载任务)

4、dispatch_barrier_async():

dispatch_queue_t
concurrentQueue =
dispatch_queue_create("my.concurrent.queue",
DISPATCH_QUEUE_CONCURRENT);

dispatch_async(concurrentQueue, ^(){

NSLog(@"dispatch-1");

});

dispatch_async(concurrentQueue, ^(){

NSLog(@"dispatch-2");

});

dispatch_barrier_async(concurrentQueue, ^(){

NSLog(@"dispatch-barrier");

});

dispatch_async(concurrentQueue, ^(){

NSLog(@"dispatch-3");

});

dispatch_async(concurrentQueue, ^(){

NSLog(@"dispatch-4");

});

上面代码的执行步骤:

//    dispatch_barrier_async
作用是在并行队列中,等待前面两个操作并行操作完成,这里是并行输出

//

//    dispatch-1,dispatch-2

//

//

//   
然后执行

//

//    dispatch_barrier_async中的操作,(现在就只会执行这一个操作)执行完成后,即输出

//

//    "dispatch-barrier,

//   
最后该并行队列恢复原有执行状态,继续并行执行

//

//    dispatch-3,dispatch-4

实现对任务分组管理,如果一组任务全部完成可以通过dispatch_group_notify()方法获得完成通知(需要定义dispatch_group_t作为分组标识)

5、dispatch_group_async():

dispatch_queue_t
dispatchQueue =
dispatch_queue_create("ted.queue.next",
DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t dispatchGroup =
dispatch_group_create();

dispatch_group_async(dispatchGroup, dispatchQueue, ^(){

NSLog(@"dispatch-1");

});

dispatch_group_async(dispatchGroup, dispatchQueue, ^(){

NSLog(@"dspatch-2");

});

dispatch_group_notify(dispatchGroup,
dispatch_get_main_queue(), ^(){

NSLog(@"end");

});

//   
上面的
log1
和log2输出顺序不定,因为是在并行队列上执行,当并行队列全部执行完成后,最后到main队列上执行一个操作,保证“end”是最后输出。

线程同步

说到多线程就不得不提多线程中的锁机制,多线程操作过程中往往多个线程是并发执行的,同一个资源可能被多个线程同时访问,造成资源抢夺,这个过程中如果没有锁机制往往会造成重大问题。

要解决资源抢夺问题在iOS中有常用的有两种方法:一种是使用NSLock同步锁,另一种是使用@synchronized代码块。两种方法实现原理是类似的,只是在处理上代码块使用起来更加简单。

总结

1>无论使用哪种方法进行多线程开发,每个线程启动后并不一定立即执行相应的操作,具体什么时候由系统调度(CPU空闲时就会执行)。

2>更新UI应该在主线程(UI线程)中进行,并且推荐使用同步调用,常用的方法如下:

  • - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
  • (或者-(void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL) wait;方法传递主线程[NSThread mainThread])
  • [NSOperationQueue mainQueue] addOperationWithBlock:
  • dispatch_sync(dispatch_get_main_queue(), ^{})

3>NSThread适合轻量级多线程开发,控制线程顺序比较难,同时线程总数无法控制(每次创建并不能重用之前的线程,只能创建一个新的线程)。

4>对于简单的多线程开发建议使用NSObject的扩展方法完成,而不必使用NSThread。

5>可以使用NSThread的currentThread方法取得当前线程,使用 sleepForTimeInterval:方法让当前线程休眠。

6>NSOperation进行多线程开发可以控制线程总数及线程依赖关系。

7>创建一个NSOperation不应该直接调用start方法(如果直接start则会在主线程中调用)而是应该放到NSOperationQueue中启动。

8>相比NSInvocationOperation推荐使用NSBlockOperation,代码简单,同时由于闭包性使它没有传参问题。

9>NSOperation是对GCD面向对象的ObjC封装,但是相比GCD基于C语言开发,效率却更高,建议如果任务之间有依赖关系或者想要监听任务完成状态的情况下优先选择NSOperation否则使用GCD。

10>在GCD中串行队列中的任务被安排到一个单一线程执行(不是主线程),可以方便地控制执行顺序;并发队列在多个线程中执行(前提是使用异步方法),顺序控制相对复杂,但是更高效。

11>在GCD中一个操作是多线程执行还是单线程执行取决于当前队列类型和执行方法,只有队列类型为并行队列并且使用异步方法执行时才能在多个线程中执行(如果是并行队列使用同步方法调用则会在主线程中执行)。

12>相比使用NSLock,@synchronized更加简单,推荐使用后者。

时间: 2024-08-16 03:14:44

iOS 开发 多线程详解的相关文章

iOS开发:详解Objective-C runTime

Objective-C总Runtime的那点事儿(一)消息机制 最近在找工作,Objective-C中的Runtime是经常被问到的一个问题,几乎是面试大公司必问的一个问题.当然还有一些其他问题也几乎必问,例如:RunLoop,Block,内存管理等.其他的问题如果有机会我会在其他文章中介绍. 本篇文章主要介绍RunTime. RunTime简称运行时.就是系统在运行的时候的一些机制,其中最主要的是消息机制.对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 ).编

iOS开发 - UIActivityViewController详解

昨天在做微信分享的时候, 用到了这个东西.趁热写点东西记录下. UIActivityViewController类是一个标准的view controller,通个使用这个controller,你的应用程序就可以提供各种服务. 系统提供了一些通用的标准服务,例如拷贝内容至粘贴板.发布一个公告至社交网.通过email或者SMS发送内容. 应用程序同样可以自定义服务.(我的微信分享就属于自定义服务, 之后将会写一篇教程介绍) 你的应用程序负责配置.展现和解雇这个view controller. vie

IOS开发 Blocks详解(转)

IOS开发 Blocks详解(转) (2013-10-14 16:41:54) 从Mac OS X 10.6以及iOS 4开始,苹果在GCC和Clang编译器中为C语言引入了一个新扩展:Blocks,使得程序员可以在C.Objective-C.C++和Objective-C中使用闭包.Blocks有点像函数,但是它可以在其它函数或方法中进行声明和定义,同时它还是匿名的(匿名函数),并可以捕获其所在作用域中的变量(闭包特性). Blocks的语法 Blocks和C语言中的函数指针有点类似,如果你了

iOS开发- UICollectionView详解+实例

iOS开发- UICollectionView详解+实例 本章通过先总体介绍UICollectionView及其常用方法,再结合一个实例,了解如何使用UICollectionView. UICollectionView 和 UICollectionViewController 类是iOS6 新引进的API,用于展示集合视图,布局更加灵活,可实现多列布局,用法类似于UITableView 和 UITableViewController 类. 使用UICollectionView 必须实现UICol

Android研究之游戏开发多线程详解

 游戏开发与软件开发多线程的重要性       如果程序主线程被阻塞超过5秒,系统会提示"应用程序无响应" 这就是ANR . ANR的全称是Application Not Responding,使用多线程可以避免ANR.但是这里要注意一下不要为了避免ANR而过多的使用多线程,除非万不得已的情况. 比如访问网络服务端返回的过慢.数据过多导致滑动屏幕不流畅.或者I/O读取过大的资源等等.这里可以开启一个新线程来处理这些耗时的操作. 如果过多使用多线程会出现数据同步的问题须要程序员去处理

IOS开发之----详解在IOS后台执行

文一 我从苹果文档中得知,一般的应用在进入后台的时候可以获取一定时间来运行相关任务,也就是说可以在后台运行一小段时间. 还有三种类型的可以运行在后以,1.音乐2.location 3.voip 文二 在IOS后台执行是本文要介绍的内容,大多数应用程序进入后台状态不久后转入暂停状态.在这种状态下,应用程序不执行任何代码,并有可能在任意时候从内存中删除.应用程序提供特定的服务,用户可以请求后台执行时间,以提供这些服务. 判断是否支持多线程 UIDevice* device = [UIDevice c

iOS开发——MVC详解&amp;Swift+OC

MVC 设计模式 这两天认真研究了一下MVC设计模式,在iOS开发中这个算是重点中的重点了,如果对MVC模式不理解或者说不会用,那么你iOS肯定学不好,或者写不出好的东西,当然本人目前也在学习中,不过既然能看到这篇文档,说明你已经开始着手学习并且想深入研究它了,个人也是研究很久才搞懂,就写下来希望对各位有用,也能方便自己以后开发中查看,好了废话不多说,下面就来详细介绍一下MVC,并且用实例验证一下在项目开发中怎么去使用它. 相信你对 MVC 设计模式 并不陌生,只是不能完全理解其中的含义或者不能

iOS开发--Bison详解连连支付集成简书

"最近由于公司项目需要集成连连支付,文档写的不是很清楚,遇到了一些坑,因此记录一下,希望能帮到有需要的人." 前面简单的集成没有遇到什么坑,在此整理一下官方的集成文档,具体步骤如下 导入文件 添加头文件引用 设置link标志Target->Build Setting ,Other Linker Flags 设置为 -all_load可能添加-all_load以后和其他库冲突,可以尝试使用 -force_load 单独load库, force_load后面跟的是 lib库的完整路径

iOS开发-NSURLSession详解

Core Foundation中NSURLConnection在2003年伴随着Safari浏览器的发行,诞生的时间比较久远,iOS升级比较快,AFNetWorking在3.0版本删除了所有基于NSURLConnection API的所有支持,新的API完全基于NSURLSession.AFNetworking 1.0建立在NSURLConnection的基础之上 ,AFNetworking 2.0使用NSURLConnection基础API,以及较新基于NSURLSession的API的选项.