1. pthread
pthread
是POSIX
线程的简称,是线程的POSIX
标准。pthread
基于C语言
,是一种可以跨平台使用的方法,但是由于其使用难度较大,并且生命周期需要程序员手动管理,因此很少或几乎不用
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
pthread_t threadId = NULL;
// c字符串
char *cString = "HelloCode";
int result = pthread_create(&threadId, NULL, pthreadTest, cString);
if (result == 0) {
NSLog(@"成功");
} else {
NSLog(@"失败");
}
}
void *pthreadTest(void *para){
// 接 C 语言的字符串
// NSLog(@"===> %@ %s", [NSThread currentThread], para);
// __bridge 将 C 语言的类型桥接到 OC 的类型
NSString *name = (__bridge NSString *)(para);
NSLog(@"===>%@ %@", [NSThread currentThread], name);
return NULL;
}
使用pthread_create
创建线程
参数:
pthread_t
:要创建线程的结构体指针,通常开发的时候,如果遇到 C 语言的结构体,类型后缀_t / Ref
结尾,同时不需要*
线程的属性,
nil
(空对象,OC
使用的) ,NULL
(空地址,C
使用的)线程要执行的函数地址
void *
:返回类型,表示指向任意对象的指针,和OC
中的id
类似(*)
:函数名(void *)
:参数类型,void *
传递给第三个参数(函数)的参数
返回值:int
类型0
:创建线程成功- 非
0
:创建线程失败的错误码,失败有多种可能
1.1 C
与OC
的桥接
__bridge
只做类型转换,但是不修改对象(内存)管理权__bridge_retained
(也可以使用CFBridgingRetain
)将Objective-C
的对象转换为Core Foundation
的对象,同时将对象(内存)的管理权交给我们,后续需要使用CFRelease
或者相关方法来释放对象__bridge_transfer
(也可以使用CFBridgingRelease
)将Core Foundation
的对象转换为Objective-C
的对象,同时将对象(内存)的管理权交给ARC
2. NSThread
NSthread
是苹果官方提供面向对象的线程操作技术,是对thread
的上层封装,比较偏向于底层。简单方便,可以直接操作线程对象,使用频率较少
创建线程的三种方式:
通过init初始化方式创建,开辟线程,启动线程
detach
分离,不需要启动,直接分离出新的线程执行隐式的多线程调用方法,没有
thread
,也没有start
。所属NSObject
的NSThreadPerformAdditions
分类
- (void)creatThreadMethod{
NSLog(@"%@", [NSThread currentThread]);
//方式一:
NSThread *t = [[NSThread alloc] initWithTarget:self.p selector:@selector(study:) object:@100];
t.name = @"学习线程";
[t start];
//方式二:
[NSThread detachNewThreadSelector:@selector(study:) toTarget:self.p withObject:@10000];
//方式三:
[self.p performSelectorInBackground:@selector(study:) withObject:@5000];
NSLog(@"%@", [NSThread currentThread]);
}
2.1 属性
name
:在应用程序中,收集错误日志,能够记录工作的线程。否则不好判断具体哪一个线程出的问题stackSize
:该值必须以字节为单位,且为4KB
的倍数isExecuting
:线程是否在执行isCancelled
:线程是否被取消isFinished
:是否完成isMainThread
:是否是主线程threadPriority
:线程的优先级,取值范围0.0-1.0
,默认优先级0.5
。1.0
表示最高优先级,优先级高CPU
调度的频率高qualityOfService
:服务质量,替代threadPriority
属性
- (void)testThreadProperty{
// 主线程 512K
NSLog(@"NSThread:%@,stackSize:%zdK,isMainThread:%d", [NSThread currentThread], [NSThread currentThread].stackSize / 1024, [NSThread currentThread].isMainThread);
NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(eat) object:nil];
t.name = @"吃饭线程";
t.stackSize = 1024*1024;
t.threadPriority = 1;
[t start];
}
-------------------------
//输出以下内容:
NSThread:<NSThread: 0x281014600>{number = 1, name = main},stackSize:512K,isMainThread:1
2.2 类方法
currentThread
:获取当前线程sleepForTimeInterval
:阻塞线程exit
:退出线程mainThread
:获取主线程
- (void)threadTest{
//当前线程
[NSThread currentThread];
// 如果返回值为1,表示主线程,否则是子线程
NSLog(@"%@", [NSThread currentThread]);
//阻塞休眠
[NSThread sleepForTimeInterval:2];//休眠时长,单位秒
[NSThread sleepUntilDate:[NSDate date]];//休眠到指定时间
//退出线程
[NSThread exit];
//判断当前线程是否为主线程
[NSThread isMainThread];
//判断当前线程是否是多线程
[NSThread isMultiThreaded];
//主线程的对象
NSThread *mainThread = [NSThread mainThread];
NSLog(@"%@", mainThread);
}
2.3 exit
& cancel
exit
:一旦强行终止线程,后续的所有代码都不会执行cancel
:取消当前线程,但是不能取消正在执行的线程
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self testThreadStatus];
}
- (void)testThreadStatus{
NSLog(@"%d %d %d", self.t.isExecuting, self.t.isFinished, self.t.isCancelled);
if ( self.t == nil || self.t.isCancelled || self.t.isFinished ) {
self.t = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
self.t.name = @"跑步线程";
[self.t start];
}else{
NSLog(@"%@ 正在执行",self.t.name);
[self.t cancel];
self.t = nil;
}
}
- (void)run{
NSLog(@"开始");
// 下面的代码的作用是判断线程状态,可能因为下面的延时,阻塞会带来当前线程的一些影响
[NSThread sleepForTimeInterval:3];
// 判断线程是否被取消
if ([NSThread currentThread].isCancelled) {
NSLog(@"%@被取消",self.t.name);
return;
}
for (NSInteger i = 0; i < 10; i++) {
// 判断线程是否被取消
if ([NSThread currentThread].isCancelled) {
NSLog(@"%@被取消",self.t.name);
return;
}
if (i == 3) {
// 睡指定的时长(秒)
[NSThread sleepForTimeInterval:1];
}
NSLog(@"%@ %zd", [NSThread currentThread], i);
// 内部取消线程
// 强制退出 - 当某一个条件满足,不希望线程继续工作,直接杀死线程,退出
if (i == 8) {
// 强制退出当前所在线程!后续的所有代码都不会执行
[NSThread exit];
}
}
NSLog(@"完成");
}
3. GCD
3.1 dispatch_after
dispatch_after
:表示在某队列中的block
延迟执行
- 应用场景:在主队列上延迟执行一项任务,如
viewDidload
之后延迟1s
,提示一个alertview
(是延迟加入到队列,而不是延迟执行)
- (void)testGCD{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2s后输出");
});
}
3.2 dispatch_once
dispatch_once
:保证在App
运行期间,block
中的代码只执行一次
- 应用场景:单例模式、Method Swizzling
- (void)testGCD{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
...
});
}
3.3 dispatch_apply
dispatch_apply
:将指定的Block
追加到指定的队列中重复执行,并等到全部的处理执行结束,相当于线程安全的循环
应用场景:用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性
添加到串行队列中:按序执行
添加到主队列中:死锁
添加到并发队列中:乱序执行
添加到全局队列中:乱序执行
- (void)testGCD{
dispatch_queue_t queue = dispatch_queue_create("testGCD", DISPATCH_QUEUE_SERIAL);
NSLog(@"dispatch_apply前");
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"dispatch_apply 的线程 %zu - %@", index, [NSThread currentThread]);
});
NSLog(@"dispatch_apply后");
}
- 参数1:重复次数
- 参数2:添加的队列
- 参数3:任务
Block
3.4 dispatch_group_t
dispatch_group_t
:调度组将任务分组执行,能监听任务组完成,并设置等待时间
- 应用场景:多个接口请求之后刷新页面
- (void)testGCD{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
NSLog(@"接口1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"接口2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新");
});
}
dispatch_group_create
:创建一个调度任务组dispatch_group_async
:把一个任务异步提交到任务组里
3.4.1 dispatch_group_enter
& dispatch_group_leave
dispatch_group_enter
和dispatch_group_leave
成对出现,使进出组的逻辑更加清晰
- 这种方式用在不使用
dispatch_group_async
来提交任务,且必须配合使用
- (void)testGCD{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"接口1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"接口2");
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新");
});
}
3.4.2 dispatch_group_wait
dispatch_group_wait
:设置等待时间
- 在等待时间结束后,如果还没有执行完任务组,则返回。返回
0
代表执行成功,非0
则执行失败
- (void)testGCD{
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"接口1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"接口2");
dispatch_group_leave(group);
});
// long timeout = dispatch_group_wait(group, DISPATCH_TIME_NOW);
// long timeout = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 30 * NSEC_PER_SEC));
if (timeout == 0) {
NSLog(@"完成");
}else{
NSLog(@"超时");
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新");
});
}
参数:
group
:需要等待的调度组timeout
:等待的超时时间(即等多久)DISPATCH_TIME_NOW
:不等待,直接判定调度组是否执行完毕DISPATCH_TIME_FOREVER
:阻塞当前调度组,直到调度组执行完毕
返回值:
long
类型0
:在指定时间内调度组完成了任务非
0
:在指定时间内调度组没有按时完成任务
3.5 dispatch_barrier_async
dispatch_barrier_async
:先执行栅栏前任务,再执行栅栏任务,最后执行栅栏后任务
- 应用场景:无需等待栅栏执行完,会继续往下走
- (void)testGCD{
dispatch_queue_t queue = dispatch_queue_create("testGCD", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"逻辑1 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"接口1 - %@", [NSThread currentThread]);
});
NSLog(@"逻辑2 - %@", [NSThread currentThread]);
dispatch_barrier_async(queue, ^{
NSLog(@"栅栏 - %@", [NSThread currentThread]);
});
NSLog(@"逻辑3 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"接口2 - %@", [NSThread currentThread]);
});
NSLog(@"逻辑4 - %@", [NSThread currentThread]);
}
-------------------------
//输出以下内容:
逻辑1 - <NSThread: 0x280c2c940>{number = 1, name = main}
逻辑2 - <NSThread: 0x280c2c940>{number = 1, name = main}
逻辑3 - <NSThread: 0x280c2c940>{number = 1, name = main}
逻辑4 - <NSThread: 0x280c2c940>{number = 1, name = main}
接口1 - <NSThread: 0x280c64080>{number = 8, name = (null)}
栅栏 - <NSThread: 0x280c64080>{number = 8, name = (null)}
接口2 - <NSThread: 0x280c64080>{number = 8, name = (null)}
3.6 dispatch_barrier_sync
dispatch_barrier_sync
:和dispatch_barrier_async
作用相同,但会堵塞线程,影响后面的任务执行
- 需要等待栅栏执行完,才会执行栅栏后面的任务,慎用
- (void)testGCD{
dispatch_queue_t queue = dispatch_queue_create("testGCD", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"逻辑1 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"接口1 - %@", [NSThread currentThread]);
});
NSLog(@"逻辑2 - %@", [NSThread currentThread]);
dispatch_barrier_sync(queue, ^{
NSLog(@"栅栏 - %@", [NSThread currentThread]);
});
NSLog(@"逻辑3 - %@", [NSThread currentThread]);
dispatch_async(queue, ^{
sleep(2);
NSLog(@"接口2 - %@", [NSThread currentThread]);
});
NSLog(@"逻辑4 - %@", [NSThread currentThread]);
}
-------------------------
//输出以下内容:
逻辑1 - <NSThread: 0x281078980>{number = 1, name = main}
逻辑2 - <NSThread: 0x281078980>{number = 1, name = main}
接口1 - <NSThread: 0x281038380>{number = 5, name = (null)}
栅栏 - <NSThread: 0x281078980>{number = 1, name = main}
逻辑3 - <NSThread: 0x281078980>{number = 1, name = main}
逻辑4 - <NSThread: 0x281078980>{number = 1, name = main}
接口2 - <NSThread: 0x281057500>{number = 9, name = (null)}
3.7 dispatch_semaphore_t
dispatch_semaphore_t
:信号量
- 应用场景:用作同步锁,用于控制
GCD
最大并发数
- (void)testGCD{
dispatch_queue_t queue = dispatch_queue_create("testGCD", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
sleep(1);
NSLog(@"当前 - %d, 线程 - %@", i, [NSThread currentThread]);
});
}
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
for (int i = 0; i < 10; i++) {
dispatch_async(queue, ^{
sleep(1);
NSLog(@"当前 - %d, 线程 - %@", i, [NSThread currentThread]);
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
NSLog(@"完成");
}
dispatch_semaphore_create
:创建信号量dispatch_semaphore_wait
:等待信号量,信号量减1
。当信号量< 0
时会阻塞当前线程,根据传入的等待时间决定接下来的操作。如果永久等待将等到信号signal
才执行下去dispatch_semaphore_signal
:释放信号量,信号量加1
。当信号量>= 0
会执行wait
之后的代码
3.8 dispatch_source_t
在iOS
中一般使用NSTimer
来处理定时逻辑,但NSTimer
是依赖Runloop
的,而Runloop
可以运行在不同的模式下。如果NSTimer
添加在一种模式下,当Runloop
运行在其他模式下的时候,定时器会处于挂起状态。如果Runloop
在阻塞状态,NSTimer
触发时间就会推迟到下一个Runloop
周期。因此NSTimer
在计时上会有误差,并不是特别精确,而GCD
定时器不依赖Runloop
,计时精度要高很多
dispatch_source_t
:用于计时操作,它创建的timer
不依赖于RunLoop
,计时精准度比NSTimer
高
- 应用场景:
GCDTimer
static dispatch_source_t timer;
- (void)testGCD{
//1.创建队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//2.创建timer
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//3.设置timer首次执行时间,间隔,精确度
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
//4.设置timer事件回调
dispatch_source_set_event_handler(timer, ^{
NSLog(@"计时");
});
//5.默认是挂起状态,需要手动激活
dispatch_resume(timer);
}
dispatch_source
:一种基本的数据类型,可以用来监听一些底层的系统事件
Timer Dispatch Source
:定时器事件源,用来生成周期性的通知或回调Signal Dispatch Source
:监听信号事件源,当有UNIX
信号发生时会通知Descriptor Dispatch Source
:监听文件或socket
事件源,当文件或socket
数据发生变化时会通知Process Dispatch Source
:监听进程事件源,与进程相关的事件通知Mach port Dispatch Source
:监听Mach
端口事件源Custom Dispatch Source
:监听自定义事件源
常用API
:
dispatch_source_create
: 创建事件源dispatch_source_set_event_handler
: 设置数据源回调dispatch_source_merge_data
: 设置事件源数据dispatch_source_get_data
: 获取事件源数据dispatch_resume
: 继续dispatch_suspend
: 挂起dispatch_cancle
: 取消
4. NSOperation
NSOperation
是基于GCD之上的更高一层封装,NSOperation
需要配合NSOperationQueue
来实现多线程
实现多线程的步骤:
创建任务:先将需要执行的操作封装到
NSOperation
对象中创建队列:创建
NSOperationQueue
将任务加入到队列中:将
NSOperation
对象添加到NSOperationQueue
中
- (void)testNSOperation{
//处理事务
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"testNSOperation"];
//创建队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//操作加入队列
[queue addOperation:op];
}
- (void)handleInvocation:(id)operation{
NSLog(@"%@ - %@", operation, [NSThread currentThread]);
}
NSOperation
是抽象类,开发中实际用到的是它的子类:
NSInvocationOperation
NSBlockOperation
自定义子类
4.1 NSInvocationOperation
- (void)testNSOperation{
NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"testNSOperation"];
[invocationOperation start];
}
4.2 NSBlockOperation
- (void)testNSOperation{
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"main task:%@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"task1:%@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"task2:%@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"task3:%@", [NSThread currentThread]);
}];
[blockOperation start];
}
通过
addExecutionBlock
方法,可以让NSBlockOperation
实现多线程NSBlockOperation
创建block
中的任务是在主线程执行,而使用addExecutionBlock
加入的任务是在子线程执行
4.3 自定义子类
@interface LGOperation : NSOperation
@end
@implementation LGOperation
- (void)main{
for (int i = 0; i < 3; i++) {
NSLog(@"NSOperation的子类:%@",[NSThread currentThread]);
}
}
@end
- (void)testNSOperation{
LGOperation *operation = [[LGOperation alloc] init];
[operation start];
}
- 使用
NSOperation
的子类,重写它的main
方法
4.4 NSOperationQueue
NSOperationQueue
包含两种队列:
主队列:主队列上的任务是在主线程执行的
其他队列:包含串行和并发队列,加入到非主队列中的任务,默认就是并发,开启多线程
- (void)testNSOperation{
// 初始化添加事务
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务1:%@",[NSThread currentThread]);
}];
// 添加事务
[bo addExecutionBlock:^{
NSLog(@"任务2:%@",[NSThread currentThread]);
}];
// 回调监听
bo.completionBlock = ^{
NSLog(@"完成");
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo];
NSLog(@"事务添加进了NSOperationQueue");
}
NSInvocationOperation
与NSBlockOperation
的区别:NSInvocationOperation
类似于target
形式NSBlockOperation
类似于block
形式。函数式编程,业务逻辑代码可读性更高
4.4.1 执行顺序
- (void)testNSOperation{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{
NSLog(@"%@---%d", [NSThread currentThread], i);
}];
}
}
4.4.2 优先级
- (void)testNSOperation{
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
NSLog(@"第一个操作 %d --- %@", i, [NSThread currentThread]);
}
}];
// 设置最高优先级
bo1.qualityOfService = NSQualityOfServiceUserInteractive;
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
NSLog(@"第二个操作 %d --- %@", i, [NSThread currentThread]);
}
}];
// 设置最低优先级
bo2.qualityOfService = NSQualityOfServiceBackground;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo1];
[queue addOperation:bo2];
}
4.4.3 并发数
- (void)testNSOperation{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"Felix";
queue.maxConcurrentOperationCount = 2;
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{
[NSThread sleepForTimeInterval:2];
NSLog(@"%d-%@",i,[NSThread currentThread]);
}];
}
}
- 在
GCD
中只能使用信号量来设置并发数,而NSOperation
使用maxConcurrentOperationCount
就能设置并发数
4.4.4 添加依赖
- (void)testNSOperation{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"获取用户id");
}];
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"通过用户id,获取用户对象");
}];
NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"通过用户对象,获取文章列表");
}];
[bo2 addDependency:bo1];
[bo3 addDependency:bo2];
[queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
NSLog(@"完成");
}
4.4.5 线程间通讯
- (void)testNSOperation{
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"test";
[queue addOperationWithBlock:^{
NSLog(@"接口请求:%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"刷新:%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
}];
}];
}