1.多线程的介绍
对于一个APP来说,如果要进行一些比较耗时的操作,同时又不想影响用户的体验的时候,我们一般就会想到使用多线程来处理那些耗时的操作。多线程,在很多的编程语言中都是一个相对比较难的地方,主要涉及到线程的生命周期管理,要创建多少条线程才合理,等很多问题,在IOS开发中,多线程使用相对难道不是很大,很多复杂的处理过程系统已经给我们封装好了,我们只需要调用系统给我们封装好的方法就能够实现多线程,其中比较好用的有GCD和NSOperationQueue,这两个就能为我们很方便的实现多线程,当然NSThread也能实现多线程,不过相对于前两个NSThread就需要更多方面的注意了,好了下面给大家简单的介绍一下这几个开启多线程的方法。
2.NSThread的简单使用
NSThread使用相对还是比较简单,可以通过alloc
出一个实例化方法,然后调用start
方法来创建一个线程;
- (void)demo1 {
//第一种使用NSThread开启线程的方式.
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(print) object:nil];//使用NSThread的自定义初始化方法创建一个NSThread实例
[thread start];//调用start方法来启动线程
}
- (void)print {
NSLog(@"当前线程--->%@", [NSThread currentThread]);
}
上面我们是通过创建一个实例的方式来创建一个线程,NSThread提供了一个类方法+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
通过这个类方法我们也可以很方便的创建一个线程;
- (void)demo1 {
//第二种使用NSThread开启线程的方法.
[NSThread detachNewThreadSelector:@selector(print) toTarget:self withObject:nil];//直接调用NSThread的类方法创建一个线程,该方法不用调用start来启动线程
}
- (void)print {
NSLog(@"当前线程--->%@", [NSThread currentThread]);
}
NSThread通过上面两种方式就能够创建出多线程,完成耗时操作的任务,在NSThread类里面也有几个比较常用的属性和方法需要我们熟悉
+ (NSThread *)currentThread;//该方法能够返回当前线程的
+ (void)sleepUntilDate:(NSDate *)date;//让线程休眠到指定的时间
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;//让线程休眠多少秒
+ (void)exit;//退出线程
+ (BOOL)setThreadPriority:(double)p;//设置线程的优先级范围是0-1之间,1的优先级最高
+ (NSThread *)mainThread//得到主线程也就是UI线程
@property (readonly) BOOL isMainThread//该属性可以判断当前线程是不是主线程
除了NSThread可以开启新线程以为,我们在处理比较简单的耗时操作的时候还可以使用- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
来完成相应的操作。
- (void)demo1 {
//第三种直接调用performSelectorInBackground方法开启一个新线程
//这种方式适合处理相对比较简单的一些耗时操作
[self performSelectorInBackground:@selector(print) withObject:nil];
}
- (void)print {
NSLog(@"当前线程--->%@", [NSThread currentThread]);
}
NSThread如何开启新线程的方法就介绍到这里,当然还有很多很多的地方没有介绍到,这里直接简单的给大家说说平时使用比较多的方法,欢迎大家多提意见,共同学习和进步。介绍完NSThread之后,接下来进入我们IOS中比较重要的GCD了。
3.GCD(很牛逼的中枢调度器)的简单使用
先给大家介绍一个简单的使用GCD创建的多线程:
//获取全局并发队列,优先级为默认
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建一个一步的任务,并添加到队列里面
//可以同时开多个任务,并发任务
dispatch_async(queue, ^{
//这里就是我要需要进行的耗时操作
dispatch_async(dispatch_get_main_queue(), ^{
//当耗时操作完成之后,如果操作最后的数据需要显示到UI上面的话,我们需要回到UI线程进行UI界面的更新,如果不回到UI线程进行更新界面的话,会导致界面数据更新不成功
});
});
通过上面的例子我们可以看出,使用GCD很方便的就开启了一个新的线程来执行我们的耗时操作,在上面调用dispatch_get_global_queue(long identifier, unsigned long flags)
来创建一个并发的队列,函数的时候需要传入一个优先级,和一个flags,其中flags是一个备用参数系统让我们只需要传入0就可以,优先级系统给我们提供了四个选项
DISPATCH_QUEUE_PRIORITY_HIGH 2//最高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 0//默认优先级(一般都使用默认)
DISPATCH_QUEUE_PRIORITY_LOW (-2)//低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN//后台运行
并发执行,因为我刚刚创建的是一个并发队列;
- (void)demo2 {
//获取全局并发队列,优先级为默认
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建一个一步的任务,并添加到队列里面
//可以同时开多个任务,并发任务
dispatch_async(queue, ^{
[self print];
});
dispatch_async(queue, ^{
[self print];
});
}
- (void)print {
NSLog(@"当前线程--->%@", [NSThread currentThread]);
}
执行结果:
从这个结果我们可以看出两个任务在不同的线程,并且他们的执行时间是相同的,这就是并发队列的效果。
有并发队列就有串行队列,串行队列的特点就是执行完第一个任务才会开始执行第二个任务,在GCD里面我们可以通过dispatch_queue_create
创建一个串行队列,我们只需要把我们的任务加入到串行队列里面,任务就会按照顺序执行。
-(void)demo3{
//创建一个串行队列
dispatch_queue_t queue = dispatch_queue_create("com.sam", NULL);
//创建一个一步的任务,并添加到队列里面
//可以同时开多个任务,但是串行执行的
dispatch_async(queue, ^{
[self print];
});
dispatch_async(queue, ^{
[self print];
});
}
- (void)print {
[NSThread sleepForTimeInterval:2];//模拟耗时操作
NSLog(@"当前线程--->%@", [NSThread currentThread]);
}
执行结果:
从两个任务的执行的时间我们可以很清楚的看出是先执行完了第一个任务才执行第二个任务。
简单的并发和串行任务讲完之后,我想大家肯定还有如果有A、B、C三个任务要把A、B执行完成之后才执行C任务的需求,做这种的需要的时候我们就会使用dispatch_group_create()
来创建一个组,把A、B两个任务添加到组里面,然后等组执行完之后才执行C,具体看以下代码
-(void)demo4{
//获取全局并发队列,优先级为默认
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//创建队列组
dispatch_group_t group = dispatch_group_create();
//可以同时开多个任务,并发任务,添加到队列组里面
dispatch_group_async(group, queue, ^{
//任务A
[self print];
});
dispatch_group_async(group, queue, ^{
//任务B
[self print];
});
//队列组里面执行完成之后,执行该方法
dispatch_group_notify(group, queue, ^{
//任务C
[self print];
});
}
- (void)print {
[NSThread sleepForTimeInterval:2];//模拟耗时操作
NSLog(@"当前线程--->%@", [NSThread currentThread]);
}
执行结果:
能够看出AB两个任务并发执行,执行完成之后才执行C任务的。
GCD的基本使用方法就介绍到这里,再给大家列举一些可能会用到的方法名称:
dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 12);
dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t queue#>, <#^(void)block#>);//延时多久执行任务
dispatch_block_wait(<#^(void)block#>, <#dispatch_time_t timeout#>);//在同步锁的时候超时多少就不执行该任务
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 这里面的代码,在程序运行过程中,永远只会执行1次
});
4.NSOperationQueue实现多线程
废话就不说了,先来一段代码,让大家先看看到底是怎么实现的
- (void)demo5 {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];//创建一个队列
queue.maxConcurrentOperationCount = 2;//设置队列同时执行最多任务数量,推荐2-3
//创建一个任务
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock: ^{
[self print];
}];
//直接调用队列的方法创建一个任务
[queue addOperationWithBlock:^{
[self print];
}];
//把创建的任务添加到队列里面
[queue addOperation:operation];
}
- (void)print {
[NSThread sleepForTimeInterval:2];//模拟耗时操作
NSLog(@"当前线程--->%@", [NSThread currentThread]);
}
执行结果:
从上面代码我们可以看出,在使用的时候还是必须先创建一个队列,然后再创建需要加入多线程的任务,然后把创建好的任务添加到队列里面,这样一个多线程就创建完成了。上面往队列里面添加任务分别用了两种不同的方式,一种是创建了一个NSBlockOperation
而另外一种则是调用队列的- (void)addOperationWithBlock:(void (^)(void))block
方法往队列里面添加任务。两种方式实现的最后效果都是一样的。
最大线程数和任务之间的相互依赖,以下代码实现不同任务间的依赖关系
/**
假设有A、B、C三个操作,要求:
1. 3个操作都异步执行
2. 操作C依赖于操作B
3. 操作B依赖于操作A
*/
// 1.创建一个队列(非主队列)
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 2.创建3个操作
NSBlockOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"A1---%@", [NSThread currentThread]);
}];
NSBlockOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"B---%@", [NSThread currentThread]);
}];
NSBlockOperation *operationC = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"C---%@", [NSThread currentThread]);
}];
// 设置依赖
[operationB addDependency:operationA];
[operationC addDependency:operationB];
// 3.添加操作到队列中(自动异步执行任务)
[queue addOperation:operationC];
[queue addOperation:operationA];
[queue addOperation:operationB];
NSOperationQueue里面还有几个常用的方法大家需要记住
- (void)cancelAllOperations;//取消所有操作
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;//设置最大并发数
[queue setSuspended:YES];//暂停所有操作
[queue setSuspended:NO];//恢复所有操作
5.线程安全
线程安全实质就是多个线程同时访问或者修改同一个资源的时候导致,资源的内容与实际内容不匹配,这个时候我们就需要给修改的资源添加如下代码
@synchronized(self) {
// 被锁住的代码
}
通过这个代码就可以防止多个线程同时修改一个资源,保证了每次修改该资源只有一个线程。