美文网首页
NSThread的使用

NSThread的使用

作者: 想聽丿伱說衹愛我 | 来源:发表于2020-09-17 20:20 被阅读0次

    版本:iOS13.7

    一、简介

    NSThread可让使用者操作线程,要注意的是需要手动管理该线程的生命周期,可使用cancle取消该线程和exit退出当前线程。

    二、NSThread的API

    @interface NSThread : NSObject
    
    //返回当前线程 只读
    @property (class, readonly, strong) NSThread *currentThread;
    例:
        NSThread *thread = [NSThread currentThread];
        NSLog(@"%@", thread);
    输出:
    <NSThread: 0x600000894240>{number = 1, name = main}
    
    //创建一个新线程自动执行block
    + (void)detachNewThreadWithBlock:(void (^)(void))block;
    例:
        [NSThread detachNewThreadWithBlock:^{
            NSLog(@"执行block");
            NSThread *thread = [NSThread currentThread];
            //返回的并不是主线程
            NSLog(@"%@", thread);
        }];
    输出:
    执行block
    <NSThread: 0x600001eb1d40>{number = 6, name = (null)}
    
    //创建一个新线程自动执行对象target的选择器selector
    //argument为选择器传入的参数
    + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target 
                         withObject:(nullable id)argument;
    例:
    [NSThread detachNewThreadSelector:@selector(test:) toTarget:self withObject:@"�执行test"];
    
    - (void)test:(NSString *)object {
        NSLog(@"%@", object);
        NSThread *thread = [NSThread currentThread];
        //返回的并不是主线程
        NSLog(@"%@", thread);
    }
    输出:
    \^P执行test
    <NSThread: 0x600000c67000>{number = 5, name = (null)}
    
    //当前线程是否为多线程
    + (BOOL)isMultiThreaded;
    
    //线程的专属字典 只读
    //该字典可以在任何地方通过该线程来访问
    @property (readonly, retain) NSMutableDictionary *threadDictionary;
    
    //使当前线程休眠至该日期
    + (void)sleepUntilDate:(NSDate *)date;
    //使当前线程休眠ti秒钟
    + (void)sleepForTimeInterval:(NSTimeInterval)ti;
    例:
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"%@", [NSDate date]);
            //休眠至2秒后
            [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]];
            NSLog(@"%@", [NSDate date]);
            //休眠3秒钟
            [NSThread sleepForTimeInterval:3];
            NSLog(@"%@", [NSDate date]);
        });
    输出:
    Thu Sep 17 11:28:53 2020
    Thu Sep 17 11:28:55 2020
    Thu Sep 17 11:28:58 2020
    
    //退出当前线程
    + (void)exit;
    
    //当前线程优先级
    //0-1 越大越优先 主线程默认为0.5
    + (double)threadPriority;
    //设置当前线程的优先级 
    //返回是否设置成功
    + (BOOL)setThreadPriority:(double)p;
    
    //线程优先级
    //被废弃 建议使用qualityOfService
    @property double threadPriority;
    
    //线程优先级
    //在线程开始之后变为只读
    @property NSQualityOfService qualityOfService; 
    
    typedef NS_ENUM(NSInteger, NSQualityOfService) {
        //最高优先级,处理与用户交互和绘制图像的任务
        NSQualityOfServiceUserInteractive = 0x21,
        //次高优先级 处理用户发起的需要立即返回的任务
        NSQualityOfServiceUserInitiated = 0x19,
        //普通优先级 处理不需要立即返回的任务
        NSQualityOfServiceUtility = 0x11,
        //后面优先级 处理后台运行不紧急的任务
        NSQualityOfServiceBackground = 0x09,
        //默认优先级 级别高于 普通优先级
        NSQualityOfServiceDefault = -1
    }
    
    //返回线程中函数调用的地址的数组 只读
    //函数的调用就会有栈返回地址的记录,返回的就是函数调用返回的虚拟地址
    @property (class, readonly, copy) NSArray<NSNumber *> *callStackReturnAddresses;
    //返回线程中函数调用的名字 只读
    @property (class, readonly, copy) NSArray<NSString *> *callStackSymbols;
    例:
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSArray *array = [NSThread callStackReturnAddresses];
            NSLog(@"array = %@", array);
            NSArray *array1 = [NSThread callStackSymbols];
            NSLog(@"array1 = %@", array1);
        });
    输出:
    array = (0x103fdac87 0x1042878ac 0x104288a88 0x10428af06
             0x10429a5b6 0x10429af1b 0x7fff5dcd89f7 0x7fff5dcd7b77)
    array1 = (
        0   DEMO                     0x0000000103fdacbf __21-[Thread viewDidLoad]_block_invoke_3 + 95
        1   libdispatch.dylib        0x00000001042878ac _dispatch_call_block_and_release + 12
        2   libdispatch.dylib        0x0000000104288a88 _dispatch_client_callout + 8
        3   libdispatch.dylib        0x000000010428af06 _dispatch_queue_override_invoke + 1032
        4   libdispatch.dylib        0x000000010429a5b6 _dispatch_root_queue_drain + 351
        5   libdispatch.dylib        0x000000010429af1b _dispatch_worker_thread2 + 135
        6   libsystem_pthread.dylib  0x00007fff5dcd89f7 _pthread_wqthread + 220
        7   libsystem_pthread.dylib  0x00007fff5dcd7b77 start_wqthread + 15
    )
    
    //线程的名字
    //主线程默认为main
    @property (nullable, copy) NSString *name;
    
    //线程使用栈区大小
    //默认是512K 524288
    @property NSUInteger stackSize;
    
    //线程是否为主线程 只读
    @property (readonly) BOOL isMainThread;
    //当前线程是否为主线程 只读
    @property (class, readonly) BOOL isMainThread;
    //返回主线程 只读
    @property (class, readonly, strong) NSThread *mainThread;
    
    //初始化
    //需要调用start开启线程
    - (instancetype)init;
    //通过调用对象的选择器进行初始化
    //target 对象 selector 对象的选择器 argument 选择器的入参
    //需要调用start开启线程
    - (instancetype)initWithTarget:(id)target selector:(SEL)selector 
                            object:(nullable id)argument;
    //通过调用block进行初始化
    //需要调用start开启线程
    - (instancetype)initWithBlock:(void (^)(void))block;
    
    //线程是否正在执行 只读
    @property (readonly, getter=isExecuting) BOOL executing;
    //线程是否已完成 只读
    @property (readonly, getter=isFinished) BOOL finished;
    //线程是否已取消 
    @property (readonly, getter=isCancelled) BOOL cancelled;
    
    //取消线程
    - (void)cancel;
    
    //开启线程
    //通过init初始化的线程需要调用start开启
    - (void)start;
    
    //自定义线程的main方法
    //当通过init初始化调用start后,会自动调用main方法
    - (void)main;
    例:
    //自定义一个NSThread的子类
    @interface TestThread : NSThread
    
    @end
    
    @implementation TestThread
    
    - (void)main {
        //会在start后自动调用该方法
        //要执行的操作
    }
    
    @end
    

    三、NSObject的NSThreadPerformAdditions扩展

    @interface NSObject (NSThreadPerformAdditions)
    
    //在主线程中调用选择器
    //aSelector 要调用的选择器 arg 选择器的入参
    //wait 是否需要等待选择器执行完毕
    //array 模式
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg
            waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
    //默认的模式,系统大部分操作都会运行在该模式下
    NSDefaultRunLoopMode 
    //当滚动scorllView时系统会将模式从NSDefaultRunLoopMode变成该模式
    //停止滚动后自动变回为NSDefaultRunLoopMode
    UITrackingRunLoopMode
    //该模式相当于UITrackingRunLoopMode和NSDefaultRunLoopMode的集合
    NSRunLoopCommonModes
    
    //在主线程中调用选择器
    //与上面的方法一致 运行在NSRunLoopCommonModes模式下
    - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg 
                          waitUntilDone:(BOOL)wait;
    
    //在指定线程中调用选择器
    //thr 要调用选择器的线程
    //其他参数 参考performSelectorOnMainThread 
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr 
                 withObject:(nullable id)arg waitUntilDone:(BOOL)wait 
                      modes:(nullable NSArray<NSString *> *)array;
    //在指定线程中调用选择器
    //与上面的方法一致 运行在NSRunLoopCommonModes模式下
    - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr 
                 withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
    
    //在后台线程中调用选择器
    //aSelector 要调用的选择器 arg 选择器的入参
    - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg;
    
    @end
    

    四、给线程加锁

    程序运行时会存在很多线程,因为线程读写资源时会有先后或同时读写资源的情况,因此为了防止线程读取数据的同时数据被另一线程修改,所以需要给线程在读写数据时加锁,保证读写同一个数据对象的线程只有一个,当这个线程执行完成之后才能被另一线程使用。可以通过@synchronizeddispatch_semaphoreNSLockatomic实现。

    • @synchronized
      synchronized作用是给{ }里的代码加上互斥锁
      下面为取钱的例子
        NSThread *thread1 = [[NSThread alloc] initWithTarget:self 
                                              selector:@selector(drawMoney:)  object:@(600)];
        thread1.name = @"thread1";
        [thread1 start];
        
        NSThread *thread2 = [[NSThread alloc] initWithTarget:self 
                                              selector:@selector(drawMoney:) object:@(700)];
        thread2.name = @"thread2";
        [thread2 start];
    
    - (void)drawMoney:(NSNumber *)money {
        static NSInteger total = 1000;
        @synchronized (self) {
            if (total > money.integerValue) {
                total -= money.integerValue;
                NSLog(@"%@取了%@,还剩%ld", [NSThread currentThread].name, money, total);
            } else {
                NSLog(@"%@想取%@,但还剩%ld", [NSThread currentThread].name, money, total);
            }
        }
    }
    输出:
    thread1取了600,还剩400
    thread2想取700,但还剩400
    

    若不加锁,可能会输出:
    thread1取了600,还剩400
    thread2取了700,还剩300

        NSThread *thread1 = [[NSThread alloc] initWithTarget:self 
                                              selector:@selector(drawMoney:) object:@(600)];
        thread1.name = @"thread1";
        [thread1 start];
        
        NSThread *thread2 = [[NSThread alloc] initWithTarget:self 
                                              selector:@selector(drawMoney:) object:@(700)];
        thread2.name = @"thread2";
        [thread2 start];
        //创建信号
        self.semaphore = dispatch_semaphore_create(1);
    
    - (void)drawMoney:(NSNumber *)money {
        dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
        static NSInteger total = 1000;
        if (total > money.integerValue) {
            total -= money.integerValue;
            NSLog(@"%@取了%@,还剩%ld", [NSThread currentThread].name, money, total);
        } else {
            NSLog(@"%@想取%@,但还剩%ld", [NSThread currentThread].name, money, total);
        }
        dispatch_semaphore_signal(self.semaphore);
    }
    
    • NSLock
      使用方式与dispatch_semaphore大致相同。
      除了NSLock外,还有条件锁NSConditionLock、递归锁NSRecursiveLock
    self.lock = [[NSLock alloc] init];
    
    - (void)drawMoney:(NSNumber *)money {
        [self.lock lock];
        static NSInteger total = 1000;
        if (total > money.integerValue) {
            total -= money.integerValue;
            NSLog(@"%@取了%@,还剩%ld", [NSThread currentThread].name, money, total);
        } else {
            NSLog(@"%@想取%@,但还剩%ld", [NSThread currentThread].name, money, total);
        }
        [self.lock unlock];
    }
    
    • atomic
      在声明属性时,通常使用nonatomic非原子性的。
    @property (nonatomic, strong) NSString *name;
    

    将nonatomic变成atomic原子性

    @property (atomic, strong) NSString *name;
    

    因为原子性属性内部有一个锁,所以在读写这个属性的时候,会保证同一时间内只有一个线程能够操作。然而只要加了锁,都会十分消耗性能,如非必要,通常使用nonatomic。

    相关文章

      网友评论

          本文标题:NSThread的使用

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