欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

iOS:多线程(二) —— NSThread

程序员文章站 2024-03-24 13:12:58
...

前言

在日常开发中,多线程的使用能帮助我们解决很多问题,比如大量数据的运算,复杂程序的执行,以及利用锁来实现一些需求,本系列文章主要介绍 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]);
}

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

以上是关于 NSThread 的介绍和简单使用的说明,相关 demo 请参考
https://github.com/G-Jayson/Multi-thread