NSThread的使用

2020-09-17  本文已影响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实现。

    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);
}
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];
}
@property (nonatomic, strong) NSString *name;

将nonatomic变成atomic原子性

@property (atomic, strong) NSString *name;

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

上一篇下一篇

猜你喜欢

热点阅读