iOS 开发 多线程详解
Posted 许小罗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 开发 多线程详解相关的知识,希望对你有一定的参考价值。
线程状态分为isExecuting(正在执行)、isFinished(已经完成)、isCancellled(已经取消)三种。其中取消状态程序可以干预设置,只要调用线程的cancel方法即可。但是需要注意在主线程中仅仅能设置线程状态,并不能真正停止当前线程,如果要终止线程必须在线程中调用exist方法,这是一个静态方法,调用该方法可以退出当前线程。
NSThread
NSOperation
/*创建一个调用操作
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];
}
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]];
}];
}
-(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];
}];
线程执行顺序
前面使用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中也有一个类似于NSOperationQueue的队列,GCD统一管理整个队列中的任务。但是GCD中的队列分为并行队列和串行队列两类:
- 串行队列:只有一个线程,加入到队列中的操作按添加顺序依次执行。
- 并发队列:有多个线程,操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理。
-(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()方法取得一个全局的并发队列(当然如果有多个并发队列可以使用前者创建)。下面通过并行队列演示一下多个图片的加载。代码与上面串行队列加载类似,只需要修改照片加载方法如下:
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]];
});
}
}
其他任务执行方法
dispatch_apply(100,dispatch_queue_create("myThread", DISPATCH_QUEUE_PRIORITY_DEFAULT), ^(size_t index) {
NSLog(@"index == %zu and thread == %@",index,[NSThread currentThread]);
});
static id __singleton__;
dispatch_once( &__singletonToken, ^{
__singleton__ = [[self alloc] init];
} );
__block SeanGCDViewController* bself = self;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
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-1,dispatch-2
//
//
// 然后执行
//
// dispatch_barrier_async中的操作,(现在就只会执行这一个操作)执行完成后,即输出
//
// "dispatch-barrier,
// 最后该并行队列恢复原有执行状态,继续并行执行
//
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”是最后输出。
线程同步
总结
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中串行队列中的任务被安排到一个单一线程执行(不是主线程),可以方便地控制执行顺序;并发队列在多个线程中执行(前提是使用异步方法),顺序控制相对复杂,但是更高效。
以上是关于iOS 开发 多线程详解的主要内容,如果未能解决你的问题,请参考以下文章