iOS中主要有四种实现多线程操作的方案,pthread、NSthread、GCD和NSOperation。前两个用得很少,基本不用,iOS代码中主要靠后面两个。但是后面两个实际上最终都是被“翻译”成pthread的方法来实现与系统交互的。
1、pthread
pthread可以说是一个万能膏药,是一套通用的多线程接口,可以在Linux/Unix/Windows/iOS等操作系统中跨平台使用,它是基于C语言的,而且需要程序员手动来管理线程的开启和销毁。
使用pthread需要引入头文件
#import <pthread.h>
来看一段pthread的使用代码:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
pthread_t thread;
// 创建一个线程并自动执行
int i = 10;
pthread_create(&thread, NULL, start, &i);
pthread_join(thread, NULL);
}
void *start(void *data){
NSLog(@"%@", [NSThread currentThread]);
int i = *(int *)data;
printf("%d", i);
return NULL;
}
通过pthread_create方法来创建一个线程,这个方法需要传四个参数。
- 第一个参数是创建线程的地址
- 第二个参数是配置线程的属性,如果设置为NULL,则为默认的属性配置。关于属性配置可以查看pThread_attr_t相关资料。
- 第三个参数是线程运行的函数的指针。
- 第四个参数是运行函数的参数
有些童鞋可能会在第三个参数中传参,我这边试了单个参数也是可以的。但是这样可能会导致不可知的问题,严格意义来说第三个参数只能传函数的地址。
如果需要传递函数参数,仅传一个参数时,可以按照如图代码所示直接在第四个参数中配置。但是如果需要传递多个参数,那么需要把多个参数放在一个结构体中,然后把结构体作为运行函数的参数来传递。
注:图片转自欧阳大哥:iOS线程生命周期的监控
pthread是iOS中其他多线程实现方式的基础,其他几种多线程实现方式都最终会转化成pthread的方法,只有pthread的方法才能与系统底层进行交互。更深入的交互过程还有待后续继续挖掘之后再来分享,也可参考上面大神的分解。
2、NSThread
NSThread是苹果官方提供的第一种多线程操作接口,基于Objective-C,方便iOS开发者使用,但是由于这个对象仍然需要手动来管理线程的生命周期,在开发中用得并不是很多。
2.1 创建线程
创建线程可以通过三种方式:
- 通过init来添加一个运行方法,这种方式创建的线程需要使用start来手动的启动线程。@selector中的方法就是需要执行的代码。
// 创建线程(子线程)
NSThread *myThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadCallFunc) object:nil];
[myThread start];
- 下面的两种方式是不需要手动开启线程,创建之后会自动执行start方法。其中iOS10.0以后新增的线程创建方式是通过Block来传递需要执行的代码。
// Block方式,iOS10以后可用
if (@available(iOS 10.0, *)) {
[NSThread detachNewThreadWithBlock:^{
}];
} else {
// Fallback on earlier versions
// 创建并同时自动启动的方法(SEL)
[NSThread detachNewThreadSelector:@selector(threadCallFunc) toTarget:self withObject:nil];
}
2.2 NSThread的基本属性
要查看NSThread的属性和定义的方法,可以点开NSThread查看.h文件的声明。
主要有以下一些方法的使用,不过由于NSThread对于开发者也并不是那么友好,开发过程中很多时候我们基本用的比较多的只有[NSThread currentThread]来获取当前线程这个方法了。
// 线程调用返回的栈地址的记录,实际返回的是虚拟地址,就是当前线程所在函数所使用的虚拟地址的的数组
NSArray *array = [NSThread callStackReturnAddresses];
NSArray *array1 = [NSThread callStackSymbols];
NSLog(@"栈地址数组--%@", array);
NSLog(@"栈地址符号数组--%@", array1);
/**
* NSThread中其他方法
*/
// 取消线程
[myThread cancel];
// 设置和获取线程方法,自己创建的线程一般打印出来name字段是没有的,可以通过这个方法设置线程的名字
[myThread setName:@"jc-thread"];
NSString *threadName = [myThread name];
NSLog(@"当前threadName:%@", threadName);
// 获取当前线程
[NSThread currentThread];
// 获取主线程
[NSThread mainThread];
// 使当前线程休眠一段时间
[NSThread sleepForTimeInterval:3.0f];
// 使当前线程一直休眠直到某个时间
[NSThread sleepUntilDate:[NSDate date]];
// NSTread BOOL
// 是否正在执行
BOOL isExcute = myThread.isExecuting;
// 是否已经取消
BOOL isCanceled = myThread.isCancelled;
// 是否已经结束
BOOL isFinished = myThread.isFinished;
NSLog(@"%d, %d, %d", isExcute, isCanceled, isFinished);
提一下callStackReturnAddress和callStackSymbols这两个方法。我们平时在调试bug的时候,遇到崩溃的问题时可能会被断到某个地址,或者包括我们查看友盟的一些崩溃日志时也可能会留意到一些看不懂的地址。这些地址和符号就是方法名通过特殊处理后的展示,分析问题的时候通过符号表,可以反过来推倒报错的方法名从而更快的找到问题。所以这两个方法可以与NSLog联合使用来跟踪线程的函数使用情况。
2.3 线程的优先级
在编写程序的过程中,不同的任务会有不同的优先级,为了满足这个需求,就存在着线程的优先级,通过优先级设置来告诉系统哪些任务需要立即执行,哪些任务可以缓一缓。
NSThread给线程订立了五个优先级,可以通过如下属性来设置子线程的优先级
@property NSQualityOfService qualityOfService API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0)); // read-only after the thread is started
可以点击NSQualityOfService查看优先级结构体详情,他们的优先级顺序是NSQualityOfServiceUserInteractive> NSQualityOfServiceUserInitiated> NSQualityOfServiceDefault> NSQualityOfServiceUtility> NSQualityOfServiceBackground
未设置优先级时,优先级默认为NSQualityOfServiceDefault
typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21, // 最高优先级,主要用于UI交互的操作,比如处理点击事件,绘制图像到屏幕上等
NSQualityOfServiceUserInitiated = 0x19, // 次高优先级,主要用于需要立即执行的操作
NSQualityOfServiceUtility = 0x11, // 普通优先级,主要用于不需要立即执行的操作
NSQualityOfServiceBackground = 0x09, // 后台优先级,用于完全不紧急的操作
NSQualityOfServiceDefault = -1 // 默认优先级,当没有设置优先级的时候,线程默认优先级
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
2.4 线程间通信
- 指定当前线程执行操作,是继承自NSObject的方法。
// 指定当前线程执行操作
[self performSelector:@selector(nsThreadComunications)];
[self performSelector:@selector(nsThreadComunications) withObject:nil];
[self performSelector:@selector(nsThreadComunications) withObject:nil afterDelay:1.0f];
- 指定在主线程执行操作
// 指定在主线程中执行操作
[self performSelectorOnMainThread:@selector(nsThreadComunications) withObject:nil waitUntilDone:NO];
// 结合runloop,唤醒主线程中的runloop
[self performSelector:@selector(nsThreadComunications) withObject:nil afterDelay:1.0f inModes:@[NSRunLoopCommonModes]];
- 当前在主线程时,指定在其他线程执行操作
// 指定在子线程执行操作
[self performSelector:@selector(nsThreadComunications) onThread:myThread withObject:nil waitUntilDone:NO];
[self performSelectorInBackground:@selector(nsThreadComunications) withObject:nil];