多线程

线程的注意点

1.不要同时开太多的线程(1~3条线程即可,不要超过5条)
2.线程概念

  • 1)主线程 : UI线程,显示、刷新UI界面,处理UI控件的事件
  • 2)子线程 : 后台线程,异步线程

3.不要把耗时的操作放在主线程,要放在子线程中执行

iOS的三种多线程技术

NSThread

  • 使用NSThread对象建立一个线程非常方便
  • 但是!要使用NSThread管理多个线程非常困难,不推荐使用
  • 技巧!使用[NSThread currentThread]跟踪任务所在线程,适用于这三种技术

NSOperation/NSOperationQueue

  • 是使用GCD实现的一套Objective-C的API
  • 是面向对象的线程技术
  • 提供了一些在GCD中不容易实现的特性,如:限制最大并发数量、操作之间的依赖关系

GCD —— Grand Central Dispatch

  • 是基于C语言的底层API
  • 用Block定义任务,使用起来非常灵活便捷
  • 提供了更多的控制能力以及操作队列中所不能使用的底层函数

NSThread

1.创建和启动线程的3种方式

1)先创建,后启动

1
2
3
4
// 创建
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(download:) object:nil];
// 启动
[thread start];

2)创建完自动启动

1
[NSThread detachNewThreadSelector:@selector(download:) toTarget:self withObject:nil];

3)隐式创建(自动启动)

1
[self performSelectorInBackground:@selector(download:) withObject:nil];

2.常见方法

1)获得当前线程

1
+ (NSThread *)currentThread;

2)获得主线程

1
+ (NSThread *)mainThread;

3)睡眠(暂停)线程

1
2
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

4)设置线程的名字

1
2
- (void)setName:(NSString *)n;
- (NSString *)name;

线程同步

1.实质:为了防止多个线程抢夺同一个资源造成的数据安全问题

2.实现:给代码加一个互斥锁(同步锁)

1
2
3
@synchronized(self) {
// 被锁住的代码
}

GCD(Grand Central Dispatch)

1.队列和任务

1)任务 :需要执行什么操作

用block来封装任务

2)队列 :存放任务

全局的并发队列 : 可以让任务并发执行

1
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

自己创建的串行队列 : 让任务一个接着一个执行

1
dispatch_queue_t queue = dispatch_queue_create("cn.heima.queue", NULL);

主队列 : 让任务在主线程执行

1
dispatch_queue_t queue = dispatch_get_main_queue();

2.执行任务的函数

1)同步执行 : 不具备开启新线程的能力
dispatch_sync...
2)异步执行 : 具备开启新线程的能力
dispatch_async...

3.常见的组合

1)dispatch_async + 全局并发队列
2)dispatch_async + 自己创建的串行队列

4.线程间的通信

1
2
3
4
5
6
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行耗时的异步操作...
dispatch_async(dispatch_get_main_queue(), ^{
// 回到主线程,执行UI刷新操作
});
});

5.GCD的所有API都在libdispatch.dylib,Xcode会自动导入这个库

主头文件 : #import <dispatch/dispatch.h>

6.延迟执行

1) perform....

1
2
// 3秒后自动回到当前线程调用self的download:方法,并且传递参数:@"http://555.jpg"
[self performSelector:@selector(download:) withObject:@"http://555.jpg" afterDelay:3];

2) dispatch_after...

1
2
3
4
5
6
// 任务放到哪个队列中执行
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秒后需要执行的任务
});

7.一次性代码

1
2
3
4
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 这里面的代码,在程序运行过程中,永远只会执行1次
});

单例模式(懒汉式)

1.ARC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@interface HMDataTool : NSObject
+ (instancetype)sharedDataTool;
@end
@implementation HMDataTool
// 用来保存唯一的单例对象
static id _instace;
+ (id)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [super allocWithZone:zone];
});
return _instace;
}
+ (instancetype)sharedDataTool
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [[self alloc] init];
});
return _instace;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instace;
}
@end

2.MRC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
@interface HMDataTool : NSObject
+ (instancetype)sharedDataTool;
@end
@implementation HMDataTool
// 用来保存唯一的单例对象
static id _instace;
+ (id)allocWithZone:(struct _NSZone *)zone
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [super allocWithZone:zone];
});
return _instace;
}
+ (instancetype)sharedDataTool
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instace = [[self alloc] init];
});
return _instace;
}
- (id)copyWithZone:(NSZone *)zone
{
return _instace;
}
- (oneway void)release {
}
- (id)retain {
return self;
}
- (NSUInteger)retainCount {
return 1;
}
- (id)autorelease {
return self;
}
@end

NSOperation和NSOperationQueue

1.队列的类型

1) 主队列

[NSOperationQueue mainQueue]
添加到”主队列”中的操作,都会放到主线程中执行

2) 非主队列

[[NSOperationQueue alloc] init]
添加到”非主队列”中的操作,都会放到子线程中执行

2.队列添加任务

1
2
- (void)addOperation:(NSOperation *)op;
- (void)addOperationWithBlock:(void (^)(void))block;

3.常见用法

1) 设置最大并发数

1
2
- (NSInteger)maxConcurrentOperationCount;
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

2) 队列的其他操作

取消所有的操作
- (void)cancelAllOperations;

暂停所有的操作
[queue setSuspended:YES];

恢复所有的操作
[queue setSuspended:NO];

4.操作之间的依赖

NSOperation之间可以设置依赖来保证执行顺序
[operationB addDependency:operationA];

操作B依赖于操作A,等操作A执行完毕后,才会执行操作B。
注意:不能相互依赖,比如A依赖B,B依赖A;可以在不同queue的NSOperation之间创建依赖关系

5.线程之间的通信

1
2
3
4
5
6
7
8
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
// 1.执行一些比较耗时的操作
// 2.回到主线程
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//主线程操作
}];
}];

从其他线程回到主线程的方式

1.perform...

1
[self performSelectorOnMainThread:<#(SEL)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>];

2.GCD

1
2
3
dispatch_async(dispatch_get_main_queue(), ^{
//主线程操作
});

3.NSOperationQueue

1
2
3
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
//主线程操作
}];