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{// 主线程 512KNSLog(@"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.创建timertimer = 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是抽象类,开发中实际用到的是它的子类:
NSInvocationOperationNSBlockOperation自定义子类
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]);}];}];}
