基本每门语言都有多线程这个技术点,多线程是为了实现并发执行,可以理解为一个系统进程是由一个或多个线程组成的。iOS中创建线程的方式简单到可以直接调用对象的方法来实现,下面我们来看看。
调用NSObject方法实现多线程
NSObject提供了以 performSelector为前缀的一系列方法。它允许用户在指定的线程、什么时间执行某个方法的调用。实现了多线程编程最简洁的方式
在当前线程中执行方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelectorwithObject:(id)object;
- (id)performSelector:(SEL)aSelectorwithObject:(id)object1 withObject:(id)object2;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;//delay为延迟多少秒调用
在主线程中执行方法
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait;
在指定线程中执行方法
- (void)performSelector:(SEL)aSelectoronThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)waitmodes:(NSArray *)array;
- (void)performSelector:(SEL)aSelectoronThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
在后台线程中执行方法
- (void)performSelectorInBackground:(SEL)aSelectorwithObject:(id)arg;
取消延迟调用的方法+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelectorobject:(id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
上述方法中需要注意的是
指定执行的方法(但传入的参数数量有限制,最多两个);
iOS默认的主线程为UI线程,更新UI的代码需要在主线程中执行,这点与Android相同,一般在非主线程需要进行UI更新,可以调用performSelectorOnMainThread来实现;
如果指定在后台线程执行,则会自动创建一个线程去调用方法
如果使用了waitUntilDone:相关的方法来延迟调用方法,在期间如果不想调用了可以取消掉
NSThread
以对象的方式来创建线程,它是线程的一个轻量级实现,有点类似于Java的Thread类。在执行一些简单任务时,NSThread很有用,比如线程休眠、线程同步、线程间通信
三种创建方式
立即执行
[NSThreaddetachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:selfwithObject:nil];
通过 start执行
NSThread* myThread = [[NSThread alloc]initWithTarget:self
selector:@selector(myThreadMainMethod:)
object:nil];
[myThread start];
通过继承方式
@interface TalkService :NSThread
@end
@implementationTalkService
-(void)main{
//要执行的代码
NSLog(@"哄");
}
@end
TalkService *talk = [TalkService new];
[talk start];
相关方法
开启线程 - (void)start;
停止线程
- (void)cancel;
+ (void)exit;与前者的区别在于,它是强制性的,不会给你线程清理任何资源的机会
休眠线程
+ (void)sleepUntilDate:(NSDate*)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
获取主线程 [NSThread mainThread];
判断当前线程是否为主线程 [NSThread isMainThread]
获取当前线程 [NSThread currentThread];
线程间同步
涉及到线程间同步仍然需要配合使用NSLock,NSCondition 或者 @synchronized。
一个简单例子,两个人同时一起抢火车票,但是票只有最后一张了,看谁先抢到,当然这里的抢票规则完全靠哪个线程被先执行谁就先拿到票,话说我现在都还没抢到高铁票,过年又得骑车回家啦
@interface TestNSObjectThread : NSObject
@property(nonatomic,strong) NSLock *lock;
@property(nonatomic) int count;
-(void)test;
@end
@implementation TestNSObjectThread
-(void)test{
self.lock = [[NSLock alloc] init];
self.count = 1;
NSThread *mokai = [[NSThread alloc] initWithTarget:self selector:
@selector(run) object:nil];
[mokai setName:@"mokai"];
[mokai start];
NSThread *gongkai = [[NSThread alloc] initWithTarget:self selector:
@selector(run) object:nil];
[gongkai setName:@"gongkai"];
[gongkai start];
[NSThread sleepForTimeInterval:1];//因为为控制台项目,所以要防止主线程先执行完结束
}
-(void)run{
[self.lock lock];//给lock锁上,这里可以通俗地理解为一间房一次只能进一个人,每个进去时把门反锁,出来时把门打开
if(self.count > 0){
self.count--;
NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);
}
[self.lock unlock];//解锁
}
@end
上述run方法也可以用@synchronized来代替
@synchronized(self){
if(self.count > 0){
self.count--;
NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);
}
}
传进去的self为对象锁,可以理解为该对象做了一个标识“这个对象我在用,你等着!我用完你再用。”
好了,多运行多几次,控制台会随机的输出:
2015-01-06 11:38:18.119 TestMultithreading[1266:77053] mokai抢到一张火车票
2015-01-06 11:38:26.028 TestMultithreading[1269:77140] mokai抢到一张火车票
2015-01-06 11:38:32.259 TestMultithreading[1273:77236] mokai抢到一张火车票
2015-01-06 11:38:38.450 TestMultithreading[1276:77311]gongkai抢到一张火车票
GCD
参考:iOS多线程GCD
GCD(GrandCentral Dispatch),基于系统多核编程、C函数式编程、结合Blocks配合使用
简单一例
在上面我们可以通过performSelector系列方法来创建线程
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES];
在GCD中,我们是这样写的
dispatch_queue_t t = dispatch_get_main_queue();
dispatch_async(t, ^{
[self run];
});
虽然看起来没前者那么简洁,尤其对于面向对象开发者来说,函数式编程怎么看怎么不爽。但是官方说了,
GCD
是基于多核的,性能杆杆的,强烈建议能用
GCD
解决尽量使用
GCD
解决。不管你用不用,反正我平常都是用的本文第一种“调用NSObject方法实现多线程”
分发队列
执行任务的容器,可以提供各种自定义操作,如并行,串行
在GCD中有三种队列:
1、主线程队列
主线程队列中的任务会在应用的主线程中执行。一般用于UI更新操作
dispatch_queue_t queue =dispatch_get_main_queue();
2、并行队列
串行分发队列又被称为全局分发队列,由系统创建三个不同优先级的dispatchqueue,也按顺序执行队列中的任务,但是顺序开始的多个任务会并发同时执行。并发分发队列常用于管理并发任务。
dispatch_queue_t queue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
3、串行队列
串行分发队列又被称为私有分发队列,按顺序执行队列中的任务,且同一时间只执行一个任务。但各个串行队列之间是并发的。
当想要任务按照某一个特定的顺序执行时,串行队列是很有用的。串行队列在同一个时间只执行一个任务。我们可以使用串行队列代替锁去保护共享的数据。和锁不同,一个串行队列可以保证任务在一个可预知的顺序下执行。另外,通过这种方式创建的队列需要我们手动调用dispatch_release释放
dispatch_queue_t queue =dispatch_queue_create("me.mokai.queue",NULL);//注意,此处传的是C字符不是@""
任务执行
同步执行,主线程会被阻塞,可以理解为Block的执行是主线程上进行的
dispatch_sync(dispatch_queue_t queue,dispatch_block_t block);
异步执行,与同步执行相反,主线程不会被阻塞
dispatch_async(dispatch_queue_t queue,dispatch_block_t block);
加载网络图片的例子
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData * data = [NSData dataWithContentsOfURL:@"http://su.bdimg.com/static/superplus/img/logo_white_ee663702.png"];
UIImage * image = [UIImageimageWithData:data];
//加载UI的操作,一般放在主线程进行
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
实际中我们会使用了嵌套GCD来完成功能,先用后台线程执行任务,然后调用主线程执行UI更新,注意,GCD的嵌套也不能乱嵌,否则会出现死锁情况,像如下代码,会立即死锁
dispatch_queue_t queue =dispatch_queue_create("me.mokai.queue", NULL);
dispatch_sync(queue, ^{
NSLog(@"1");
dispatch_sync(queue, ^{
NSLog(@"2");
});
});
常见场景
1、在主线程执行代码
dispatch_async(dispatch_get_main_queue(),^{
// updateui
});
2
、延时执行
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC));//表示当前时间后的5秒
dispatch_after(time, dispatch_get_main_queue(), ^(void){
//执行代码
});
3、使用 dispatch_once 实现线程安全单一执行要求
GCD 的 dispatch_once能够在保证自应用运行到暂停 block只执行一次。以下是编程中经常用到的单例模式,这也是现在OC中推荐的一种方法
+ (id)sharedInstance {
static TestNSObjectThread*sharedInstance = nil;
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
操作队列NSOperation
参考:iOS多线程编程之NSOperation和NSOperationQueue的使用
NSOperation,类似于java的Runable接口,设计用来扩展的,自带有二个扩展,NSInvocationOperation(基于Selector调用)、NSBlockOperation(基于Block调用),如果想自定义只需重写main方法即可(java的run方法),使用与前面介绍的NSThread差不多NSOperationQueue队列,用来管理执行Operations的容器
NSInvocationOperation
NSInvocationOperation*operation = [[NSInvocationOperation alloc]initWithTarget:self
selector:@selector(downloadImage:)
object:kURL];
NSOperationQueue *queue= [[NSOperationQueue alloc]init];
[queue addOperation:operation];
NSBlockOperation
NSBlockOperation*operation = [NSBlockOperation blockOperationWithBlock:^{
//代码...
}];
NSOperationQueue *queue= [[NSOperationQueue alloc]init];
[queue addOperation:operation];
细粒化控制
通过NSOperation与NSOperationQueue的配合使用,可实现复杂的流程化任务。
1、NSOperation依赖对象,通过addDependency来添加一个或者多个依赖的对象,只有所有依赖的对象都已经完成操作,当前NSOperation对象才会开始执行操作。另外,通过removeDependency方法来删除依赖对象。依赖关系不局限于相同queue中的NSOperation对象,NSOperation对象会管理自己的依赖, 因此完全可以在不同的queue之间的NSOperation对象创建依赖关系
唯一的限制是不能创建环形依赖,比如A依赖B,B依赖A,这是错误的
依赖关系会影响到NSOperation对象在queue中的执行顺序,默认是按照添加顺序执行的
2、执行修改Operations的执行顺序
依赖关系相对优先级:优先级等级则是operation对象本身的一个属性。默认所有operation都拥有“普通”优先级,不过可以通过setQueuePriority:方法来提升或降低operation对象的优先级。优先级只能应用于相同queue中的operations。如果应用有多个operationqueue,每个queue的优先级等级是互相独立的。因此不同queue中的低优先级操作仍然可能比高优先级操作更早执行。
3、设置队列的最大并发操作数量
setMaxConcurrentOperationCount:默认为
如果设置为1就表示为串行化operationqueue
4、等待Options完成
[operation waitUntilFinished]; //单个operation
[queue waitUntilAllOperationsAreFinished]; //queue中全部operation
5、暂停和继续queue
[queue setSuspended:YES]; // 暂停queue
queue setSuspended:NO]; // 继续queue
重复任务NSTimer、NSRunLoop
有时应用中需要用到定时刷新的功能,如抢票APP需要频繁地请求12306,用之前的GCD也能实现效果,但是过程太痛苦了,那么苹果提供了NSTimer类来帮助我们完成重复任务。
NSTimer与NSOperation设计一样,可以通过自身来启动任务,也可以放到NSRunLoop里面执行
NSTimer*timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(log)userInfo:nil repeats:YES];//interval为刷新间隔,repeats为是否重复执行
[timer fire];//立即执行
通过这种方式创建的NSTimer结果只会调用一次refresh方法,无论repeats是YES还是NO都会如此,如果想重复执行则需要添加到NSRunLoop中,如下
NSTimer*timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(log)userInfo:nil repeats:YES];//interval为刷新间隔,repeats为是否重复执行
NSRunLoop *loop = [NSRunLoop mainRunLoop];
[loop addTimer:timer forMode:NSDefaultRunLoopMode];//增加到运行中
[loop run];//启动执行
这里的代码每隔1秒就会调用一次log方法,其实NSTimer提供了一个快捷方法,上述代码也可以写成
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(log) userInfo:nil repeats:YES];
[timer fire];
停止任务可以通过[timer invalidate]来执行,需要注意的是NSTimer没有提供暂停的接口,如果想实现暂停的功能可以先invalidate然后重新启动一个NSTimer