美文网首页APP & program
iOS多线程之NSThread

iOS多线程之NSThread

作者: 兰帕德 | 来源:发表于2021-12-23 16:01 被阅读0次

    NSThread是官方提供的一套面向对象的轻量级多线程开发技术。使用较为简单,不需要过多地操作线程的行为配置,但是仍然需要开发者自己处理线程的生命周期。相比于C语言中的pthread相关接口,NSThread易用性更强。

    一、NSThread开启新线程的方式
    1、构造器方式

    NSThread中提供了如下两个类方法:

    + (void)detachNewThreadWithBlock:(void (^)(void))block)
    
    + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument
    

    两个方法作用类似,都是自动开启新线程,执行任务。

    2、初始化方式

    通过手动调用初始化方法,可以获取到线程对象,从而更方便地对线程进行配置以及获取线程信息。示例如下:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        
        NSLog(@"%@", [NSThread currentThread]);
        
        NSThread *thread1 = [[NSThread alloc] initWithBlock:^{
            NSLog(@"1_%@", [NSThread currentThread]);
        }];
        [thread1 start];
        
        
        NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
        [thread2 start];
    }
    
    - (void)test {
        NSLog(@"2_%@", [NSThread currentThread]);
    }
    

    运行代码,控制台输出如下:

    2021-12-23 14:12:11.726978+0800 MyProject[38641:550626] <_NSMainThread: 0x6000039c8080>{number = 1, name = main}
    2021-12-23 14:12:11.727314+0800 MyProject[38641:550803] 1_<NSThread: 0x600003983500>{number = 7, name = (null)}
    2021-12-23 14:12:11.727395+0800 MyProject[38641:550804] 2_<NSThread: 0x600003983340>{number = 8, name = (null)}
    

    需要注意的是,初始化方式创建线程,需要调用start方法来启动线程任务。

    3、自定义线程

    通过继承NSThread可以创建自定义的线程,自定义线程通过内部main函数设定要执行的任务。示例如下:

    #import "MyThread.h"
    @implementation MyThread
    
    - (void)main {
        NSLog(@"自定义线程:%@", [NSThread currentThread]);
    }
    
    @end
    

    自定义线程的使用方法如下:

    MyThread *thread = [[MyThread alloc] init];
    [thread start];
    
    4、performSelector

    只要是NSObject的子类或实例都可以通过调用方法进入子线程和主线程,其实这些方法开辟的子线程,也是NSThread的一种体现方式。常用方法如下:

    // 当前线程,延时1s执行
    [self performSelector:@selector(text) withObject:nil afterDelay:1];
    

    该方法也响应了Objective-C的动态性:延时到运行时才绑定方法。需要注意的是,带afterDelay的延时函数,会在内部创建一个NSTimer,然后添加到当前线程的runloop中。如果当前线程没有开启Runloop,则方法会失效,例如:

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self performSelector:@selector(test) withObject:nil afterDelay:0];
    });
    

    这里test方法是不会执行的,因为
    - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay
    这个方法要创建提交任务到runloop上,而GCD底层创建的线程是默认不开启对应的runloop的,所以test不会执行。
    如果将dispatch_get_global_queue改为主队列,由于主队列所在的主线程是默认开启runloop的,则回去执行test。如果将dispatch_async改为dispatch_sync,同步在当前线程执行,如果当前线程是主线程,则test可以执行。

    // 回到主线程
    // waitUntilDone YES - 立刻执行 NO - 等待当前Runloop空闲后执行
    [self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
    
    // 开辟子线程
    [self performSelectorInBackground:@selector(test) withObject:nil];
    
    // 在指定的线程中执行任务
    // 任务执行依赖Runloop,所以线程Runloop必须开启
    [self performSelector:@selector(test) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES];
    
    二、相关属性与方法
    1、获取线程信息的类方法、属性
    // 获取当前线程NSThread对象
    @property (class, readonly, strong) NSThread *currentThread;
    
    // 获取主线程NSThread对象
    @property (class, readonly, strong) NSThread *mainThread;
    
    // 当前应用程序是否支持多线程
    + (BOOL)isMultiThreaded;
    
    // 当前是否为主线程
    @property (class, readonly) BOOL isMainThread;
    
    // 获取当前线程的优先级
    + (double)threadPriority;
    
    // 当前线程执行代码的堆栈地址
    @property (class, readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses;
    
    // 当前线程执行代码的堆栈信息
    @property (class, readonly, copy) NSArray<NSString *> *callStackSymbols;
    

    2、控制线程行为的类方法

    // 使当前线程休眠到指定时间
    + (void)sleepUntilDate:(NSDate *)date;
    
    // 使当前线程休眠一定时间
    + (void)sleepForTimeInterval:(NSTimeInterval)ti;
    
    // 结束当前线程
    + (void)exit;
    
    // 设置当前线程的优先级
    + (BOOL)setThreadPriority:(double)p;
    

    3、其他常用实例属性、方法

    // 线程名称
    @property (nullable, copy) NSString *name;
    
    // 堆栈大小
    @property NSUInteger stackSize;
    
    // 是否为主线程
    @property (readonly) BOOL isMainThread;
    
    // 是否正在执行
    @property (readonly, getter=isExecuting) BOOL executing;
    
    // 是否执行完成
    @property (readonly, getter=isFinished) BOOL finished;
    
    // 是否已经取消
    @property (readonly, getter=isCancelled) BOOL cancelled;
    
    // 取消线程(线程不会立即停止执行,需要开发者根据cancelled属性做逻辑处理)
    - (void)cancel;
    
    // 启动线程
    - (void)start;
    
    // 线程主体,自定义的NSThread子类通过重写该方法来指定要执行的任务
    - (void)main;
    
    三、相关通知

    系统定义了几个线程相关的通知,我们可以通过监听来关注多线程的运行状态,具体名称以及发送时机如下:

    // 将进入多线程运行模式
    FOUNDATION_EXPORT NSNotificationName const NSWillBecomeMultiThreadedNotification;
    // 已经进入多线程运行模式
    FOUNDATION_EXPORT NSNotificationName const NSDidBecomeSingleThreadedNotification;
    // 某个线层结束
    FOUNDATION_EXPORT NSNotificationName const NSThreadWillExitNotification;
    
    四、NSThread+Runloop实现常驻线程

    NSThread在实际开发中,比较常用到的场景就是实现常驻线程。
    由于每次开辟子线程都会消耗CPU,所以频繁开启子线程会消耗大量CPU,而且创建的子线程都是任务执行后,就会被释放,不能再次利用。
    常驻线程,就是一个使用完以后,不会释放,可以再次利用的线程。
    使用NSThead+Runloop实现常驻线程,示例代码如下:

    - (void)viewDidLoad {
        [super viewDidLoad];
    
        [self performSelector:@selector(test) onThread:[self shareThread] withObject:nil waitUntilDone:NO];
    }
    
    - (void)test {
        NSLog(@"test:%@", [NSThread currentThread]);
    }
    
    // 创建一个NSThread单例
    - (NSThread *)shareThread {
        static NSThread *shareThread = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil];
            [shareThread setName:@"MyThread"];
            [shareThread start];
        });
        return shareThread;
    }
    
    - (void)threadTest {
        @autoreleasepool {
            NSRunLoop *runloop = [NSRunLoop currentRunLoop];
            [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
            [runloop run];
        }
    }
    

    相关文章

      网友评论

        本文标题:iOS多线程之NSThread

        本文链接:https://www.haomeiwen.com/subject/lqmhqrtx.html