iOS开发系列--并行开发其实很容易 后面的总结很好
iOS开发--四种多线程技术方案
关于iOS多线程,你看我就够了
GCD使用经验与技巧浅谈
使用NSOperation以及NSOperationQueue
摘录:
1.NSThread 2.NSOperation (可以自定义operation,进行扩展) 3.GCD 对应.Net中的多线程、线程池和异步调用
NSOperation是对GCD面向对象的ObjC封装,但是相比GCD基于C语言开发,效率却更高,建议如果任务之间有依赖关系或者想要监听任务完成状态的情况下优先选择NSOperation否则使用GCD。
NSThread是轻量级的多线程开发,使用起来也并不复杂,但是使用NSThread需要自己管理线程生命周期。
//方法1:使用对象方法 NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(loadImage) object:nil]; //方法2:使用类方法 [NSThread detachNewThreadSelector:@selector(loadImage) toTarget:self withObject:nil];
创建一个线程,在线程中执行loadImage方法,去下载图片,然后更新UI,[self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES];
启动: for (int i=0; i<count; ++i) { NSThread *thread= _threads[i]; [thread start]; } 取消线程: NSThread *currentThread=[NSThread currentThread]; // 如果当前线程处于取消状态,则退出当前线程 if (currentThread.isCancelled) { NSLog(@"thread(%@) will be cancelled!",currentThread); [NSThread exit];//取消当前线程 } 线程中请求: #pragma mark 请求图片数据 -(NSData *)requestData:(int )index{ //对于多线程操作建议把线程操作放到@autoreleasepool中 @autoreleasepool { NSURL *url=[NSURL URLWithString:_imageNames[index]]; NSData *data=[NSData dataWithContentsOfURL:url]; return data; } } 停止: -(void)stopLoadImage{ for (int i=0; i<ROW_COUNT*COLUMN_COUNT; i++) { NSThread *thread= _threads[i]; //判断线程是否完成,如果没有完成则设置为取消状态 //注意设置为取消状态仅仅是改变了线程状态而言,并不能终止线程 if (!thread.isFinished) { [thread cancel]; } } }
优先级
线程优先级范围为0~1,值越大优先级越高,每个线程的优先级默认为0.5。
thread.threadPriority=1.0;
线程状态分为isExecuting(正在执行)、isFinished(已经完成)、isCancellled(已经取消)三种。
线程睡眠
[NSThread sleepForTimeInterval:2.0];
扩展:
扩展--NSObject分类扩展方法 为了简化多线程开发过程,苹果官方对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和NSOperationQueue进行多线程开发类似于C#中的线程池,只要将一个NSOperation(实际开中 需要使用其子类NSInvocationOperation、NSBlockOperation)放到NSOperationQueue这个队列中线程就 会依次启动。NSOperationQueue负责管理、执行所有的NSOperation,在这个过程中可以更加容易的管理线程总数和控制线程之间的依 赖关系。
NSOperation有两个常用子类用于创建线程操作:NSInvocationOperation和NSBlockOperation,两种方式本质没有区别,但是是后者使用Block形式进行代码组织,使用相对方便。
线程依赖和使用
#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]; }]; } #pragma mark 多线程下载图片 -(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]; }
优点:
- 使用NSBlockOperation方法,所有的操作不必单独定义方法,同时解决了只能传递一个参数的问题。
- 调用主线程队列的addOperationWithBlock:方法进行UI更新,不用再定义一个参数实体(之前必须定义一个KCImageData解决只能传递一个参数的问题)。
- 使用NSOperation进行多线程开发可以设置最大并发线程,有效的对线程进行了控制(上面的代码运行起来你会发现打印当前进程时只有有限的线程被创建,如上面的代码设置最大线程数为5,则图片基本上是五个一次加载的)
GCD:
CD(Grand Central Dispatch)是基于C语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法。前面也说过三种开发中GCD抽象层次最高,当然是用起 来也最简单,只是它基于C语言开发,并不像NSOperation是面向对象的开发,而是完全面向过程的。
GCD中也有一个类似于NSOperationQueue的队列,GCD统一管理整个队列中的任务。但是GCD中的队列分为并行队列和串行队列两类:
- 串行队列:只有一个线程,加入到队列中的操作按添加顺序依次执行。
- 并发队列:有多个线程,操作进来之后它会将这些队列安排在可用的处理器上,同时保证先进来的任务优先处理。
其实在GCD中还有一个特殊队列就是主队列,用来执行主线程上的操作任务(从前面的演示中可以看到其实在NSOperation中也有一个主队列)。
pragma mark 加载图片 -(void)loadImage:(NSNumber *)index{ int i=[index integerValue]; //请求数据 NSData *data= [self requestData:i]; //更新UI界面,此处调用了GCD主线程队列的方法 dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ [self updateImageWithData:data andIndex:i]; }); } #pragma mark 多线程下载图片 -(void)loadImageWithMultiThread{ int count=ROW_COUNT*COLUMN_COUNT; // dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //这里创建一个并发队列(使用全局并发队列也可以) dispatch_queue_t queue=dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT); for (int i=0; i<count; i++) { dispatch_async(queue, ^{ [self loadImage:[NSNumber numberWithInt:i]]; }); } }
线程同步
要解决资源抢夺问题在iOS中有常用的有两种方法:一种是使用NSLock同步锁,另一种是使用@synchronized代码块。两种方法实现原理是类似的,只是在处理上代码块使用起来更加简单(C#中也有类似的处理机制synchronized和lock
原子数组
@property (atomic,strong) NSMutableArray *imageNames;
/*初始化信号量 参数是信号量初始值 */ _semaphore=dispatch_semaphore_create(1); -(NSData *)requestData:(int )index{ NSData *data; NSString *name; /*信号等待 第二个参数:等待时间 */ //每次只需先判断_imageNames的个数,如果大于一就读取一个链接加载图片,随即把用过的链接删除 dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); if (_imageNames.count>0) { name=[_imageNames lastObject]; [NSThread sleepForTimeInterval:0.001f]; [_imageNames removeObject:name]; } //信号通知 dispatch_semaphore_signal(_semaphore); if(name){ NSURL *url=[NSURL URLWithString:name]; data=[NSData dataWithContentsOfURL:url]; } return data; }
//初始化锁对象 _lock=[[NSLock alloc]init];
//加锁
[_lock lock];
//使用完解锁
[_lock unlock];
同步代码块:
//线程同步
@synchronized(self){
if (_imageNames.count>0) {
name=[_imageNames lastObject];
[NSThread sleepForTimeInterval:0.001f];
[_imageNames removeObject:name];
}
}
生产者和消费者
#pragma mark 图片资源存储容器 @property (atomic,strong) NSMutableArray *imageNames; //初始化锁对象 _condition=[[NSCondition alloc]init]; _currentIndex=0; #pragma mark 加载图片并将图片显示到界面 -(void)loadAnUpdateImageWithIndex:(int )index{ //请求数据 NSData *data= [self requestData:index]; //更新UI界面,此处调用了GCD主线程队列的方法 dispatch_queue_t mainQueue= dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ UIImage *image=[UIImage imageWithData:data]; UIImageView *imageView= _imageViews[index]; imageView.image=image; }); } #pragma mark 请求图片数据 -(NSData *)requestData:(int )index{ NSData *data; NSString *name; name=[_imageNames lastObject]; [_imageNames removeObject:name]; if(name){ NSURL *url=[NSURL URLWithString:name]; data=[NSData dataWithContentsOfURL:url]; } return data; } #pragma mark 加载图片 -(void)loadImage:(NSNumber *)index{ int i=(int)[index integerValue]; //加锁 [_condition lock]; //如果当前有图片资源则加载,否则等待 if (_imageNames.count>0) { NSLog(@"loadImage work,index is %i",i); [self loadAnUpdateImageWithIndex:i]; [_condition broadcast]; }else{ NSLog(@"loadImage wait,index is %i",i); NSLog(@"%@",[NSThread currentThread]); //线程等待 [_condition wait]; NSLog(@"loadImage resore,index is %i",i); //一旦创建完图片立即加载 [self loadAnUpdateImageWithIndex:i]; } //解锁 [_condition unlock]; } #pragma mark - UI调用方法 #pragma mark 异步创建一张图片链接 -(void)createImageWithMultiThread{ dispatch_queue_t globalQueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //创建图片链接 dispatch_async(globalQueue, ^{ [self createImageName]; }); } #pragma mark 多线程下载图片 -(void)loadImageWithMultiThread{ int count=ROW_COUNT*COLUMN_COUNT; 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]]; }); } }
iOS中的其他锁
在iOS开发中,除了同步锁有时候还会用到一些其他锁类型,在此简单介绍一下:
NSRecursiveLock :递归锁,有时候“加锁代码”中存在递归调用,递归开始前加锁,递归调用开始后会重复执行此方法以至于反复执行加锁代码最终造成死锁,这个时候可以使用递 归锁来解决。使用递归锁可以在一个线程中反复获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。
NSDistributedLock:分布锁,它本身是一个互斥锁,基于文件方式实现锁机制,可以跨进程访问。
pthread_mutex_t:同步锁,基于C语言的同步锁机制,使用方法与其他同步锁机制类似。