iOS:多线程(二) —— NSThread

1,533 阅读5分钟

前言

在日常开发中,多线程的使用能帮助我们解决很多问题,比如大量数据的运算,复杂程序的执行,以及利用锁来实现一些需求,本系列文章主要介绍 iOS 中多线程实现技术的用法。

iOS:多线程(一) —— pthread

关于 pthread 的介绍和使用请查看之前的文章,本篇文章针对 NSThread 来赘述。

关于 NSThread

NSThread 是苹果官方提供给我们的一种面向对象的轻量级多线程解决方案,一个 NSThread 对象代表一个线程,需要程序员手动管理线程的生命周期,处理线程同步等问题。

NSThread 使用

常用方法

在日常开发中,我们经常会用 [NSThread currentThread] 来获取当前线程,便于开发调试,这是最常用的一个方法,除此之外,下面的这几个方法,使用频率也是非常高,基于 NSObjectNSThreadPerformAdditions 分类中的方法,继承自 NSObject 的子类都可以很方便的调用。

    // 当前线程睡到指定时间
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];

    // 线程沉睡时间间隔 常用在设置启动页间隔
    [NSThread sleepForTimeInterval:1.0];

    // 返回调用堆栈信息 可用于调试
    
    // [NSThread callStackSymbols]  return NSArray
    // [NSThread callStackReturnAddresses]   return NSArray
    NSLog(@"callStackSymbols : %@", [NSThread callStackSymbols]);
    NSLog(@"callStackReturnAddresses : %@", [NSThread callStackReturnAddresses]);


@interface NSObject (NSThreadPerformAdditions)

// 指定方法在主线程中执行
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
	// equivalent to the first method with kCFRunLoopCommonModes

// 指定方法在某个线程中执行
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
	// equivalent to the first method with kCFRunLoopCommonModes

//  指定方法在开启的子线程中执行
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

@end

创建线程

  • 实例方法创建线程

    实例方法创建线程,可以根据需要设置参数,调用 - start() 才能开启线程,调用 - cancel() 取消线程

    // SEL
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(nsThreadMethod:) object:@"thread"];

    NSThread *thread2 = [[NSThread alloc] init];

    // block 方式
    NSThread *thread3 = [[NSThread alloc] initWithBlock:^{
        NSLog(@"block 创建 thread  %s : %@", __func__, [NSThread currentThread]);
    }];

    // 设置名称
    thread1.name = @"thread1";

    // 设置线程优先级 调度优先级的取值范围是0.0 ~ 1.0,默认0.5,值越大,优先级越高。
    thread3.threadPriority = 0.0;

    // 启动线程
    [thread1 start];

    // 线程是否正在执行
    if ([thread3 isExecuting]) {
        NSLog(@"thread1 is executing! ");
    }
    
    // 取消线程
    [thread1 cancel];

    // 线程是否撤销
    if ([thread1 isCancelled]) {
        NSLog(@"thread1 canceled!");
    }

    // 线程是否执行结束
    if ([thread3 isFinished]) {
        NSLog(@"thread3 is finished!");
    }
  • 类方法创建线程

    类方法创建 NSThread 不需要再调用 start 方法,设置参数也是通过类方法设置

    // block 方式
    [NSThread detachNewThreadWithBlock:^{
        NSLog(@"类方法 block 创建 thread : %s : %@", __func__, [NSThread currentThread]);
    }];

    // SEL 方式
    [NSThread detachNewThreadSelector:@selector(nsThreadMethod:) toTarget:self withObject:nil];
    
    /*
     [NSThread currentThread];  获取当前线程
     [NSThread isMultiThreaded];  当前代码运行线程是否为子线程
     */
    
    // 当前线程睡到指定时间
    [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];

    // 线程沉睡时间间隔 常用在设置启动页间隔
    [NSThread sleepForTimeInterval:1.0];

    // 获取线程优先级 / 设置优先级
    double priority = [NSThread threadPriority];
    NSLog(@"当前线程优先级 : %f", priority);
    [NSThread setThreadPriority:0.9];

线程间通信

通常,例如我们有个网络请求是在子线程中执行,请求成功后我么要回到主线程中刷新UI,这是时候我们就需要了解子线程和主线程之间的通信,NSThread 为我们提供了解决方案,调用 NSObjectNSObject (NSThreadPerformAdditions) 分类中的方法,所有继承自 NSObject 实例化对象都可调用以下方法

    // 指定方法在主线程中执行
    [self performSelectorOnMainThread:@selector(performMethod:)  // 要执行的方法
                           withObject:nil                         // 执行方法时,要传入的参数 类型为 id
                        waitUntilDone:YES];                       // 当前线程是否要被阻塞,直到主线程将我们指定的代码块执行完,当前线程为主线程,设置为YES时,会立即执行,为NO时加入到RunLoop中在下一次运行循环时执行

    [self performSelectorOnMainThread:@selector(performMethod:)
                           withObject:nil
                        waitUntilDone:YES
                                modes:@[@"kCFRunLoopDefaultMode"]];

    // 指定方法在某个线程中执行
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait;

    // 在当前线程上执行操作,调用 NSObject 的 performSelector:相关方法
    - (id)performSelector:(SEL)aSelector;
    - (id)performSelector:(SEL)aSelector withObject:(id)object;
    - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
    
  
    //  指定方法在开启的子线程中执行 (相当于创建了一个子线程,并且执行方法)
    [self performSelectorInBackground:@selector(performMethod:) withObject:nil];

举个例子,我们来模拟网络请求成功回到线程刷新 UI 的实现

    // 开辟子线程模拟网络请求
   [NSThread detachNewThreadWithBlock:^{
        NSLog(@"类方法 block 创建 thread : %s : %@", __func__, [NSThread currentThread]);
        
        // 模拟网络请求耗时操作
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"网络请求中  %@",[NSThread currentThread]);
        }
        
        NSLog(@"网络请求成功 准备回到主线程刷新 UI  %@",[NSThread currentThread]);
        
        // 主线程刷新UI
        [self performSelectorOnMainThread:@selector(mainThreadRefreshUI) withObject:nil waitUntilDone:YES];
    }];

// 主线程刷新 UI 调用方法
- (void)mainThreadRefreshUI {
    NSLog(@"回到了主线程并且刷新 UI  %s : %@", __func__, [NSThread currentThread]);
}

跟我们预想的一样,网络请求耗时操作是在子线程中执行,执行结束后调用线程间通信方法回到了主线程刷新 UI。

以上是关于 NSThread 的介绍和简单使用的说明,相关 demo 请参考

github.com/G-Jayson/Mu…