iOS中的多线程解决方案:
- Pthreads
- NSThread
- GCD
- NSOperation & NSOperationQueue
Pthreads
基于C语言,在类Unix操作系统(Unix、Linux、Mac OS X等)中,都可以使用。
(1)实现必须导入头文件
#import <pthread.h>
(2)然后创建线程,并执行任务
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
pthread_t thread;
//创建一个线程并自动执行
pthread_create(&thread, NULL, start, NULL);
}
void *start(void *data) {
NSLog(@"%@", [NSThread currentThread]);
return NULL;
}
由于是C语言函数,所以需要自己管理线程的生命周期(切换、销毁)
NSThread
Apple封装之后,面向对象,但是仍然需要手动管理生命周期。但可以获得当前线程的各种属性,比[NSThread currentThread]
,用于调试很方便。
- 创建线程,然后开启线程
// 创建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
// 启动
[thread start];
- 创建并自动开启线程
[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];
- performSelector形式创建线程
[self performSelectorInBackground:@selector(run:) withObject:nil];
其他作用
//取消线程
- (void)cancel;
//启动线程
- (void)start;
//判断某个线程的状态的属性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;
//设置和获取线程名字
-(void)setName:(NSString *)n;
-(NSString *)name;
//获取当前线程信息
+ (NSThread *)currentThread;
//获取主线程信息
+ (NSThread *)mainThread;
//使当前线程暂停一段时间,或者暂停到某个时刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;
GCD
使用了Block,系统自动管理生命周期。
- 任务:每一个Block就是一个任务(你想要做什么)。
- 队列:用于存放任务。
任务有两种执行方式:
同步执行:会阻塞当前线程,必须等Block完,才能继续往下执行。
异步执行:不会阻塞当前线程,程序不必等待,会直接继续执行。
队列也有两种:
串行队列:按顺序执行,即先进入的先执行,后进入的后执行。
并行队列:同时执行任务,没有执行顺序。
注意:异步操作才会开辟新线程。串行队列按顺序执行任务,并行队列同时执行多个任务。
创建队列
- 主队列:不会开辟新线程,串行执行任务。
dispatch_queue_t queue = ispatch_get_main_queue();
- 全局队列:并行队列。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- 创建串行和并行队列。
//串行队列
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", NULL);
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_SERIAL);
//并行队列
dispatch_queue_t queue = dispatch_queue_create("tk.bourne.testQueue", DISPATCH_QUEUE_CONCURRENT);
创建任务
- 同步
dispatch_sync(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
- 异步
dispatch_async(<#queue#>, ^{
//code here
NSLog(@"%@", [NSThread currentThread]);
});
总结效果:
注意:
异步添加串行队列,只会开启一个新线程;
异步和同步决定是否开启新线程,并行和串行决定任务的执行顺序;
队列组
队列组可以将很多队列添加到一个组里,这样做的好处是,当这个组里所有的任务都执行完了,队列组会通过一个方法通知我们
//1.创建队列组
dispatch_group_t group = dispatch_group_create();
//2.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//3.多次使用队列组的方法执行任务, 只有异步方法
//3.1.执行3次循环
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 3; i++) {
NSLog(@"group-01 - %@", [NSThread currentThread]);
}
});
//3.2.主队列执行8次循环
dispatch_group_async(group, dispatch_get_main_queue(), ^{
for (NSInteger i = 0; i < 8; i++) {
NSLog(@"group-02 - %@", [NSThread currentThread]);
}
});
//3.3.执行5次循环
dispatch_group_async(group, queue, ^{
for (NSInteger i = 0; i < 5; i++) {
NSLog(@"group-03 - %@", [NSThread currentThread]);
}
});
//4.都完成后会自动通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"完成 - %@", [NSThread currentThread]);
});
NSOperation和NSOperationQueue
NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。 大家可以看到NSOperation
和 NSOperationQueue
分别对应 GCD 的 任务 和 队列 。
1.将要执行的任务封装到一个 NSOperation 对象中。
2.将此任务添加到一个 NSOperationQueue 对象中。
添加任务
NSOperation
只是一个抽象类,所以不能封装任务。但它有 2 个子类用于封装任务。分别是:NSInvocationOperation
和 NSBlockOperation
。创建一个 Operation 后,需要调用 start
方法来启动任务,它会默认在当前队列同步执行。当然你也可以在中途取消一个任务,只需要调用其 cancel
方法即可。
- NSInvocationOperation : 需要传入一个方法名。
//1.创建NSInvocationOperation对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//2.开始执行
[operation start];
- NSBlockOperation
//1.创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//2.开始任务
[operation start];
之前说过这样的任务,默认会在当前线程执行。但是 NSBlockOperation
还有一个方法:addExecutionBlock:
,通过这个方法可以给 Operation 添加多个执行 Block。这样 Operation 中的任务会并发执行,它会在主线程和其它的多个线程 执行这些任务。
//1.创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//添加多个Block
for (NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
//2.开始任务
[operation start];
创建队列
看过上面的内容就知道,我们可以调用一个 NSOperation
对象的 start()
方法来启动这个任务,但是这样做他们默认是 同步执行 的。就算是 addExecutionBlock
方法,也会在当前线程和其他线程中执行,也就是说还是会占用当前线程。这时就要用到队列 NSOperationQueue
了。而且,按类型来说的话一共有两种类型:主队列、其他队列。只要添加到队列,会自动调用任务的 start() 方法。
- 主队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
- 其他队列
//1.创建一个其他队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//2.创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@", [NSThread currentThread]);
}];
//3.添加多个Block
for (NSInteger i = 0; i < 5; i++) {
[operation addExecutionBlock:^{
NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
}];
}
//4.队列添加任务
[queue addOperation:operation];
NSOperationQueue
有一个参数maxConcurrentOperationCount
(最大并发数),用来设置最多可以让多少个任务同时执行。当你把它设置为 1 的时候,就是串行。添加任务的方法:
- (void)addOperationWithBlock:(void (^)(void))block;
添加依赖
//1.任务一:下载图片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"下载图片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//2.任务二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"打水印 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//3.任务三:上传图片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"上传图片 - %@", [NSThread currentThread]);
[NSThread sleepForTimeInterval:1.0];
}];
//4.设置依赖
[operation2 addDependency:operation1]; //任务二依赖任务一
[operation3 addDependency:operation2]; //任务三依赖任务二
//5.创建队列并加入任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];
注意:
(1)不能添加相互依赖,会死锁,比如 A依赖B,B依赖A。
(2)可以使用 removeDependency 来解除依赖关系。
(3)可以在不同的队列之间依赖,反正就是这个依赖是添加到任务身上的,和队列没关系。
其他方法
- NSOperation
BOOL executing; //判断任务是否正在执行
BOOL finished; //判断任务是否完成
void (^completionBlock)(void); //用来设置完成后需要执行的操作
- (void)cancel; //取消任务
- (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕
- NSOperationQueue
NSUInteger operationCount; //获取队列的任务数
- (void)cancelAllOperations; //取消队列中所有的任务
- (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕
[queue setSuspended:YES]; // 暂停queue
[queue setSuspended:NO]; // 继续queue
其他
线程同步
- 同步锁,即资源竞争:多条线程同时访问同一资源,造成的数据安全问题,通过加锁保证每次只有一个线程访问资源。
@synchronized(self) {
//需要执行的代码块
}
- 同步执行:把多个线程都要执行此段代码添加到同一个串行队列,这样就实现了线程同步的概念。
//GCD
//需要一个全局变量queue,要让所有线程的这个操作都加到一个queue中
dispatch_sync(queue, ^{
NSInteger ticket = lastTicket;
[NSThread sleepForTimeInterval:0.1];
NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
ticket -= 1;
lastTicket = ticket;
});
//NSOperation & NSOperationQueue
//重点:1. 全局的 NSOperationQueue, 所有的操作添加到同一个queue中
// 2. 设置 queue 的 maxConcurrentOperationCount 为 1
// 3. 如果后续操作需要Block中的结果,就需要调用每个操作的waitUntilFinished,阻塞当前线程,一直等到当前操作完成,才允许执行后面的。waitUntilFinished 要在添加到队列之后!
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
NSInteger ticket = lastTicket;
[NSThread sleepForTimeInterval:1];
NSLog(@"%ld - %@",ticket, [NSThread currentThread]);
ticket -= 1;
lastTicket = ticket;
}];
[queue addOperation:operation];
[operation waitUntilFinished];
//后续要做的事
延迟执行
- perform
// 3秒后自动调用self的run:方法,并且传递参数:@"abc"
[self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];
- GCD
// 创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 设置延时,单位秒
double delay = 3;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), queue, ^{
// 3秒后需要执行的任务
});
- NSTimer
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc" repeats:NO];
单例
@interface Tool : NSObject <NSCopying>
+ (instancetype)sharedTool;
@end
@implementation Tool
static id _instance;
+ (instancetype)sharedTool {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[Tool alloc] init];
});
return _instance;
}
@end
从其他线程返回主线程
- NSThread
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
- GCD
dispatch_async(dispatch_get_main_queue(), ^{
});
- NSOperationQueue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
}];