官方文档: https://developer.apple.com/reference/foundation/nsthread?language=objc
内容:线程的创建、启动、停止、状态,线程环境、属性、优先级,线程通信、线程常驻。
如果需要和线程直接打交道,需要创建常驻线程,可以使用 NSThread。创建线程子类需要重写 main 函数,不需要调用 super。
1、创建
// 指定初始化函数。
- (instancetype)init;
// selector:不能有返回值,最多只有一个参数。
// 注意:target 和 argument 会被强引用,只有线程 exits 后才会释放。
// 线程的 main 函数会自动调用 target 的 selector。
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument;
// iOS 10.0
- (instancetype)initWithBlock:(void (^)(void))block;
2、启动线程
// 创建并启动线程,selector 作为线程的入口点。
// target 和 argument 会被强引用,只有线程 exits 后才会释放。
// target 执行完 selector,线程就会退出。
// 线程的 main 函数会自动调用 target 的 selector。
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;
// iOS 10.0
+ (void)detachNewThreadWithBlock:(void (^)(void))block;
// 启动线程。异步调用线程的 main 函数。
// 如果线程在创建的时候,设置了 target 和 selector,main 函数会自动调用。
- (void)start;
// 线程的入口。不要直接调用,调用 start 来启动线程。
// 如果线程在创建的时候,设置了 target 和 selector,main 函数会自动调用。
// 子类重写,定义线程要完成的任务,不需要调用 super。
- (void)main;
3、停止线程
// 结束当前线程,会先发送 NSThreadWillExitNotification 通知。
// 注意:调用前记得先释放线程申请的资源。
+ (void)exit;
// 标记为取消,不会立即结束线程。
// 支持取消的线程,会周期检查 cancelled 属性,来判断是否需要 exit。
- (void)cancel;
// 阻塞当前线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;
4、线程状态
// 是否正在运行
@property(readonly, getter=isExecuting) BOOL executing;
// 是否运行完成
@property(readonly, getter=isFinished) BOOL finished;
// 是否取消了。
// 支持取消的线程,会周期检查 cancelled 属性,来判断是否需要 exit。
@property(readonly, getter=isCancelled) BOOL cancelled;
5、线程环境
// 是否主线程
@property(class, readonly) BOOL isMainThread;
// 是否多线程。
// If you detached a thread in your application using a
// non-Cocoa API, such as the POSIX or Multiprocessing Services
// APIs, this method could still return NO.
+ (BOOL)isMultiThreaded;
// 获取主线程
@property(class, readonly, strong) NSThread *mainThread;
// 获取当前线程
@property(class, readonly, strong) NSThread *currentThread;
// 调用栈地址
@property(class, readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses;
// 调用栈,崩溃时打印的东西
@property(class, readonly, copy) NSArray<NSString *> *callStackSymbols;
6、线程属性
// 用于存放用户数据,不影响线程操作。
// 比如存放线程的默认 NSConnection 或 NSAssertionHandler 对象。
@property(readonly, retain) NSMutableDictionary *threadDictionary;
// 线程名字
@property(copy) NSString *name;
// stack 的大小,以 bytes 为单位,必须是 4KB 的倍数。
// 在调用 start 之前设置,否则无效。
@property NSUInteger stackSize;
7、线程优先级
// 竞争资源的优先级
@property NSQualityOfService qualityOfService;
// 优先级定义
typedef NS_ENUM(NSInteger, NSQualityOfService) {
NSQualityOfServiceUserInteractive = 0x21,
NSQualityOfServiceUserInitiated = 0x19,
NSQualityOfServiceUtility = 0x11,
NSQualityOfServiceBackground = 0x09,
NSQualityOfServiceDefault = -1
} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));
// 用于 UI 交互,比如处理控件事件和屏幕绘制
NSQualityOfServiceUserInteractive
// 用于用户发起的请求,而且结果必须立即反馈给用户继续交互的任务。
// 比如用户点击列表触发的数据加载。
NSQualityOfServiceUserInitiated
// 用于用户不需要立即得到结果的任务。
// 比如大量文件操作,media 导入等。
NSQualityOfServiceUtility
// 用于用户不可见、无需用户察觉的任务。
// 比如数据备份、内容预加载。
NSQualityOfServiceBackground
// 表示没有定义优先级。
NSQualityOfServiceDefault
// 即将废弃,使用 qualityOfService 代替。
// 0.0 ~ 1.0,最高 1.0。
@property double threadPriority;
+ (double)threadPriority;
+ (BOOL)setThreadPriority:(double)p;
8、线程通信
8.1
// 主线程在 NSDefaultRunLoopMode 调用 aSelector。
// aSelector:没有返回值,形参最多有 1 个。
// wait:YES 会阻塞当前线程,直到主线程执行完 aSelector。NO 会立即返回。
// 如果当前线程就是主线程,wait 是 YES,aSelector 会立即执行。
// 无法取消,想要取消要调用带有 afterDelay 的函数。比如 performSelector:withObject:afterDelay:。
- (void)performSelectorOnMainThread:(SEL)aSelector
withObject:(id)arg
waitUntilDone:(BOOL)wait;
8.2
// array:aSelector 可以在哪些模式下被调用,至少要有一个值,否则不会被调用。
// 可以是:NSRunLoopCommonModes、NSDefaultRunLoopMode、UITrackingRunLoopMode。
// NSRunLoopMode 其实就是 NSString。
- (void)performSelectorOnMainThread:(SEL)aSelector
withObject:(id)arg
waitUntilDone:(BOOL)wait
modes:(NSArray<NSString *> *)array;
8.3
// wait:YES 会阻塞当前线程,直到目标线程执行完 aSelector。NO 会立即返回。
// 如果当前线程和目标线程相同,wait 是 YES,aSelector会立即执行。
- (void)performSelector:(SEL)aSelector
onThread:(NSThread *)thr
withObject:(id)arg
waitUntilDone:(BOOL)wait;
8.4
// array:aSelector 可以在哪些模式下被调用,至少要有一个值,否则不会被调用。
// 可以是:NSRunLoopCommonModes、NSDefaultRunLoopMode、UITrackingRunLoopMode。
// NSRunLoopMode 其实就是 NSString。
- (void)performSelector:(SEL)aSelector
onThread:(NSThread *)thr
withObject:(id)arg
waitUntilDone:(BOOL)wait
modes:(NSArray<NSString *> *)array;
8.5
// Invokes a method of the receiver on a new background thread.
// The method represented by aSelector must set up the thread environment
// just as you would for any other new thread in your program.
// For more information about how to configure and run threads, see Threading Programming Guide.
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg;
8.6
// timer 在当前线程的 NSDefaultRunLoopMode 才会 fire。
// This method sets up a timer to perform the aSelector message on the current thread’s run loop.
// The timer is configured to run in the default mode (NSDefaultRunLoopMode).
// When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector.
// It succeeds if the run loop is running and in the default mode;
// otherwise, the timer waits until the run loop is in the default mode.
- (void)performSelector:(SEL)aSelector
withObject:(id)anArgument
afterDelay:(NSTimeInterval)delay;
8.7
// array:aSelector 可以在哪些模式下被调用,至少要有一个值,否则不会被调用。
// 可以是:NSRunLoopCommonModes、NSDefaultRunLoopMode、UITrackingRunLoopMode。
- (void)performSelector:(SEL)aSelector
withObject:(id)anArgument
afterDelay:(NSTimeInterval)delay
inModes:(NSArray<NSRunLoopMode> *)modes;
8.8
// Cancels perform requests previously registered with the performSelector:withObject:afterDelay: instance method.
// All perform requests having the same target aTarget are canceled.
// This method removes perform requests only in the current run loop, not all run loops.
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget
selector:(SEL)aSelector
object:(id)anArgument;
注意:
线程通信的 performSelector 系列函数,都有这段文字说明,不知道是不是文档的 bug。我的理解是,这些函数的正确工作,依赖于线程的 runloop,而 dispatch queue 的线程可能没有 runloop,比如在 dispatch_async 里面调用 performSelector,aSelector 可能不会被调用。如果要在 dispatch queue 延迟调用某函数,可以用 dispatch_after 系列函数。
This method registers with the runloop of its current context, and depends on that runloop being run on a regular basis to perform correctly. One common context where you might call this method and end up registering with a runloop that is not automatically run on a regular basis is when being invoked by a dispatch queue. If you need this type of functionality when running on a dispatch queue, you should usedispatch_after
and related methods to get the behavior you want.
9、线程常驻
GCD 是从线程池里分配线程的,有可能会枯竭,所以在某些场景创建自己的常驻线程还是有必要的。
步骤是在线程的入口函数,创建自动释放池,创建 runloop,给 runloop 添加输入源或者 timer。最后启动 runloop,如果不需要退出,就调用 run 方法来启动,否则还是调用 runMode:beforeDate: 来启动。要小心强引用导致内存无法释放的问题。
9.1 创建线程
#import "MYViewController.h"
@interface MYViewController ()
@property (nonatomic, strong) NSThread *thread;
@property (nonatomic, assign) BOOL shouldKeepRunning;
@property (nonatomic, assign) CFRunLoopObserverRef observer;
@end
@implementation MYViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self createBtn];
[self createThread];
}
- (void)createThread {
// 创建线程。注意,会被强引用,线程 exit 后才会释放。
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(entryPoint) object:nil];
self.thread.name = @"子线程";
// 启动线程
[self.thread start];
}
- (void)entryPoint {
// 创建自动释放池,否则会有内存泄露。
@autoreleasepool {
// 创建 runloop。获取的时候系统会自动创建。
NSRunLoop *loop = [NSRunLoop currentRunLoop];
// 添加输入源,否则 runloop 会直接退出。
[loop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// 添加观察者,观察 runloop 的状态
[self addObserver];
// 启动 runloop。两种方式。
// 1、调用 run 方法启动有个问题:很难停下来。
// 手动移除输入源或 timer 也没用,系统可能会添加,导致无法退出 runloop。
// 如果没有输入源或 timer,会立即返回,否则 不会 往下执行。
// [loop run];
// 2、这里换个方法启动。想停下来,shouldKeepRunning 设置为 NO 就行了。
self.shouldKeepRunning = YES;
while (self.shouldKeepRunning &&
[loop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:5]]) {
// 这里不会一直执行,runMode 函数返回才会执行
// 比如在 performSelector 的函数执行后,runMode 函数才会返回
// 或者超时了,runMode 函数也会返回
static int i;
NSLog(@"这里是循环 %d", i++);
}
NSLog(@"这里还执行吗?上面的循环结束了才执行");
}
}
9.2
#pragma mark - 退出
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
// 退出 runloop
// 1、如果是调用 runMode:beforeDate: 启动 runloop,这样就能退出了
self.shouldKeepRunning = NO;
// 在子线程移除观察者,否则控制器无法释放。
[self performSelector:@selector(removeMyObserver) onThread:self.thread withObject:nil waitUntilDone:NO];
// 2、如果是调用 run 方法启动 runloop,很难退出,还会导致无法释放的问题。
// [self.thread cancel]; // cancel 不能退出 runloop。
// self.thread = nil; // 也不能退出 runloop。
// 可以释放控制器。runloop 没退出,直接干掉了。线程也被干掉了。
// 可是为什么不会崩溃?performSelector 是实例方法啊。。。
// 直接在子线程调用 exit,可以退出线程但无法释放控制器,为什么呢?
[NSThread performSelector:@selector(exit) onThread:self.thread withObject:nil waitUntilDone:NO];
// [NSThread.class performSelector:@selector(exit) onThread:self.thread withObject:nil waitUntilDone:NO]; // 结果同上
// 会崩溃。提示 +exit 是 unrecognized selector。
// [self.thread performSelector:@selector(exit) onThread:self.thread withObject:nil waitUntilDone:NO];
// 线程被干掉了,但是没释放控制器
// [self performSelector:@selector(exitMyThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)exitMyThread {
[self.thread cancel]; // 子线程还在
self.thread = nil; // 子线程还在
// CFRunLoopStop([[NSRunLoop currentRunLoop] getCFRunLoop]); // 会退出当前循环,然后重新进入循环。。。
// 子线程退出了,但控制器没有释放。想不通,有大神指点吗?
[NSThread exit];
// 下面的代码不会执行了,因为线程已经退出了
NSLog(@"这句代码不会执行了,因为线程已经退出了");
}
- (void)dealloc
{
NSLog(@"MYViewController 释放了");
}
9.3 观察
#pragma mark - 观察
- (void)addObserver {
CFRunLoopRef runloop = CFRunLoopGetCurrent();
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"%@: %@", [NSThread currentThread].name, [self getStateWithActivity:activity]);
});
CFRunLoopAddObserver(runloop, observer, kCFRunLoopDefaultMode);
self.observer = observer;
}
- (void)removeMyObserver {
if (self.observer) {
CFRunLoopRef runloop = CFRunLoopGetCurrent();
CFRunLoopRemoveObserver(runloop, self.observer, kCFRunLoopDefaultMode);
CFRelease(self.observer);
self.observer = nil;
}
}
- (NSString *)getStateWithActivity:(CFRunLoopActivity) activity {
NSString *state = @"未知";
switch (activity) {
case kCFRunLoopEntry: {
state = @"进入";
break;
}
case kCFRunLoopBeforeTimers: {
state = @"定时器";
break;
}
case kCFRunLoopBeforeSources: {
state = @"输入源";
break;
}
case kCFRunLoopBeforeWaiting: {
state = @"等待前";
break;
}
case kCFRunLoopAfterWaiting: {
state = @"等待后";
break;
}
case kCFRunLoopExit: {
state = @"退出";
break;
}
case kCFRunLoopAllActivities: {
state = @"所有";
break;
}
default:
break;
}
return state;
}
9.4 事件处理
#pragma mark - 事件处理
- (void)createBtn {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
[btn setTitle:@"点击" forState:UIControlStateNormal];
btn.frame = CGRectMake(100, 200, 100, 100);
[self.view addSubview:btn];
[btn addTarget:self action:@selector(clickBtn) forControlEvents:UIControlEventTouchUpInside];
}
- (void)clickBtn {
[self performSelector:@selector(log) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void)log {
NSLog(@"在子线程处理点击事件");
}
如有错误,欢迎指正和交流。