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