一、线程方式:NSThread,NSQueueOperation, GCD, pthread
二、僵尸线程问题(NSThead操作)
- 在子线程添加定时器,如果在主线程中移除定时器会造成僵尸线程。
1、在子线程添加定时器,必须要执行[[NSRunLoop currentRunLoop] run]
。因为runloop中的资源没被移除。
2、在子线程中无法执行到NSLog(@"runloop finished")
这段代码。
3、触摸屏幕时,将定时器从runloop中移除,但是定时器的时间依旧还在执行。
- (void)viewDidLoad {
[super viewDidLoad];
NSString * str = @"12211212";
[NSThread detachNewThreadSelector:@selector(threadEvent:) toTarget:self withObject:str];
}
- (void)threadEvent:(id)thread{
NSLog(@"%@",[NSThread currentThread]);
//self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:@"2121" repeats:YES];
self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:nil repeats:YES];
//使用 NSRunLoopCommonModes 模式,这个模式相当于 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode 的结合
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"runloop finished");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.timer invalidate];
}
- (void)timeEvent:(NSTimer *)sender{
//定时器事件在子线程中执行
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%s",__func__);
}
线程依旧存在,如图一所示。
- 如何解决僵尸线程
在该线程中移除掉定时器。
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEvent:) object:self];
[self.thread start];
}
- (void)threadEvent:(id)thread{
NSLog(@"%@",[NSThread currentThread]);
//self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:@"2121" repeats:YES];
self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:nil repeats:YES];
//使用 NSRunLoopCommonModes 模式,这个模式相当于 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode 的结合
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"runloop finished");
}
- (void)timeInvalidate:(id)sender{
[self.timer invalidate];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
/*
You cannot cancel messages queued using this method.
If you want the option of canceling a message on the current thread,
you must use either the performSelector:withObject:afterDelay: or performSelector:withObject:afterDelay:inModes: method.
要想自线程消灭掉,必须在其子线程中执行performSelector:withObject:afterDelay:或者performSelector:withObject:afterDelay:inModes: 方法
*/
[self performSelector:@selector(timeInvalidate:) onThread:self.thread withObject:@"e" waitUntilDone:YES];
}
- (void)timeEvent:(NSTimer *)sender{
//定时器事件在子线程中执行
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%s",__func__);
}
但是依旧不能解决僵尸线程的问题,因为在移除定时器的同时,给runLoop中添加一个timeInvalidate
方法。
- 彻底解决僵尸线程
1、方法一:
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEvent:) object:self];
[self.thread start];
}
- (void)threadEvent:(id)thread{
NSLog(@"%@",[NSThread currentThread]);
//self.timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:@"2121" repeats:YES];
self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:nil repeats:YES];
//使用 NSRunLoopCommonModes 模式,这个模式相当于 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode 的结合
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
[self performSelector:@selector(timeInvalidate:) withObject:nil afterDelay:5];
[[NSRunLoop currentRunLoop] run];
NSLog(@"runloop finished");
}
- (void)timeInvalidate:(id)sender{
[self.timer invalidate];
}
- (void)timeEvent:(NSTimer *)sender{
//定时器事件在子线程中执行
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%s",__func__);
}
2、方法二:
- (void)viewDidLoad {
[super viewDidLoad];
_flag = NO;
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEvent:) object:self];
[self.thread start];
}
- (void)threadEvent:(id)thread{
NSLog(@"%@",[NSThread currentThread]);
self.timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(timeEvent:) userInfo:nil repeats:YES];
//使用 NSRunLoopCommonModes 模式,这个模式相当于 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode 的结合
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"runloop finished");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.flag = YES;
}
- (void)timeEvent:(NSTimer *)sender{
//定时器事件在子线程中执行
NSLog(@"%@",[NSThread currentThread]);
NSLog(@"%s",__func__);
if(self.flag == YES){
[self.timer invalidate];
self.timer = nil;
}
}
- 在runloop中添加port,如果在其它线程中移除掉,会产生僵尸线程。
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEvent:) object:self];
[self.thread start];
}
- (void)threadEvent:(id)thread{
NSLog(@"start");
self.myRunLoop = [NSRunLoop currentRunLoop];
self.port = [[NSPort alloc] init];
[self.myRunLoop addPort:self.port forMode:NSRunLoopCommonModes];
[self.myRunLoop run];
NSLog(@"finished");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.myRunLoop removePort:self.port forMode:NSRunLoopCommonModes];
}
- 解决在runloop中添加port产生僵尸线程
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadEvent:) object:self];
[self.thread start];
}
- (void)threadEvent:(id)thread{
NSLog(@"start");
self.myRunLoop = [NSRunLoop currentRunLoop];
self.port = [[NSPort alloc] init];
[self.myRunLoop addPort:self.port forMode:NSRunLoopCommonModes];
[self performSelector:@selector(portCancel) withObject:nil afterDelay:5];
[self.myRunLoop run];
NSLog(@"finished");
}
- (void)portCancel{
[self.myRunLoop removePort:self.port forMode:NSRunLoopCommonModes];
}
结论:
在子线程中,给runloop添加资源,必须在该线程中进行释放,否则会产生僵尸线程。
三、线程队列NSQueueOperation
3.1 Operation对象的并发和非并发
3.2 系统的Operation对象
3.2.1 NSBlockOperation
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 2;
NSBlockOperation * blockOperation = [[NSBlockOperation alloc] init];
[blockOperation addExecutionBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[queue addOperation:blockOperation];
}
打印结果:
<NSThread: 0x608000270b40>{number = 5, name = (null)}
<NSThread: 0x608000270940>{number = 4, name = (null)}
<NSThread: 0x608000270b80>{number = 6, name = (null)}
<NSThread: 0x608000270900>{number = 3, name = (null)}
<NSThread: 0x608000270b40>{number = 5, name = (null)}
<NSThread: 0x608000270940>{number = 4, name = (null)}
<NSThread: 0x608000270b80>{number = 6, name = (null)}
3.3.2 NSInvocationOperation(NSInvocation)
- 使用initWithTarget:selector:object:开启线程。缺点:只能传递一个参数。
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
NSInvocationOperation * invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationEvent) object:nil];
[queue addOperation:invocationOperation];
}
- (void)invocationOperationEvent{
NSLog(@"%@",[NSThread currentThread]);
}
- 使用NSInvocation传递多个参数
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue * queue = [[NSOperationQueue alloc] init];
NSMethodSignature * signature = [self methodSignatureForSelector:@selector(name:andAge:andSex:)];
NSInvocation * invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.selector = @selector(name:andAge:andSex:);
invocation.target = self;
NSString * name = @"Jerry";
NSString * age = @"2";
NSString * sex = @"男";
//这里的index要从2开始,前两个已经被target和selector占用
[invocation setArgument:&name atIndex:2];
[invocation setArgument:&age atIndex:3];
[invocation setArgument:&sex atIndex:4];
NSInvocationOperation * invocationOperation = [[NSInvocationOperation alloc] initWithInvocation:invocation];
[queue addOperation:invocationOperation];
}
- (void)name:(NSString *)name andAge:(NSString *)age andSex:(NSString *)sex{
NSLog(@"name = %@,age = %@,sex = %@",name,age,sex);
}
3.3 自定义Operation对象
3.3.1 Operation状态(KVO通知)
- 当isFinished状态为NO时,NSOperation不能执行dealloc。
@interface CustomOperation : NSOperation
@end
@implementation CustomOperation
@synthesize finished = _finished;
- (void)dealloc{
NSLog(@"%s",__func__);
}
- (BOOL)isFinished{
return _finished;
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue * queue = [NSOperationQueue new];
CustomOperation * customOperation = [CustomOperation new];
[queue addOperation:customOperation];
}
CustomOperation
不能执行dealloc
,用KVO的形式,改变isFinished的值为YES,才能执行CustomOperation才能被释放。
[self willChangeValueForKey:@"isFinished"];//_finished,finished
_finished = YES;
[self didChangeValueForKey:@"isFinished"];
3.3.2 配置Operation执行行为(依赖关系,优先级等)
NSOperationQueue * operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation * blockOperation = [[NSBlockOperation alloc] init];
[blockOperation addExecutionBlock:^{
NSLog(@"NSBlockOperation = %@",[NSThread currentThread]);
}];
NSInvocationOperation * invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationEvent) object:nil];
NSBlockOperation * blockOperation2 = [[NSBlockOperation alloc] init];
[blockOperation addExecutionBlock:^{
NSLog(@"NSBlockOperation2 = %@",[NSThread currentThread]);
}];
//在入队前处理好依赖关系
[blockOperation addDependency:invocationOperation];
[blockOperation2 addDependency:blockOperation];
[operationQueue addOperation:blockOperation];
[operationQueue addOperation:invocationOperation];
[operationQueue addOperation:blockOperation2];
结果:
invocationOperation = <NSThread: 0x6080000740c0>{number = 3, name = (null)}
NSBlockOperation = <NSThread: 0x600000079580>{number = 4, name = (null)}
NSBlockOperation2 = <NSThread: 0x6080000740c0>{number = 3, name = (null)}
⚠️:A线程依赖B线程,B依赖C,C依赖A,这样会产生死锁。