手机APP开发 2016-11-21
每一个iOS应用(进程)运行都会有一个主线程(UI线程),UI上的更新推荐在主线程中去完成。多线程本身并不复杂,难点在于多个线程在其生命周期的管理,如线程的执行顺序、线程间的数据共享以及资源竞争等问题。
本文主要记录开发中常用的3种多线程模式:
NSThread是一种轻量级的多线程开发模式,使用起来也比较简单主要通过一下两个方法来创建新线程
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument; - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0); [NSThread start];
线程状态分为3类:正在运行、已经完成、正在取消;可以用cancel来改变线程状态,注意这并没有真正的终止线程,除非调用其静态方法[NSTread exist]来终止线程。
线程优先级的范围是0~1,值越大优先级越高,线程默认值为0.5
NSObject分类 NSThreadPerformAdditions提供线程UI更新的主要方法如下:
//主线程才能更新UI [self performSelectorOnMainThread:@selector(updateImageView:) withObject:imagedata waitUntilDone:YES]; - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0); - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
总结
类似C#中的线程池,创建NSOperation放入NSOperationQueue队列中一次启动执行。NSOperation更加容易管理线程总数和线程之间的依赖关系。
NSOperation有两个子类用于创建线程:NSInvocationOperation 和 NSBlockOperation。
// NSInvocationOperation创建
//创建NSOperation
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector() object:nil];
//创建操作队列
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//加入到队列,开启一个线程执行队列中的线程
[queue addOperation:operation];
//NSBlockOperation创建
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
//1、创建
queue.maxConcurrentOperationCount = 5;
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
}];
[queue addOperation:operation];
//2、直接代码块加入队列
[queue addOperationWithBlock:^{
}]; 总结
基于C语言开发的一套多线程开发机制,也是苹果推荐的多线程开发方法。这种机制最显著的优点就是他对多核运算更佳有效。
GCD有一个类似NSOperationQueue的队列,分为:
//创建一个串行异步队列
dispatch_queue_t serialQueue = dispatch_queue_create(@"queuename", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
});
//UI更新
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_sync(mainQueue, ^{
});并发队列同样是使用dispatch_queue_create()方法创建,只是最后一个参数指定为DISPATCH_QUEUE_CONCURRENT进行创建,但是在实际开发中我们通常不会重新创建一个并发队列而是使用dispatch_get_global_queue()方法取得一个全局的并发队列。
//创建一个全局并行异步队列
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(global, ^{
}); GCD其他常用的执行方法:
GCD的锁机制
说到多线程就不得不提多线程中的锁机制,多线程操作过程中往往多个线程是并发执行的,同一个资源可能被多个线程同时访问,造成资源抢夺,这个过程中如果没有锁机制往往会造成重大问题。
NSLock : 同步锁NSLock来解决,使用时把需要加锁的代码(以后暂时称这段代码为”加锁代码“)放到NSLock的lock和unlock之间,一个线程A进入加锁代码之后由于已经加锁,另一个线程B就无法访问,只有等待前一个线程A执行完加锁代码后解锁,B线程才能访问加锁代码。
@synchronized代码块:日常开发中也更推荐使用此方法。首先选择一个对象作为同步对象(一般使用self),然后将”加锁代码”(争夺资源的读取、修改代码)放到代码块中。@synchronized中的代码执行时先检查同步对象是否被另一个线程占用,如果占用该线程就会处于等待状态,直到同步对象被释放。
//线程同步
@synchronized(self){
if (_imageNames.count>0) {
name=[_imageNames lastObject];
[NSThread sleepForTimeInterval:0.001f];
[_imageNames removeObject:name];
}
}GCD信号机制
在GCD中提供了一种信号机制,也可以解决资源抢占问题(和同步锁的机制并不一样)。GCD中信号量是dispatch_semaphore_t类型,支持 信号通知 和 信号等待。每当发送一个信号通知,则信号量+1;每当发送一个等待信号时信号量-1,;如果信号量为0则信号会处于等待状态,直到信号量大于0开始执行。根据这个原理我们可以初始化一个信号量变量,默认信号量设置为1,每当有线程进入“加锁代码”之后就调用信号等待命令(此时信号量为0)开始等待,此时其他线程无法进入,执行完后发送信号通知(此时信号量为1),其他线程开始进入执行,如此一来就达到了线程同步目的。
dispatch_semaphore_t _semaphore;//定义一个信号量
//初始化信号量参数
_semaphore = dispatch_semaphore_create(1);
//信号等待,第二个参数:等待时间
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
if (_imageNames.count>0) {
name=[_imageNames lastObject];
[_imageNames removeObject:name];
}
//信号通知
dispatch_semaphore_signal(_semaphore);