NSThread
NSThread 一线程对象 对应一个线程,当执行完之后,不能再重新开启同一个线程
开启线程有两种方法:
法一:主动开启
// 主动开线程
[NSThread detachNewThreadSelector:@selector(threadOne) toTarget:self withObject:nil];
法二:手动开启
//手动开启
_thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTwo) object:nil];
[_thread start];
取消线程,并不能真正的主动结束取消线程,只是设置了一个节点(即标记)然后你再主动结束线程。如下:
- (void)threadOne{
NSLog(@"threadOne:%@", [NSThread currentThread]);
}
- (void)threadTwo{
NSLog(@"threadTwo::%@", [NSThread currentThread]);
for (int i = 0; i < 5; i++) {
if([NSThread currentThread].isCancelled){
return;
}
sleep(2);
NSLog(@"%d", i);
// 取消,正在要取消线程要设置取消节点
if([NSThread currentThread].isCancelled){
return;
}
}
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
[_thread cancel];
}
自定义的NSOperation中结束线程也是一样的。
- (void)main{
NSLog(@"main");
for (int i = 0; i < 30; i++) {
sleep(1);
NSLog(@"%d", i);
// 取消节点设置越多,取消的效率越高
if (self.isCancelled) {
// 当是取消状态时,主动结束线程操作
return;
}
}
}
NSOperation
Operation抽象类必须用其子类实现,子类有NSBlockOperation和NSInvocationOperation
operation 可以自动开启,也可以手动开启
自动开启:是要添加到线程队列NSOperationQueue里面,添加进去就自动开始了,不用再start
手动开启:手动开启有start和main两种方法,手动开启的时候至少会分配一个到主线程上面,如果是自动开启都没有在主线程上。
两个的区别是:
start 没有重复开启: 因为在start方法里面对blockOperation的状态finish状态进行判断, 如果finish=YES,就不会执行了
main 直接是调用block,会重复执行
NSBlockOperation
block并不是先添加就先执行(因为是并行的,但是一般情况,比较少,和操作也不复杂的情况下,是先添加就先执行的),当operation执行,就不能再添加block,但是在添加到队列过后,还可以再添加block。
NSBlockOperation里面block块全部结束,那么这个NSBlockOperation才算结束(即finished = YES)
//运用 :多个任务处理(任务A,任务B,任务C)三个任务完成了,通过KVO监听得到 (任务是block块)
#import "EOCBlockOperationVC.h"
/*
operation 也可以手动开启
block并不是先添加就先执行(因为是并行的),当operation执行,就不能再添加block
NSBlockOperation里面block块全部结束,那么这个NSBlockOperation才算结束(即finished = YES)
//运用 :多个任务处理(任务A,任务B,任务C)三个任务完成了,通过KVO监听得到 (任务是block块)
手动开启的时候,会分配一个到主线程上执行
*/
@interface EOCBlockOperationVC (){
NSBlockOperation *blockOperation;
NSOperationQueue *operationQueue;
}
@end
@implementation EOCBlockOperationVC
- (void)viewDidLoad {
[super viewDidLoad];
void (^tblock)() = ^{
NSLog(@"tblock:%@", [NSThread currentThread]);
};
// 新开一个队列
operationQueue = [NSOperationQueue new];
blockOperation = [[NSBlockOperation alloc] init];
[blockOperation addObserver:self forKeyPath:@"isFinished" options:NSKeyValueObservingOptionNew context:@"1"];
// 将Block添加到Operation
[blockOperation addExecutionBlock:^{
NSLog(@"one block:%@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:^{
NSLog(@"two block:%@", [NSThread currentThread]);
}];
[blockOperation addExecutionBlock:tblock];
// 将Operation放到队列operationQueue中后就会自动执行,不用再start
[operationQueue addOperation:blockOperation];
[blockOperation addExecutionBlock:^{
NSLog(@"three block:%@", [NSThread currentThread]);
}];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"%@", keyPath);
NSLog(@"%@", change);
/*
打印结果:(1就是YES的意思)context可以用来区分线程,判断线程是否结束,如果结束了,就可以再重新开启新的线程,执行新的任务
kind = 1;
new = 1;
*/
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
/*
start 没有重复开启: 因为在start方法里面对blockOperation的状态finish状态进行判断,
如果finish=YES,就不会执行了
main 直接是调用block,会重复执行
需要注意的点
*/
[blockOperation start];
// [blockOperation main];
}
NSInvocationOperation
当参数只有一个或者两个的时候,我们需要传参数过去,并且在其他线程执行,可以用下面的方法之一:
[NSThread detachNewThreadSelector:<#(nonnull SEL)#> toTarget:<#(nonnull id)#> withObject:<#(nullable id)#>]
[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
但是当参数比较多的时候就不行了,就需要用到NSInvocationOperation。
需要注意的是:初始化NSInvocationOperation的时候需要方法签名,将方法对象化,并配置参数。如下:
#import "EOCInvocationOperationVC.h"
@interface EOCInvocationOperationVC (){
NSInvocationOperation *_invocationOperation;
NSInvocation *invation ;
NSString *backStr;
}
@end
@implementation EOCInvocationOperationVC
- (void)viewDidLoad {
[super viewDidLoad];
// 3 selector 方法签名(方法的对象结构,相关的结构信息:返回值(return value),调用者(target 一般为self),方法名(selector),参数(argument))
NSMethodSignature *signture = [self methodSignatureForSelector:@selector(name:age:sex:)];
// 2 signture NSInvocation可以将方法对象化
invation = [NSInvocation invocationWithMethodSignature:signture] ;
invation.target = self;
invation.selector = @selector(name:age:sex:); //和签名的seletor要对应起来
// 配置参数
NSString *name = @"eoc";
NSString *age = @"2";
NSString *sex = @"男";
[invation setArgument:&name atIndex:2]; // 前面的target和selector占了0和1,所以从2算起
[invation setArgument:&age atIndex:3];
[invation setArgument:&sex atIndex:4];
// 1 初始化 NSInvocationOperation 需要 invation
_invocationOperation = [[NSInvocationOperation alloc] initWithInvocation:invation];
// 手动调用
// [invation invoke];
// 将它放到队列中执行
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation:_invocationOperation];
}
/*
把这个方法 看作一个比较耗时业务,需要放到线程中执行
因为参数比较多,不好用下面的方法,因为下面的方法只能传一个参数object
[NSThread detachNewThreadSelector:<#(nonnull SEL)#> toTarget:<#(nonnull id)#> withObject:<#(nullable id)#>]
[self performSelector:<#(nonnull SEL)#> onThread:<#(nonnull NSThread *)#> withObject:<#(nullable id)#> waitUntilDone:<#(BOOL)#>]
这时因为参数多,就可以用NSInvocationOperation来操作
*/
- (NSString*)name:(NSString*)name age:(NSString*)age sex:(NSString*)sex{
NSLog(@"name: age: sex:%@", [NSThread currentThread]);
return [NSString stringWithFormat:@"%@%@%@", name, age, sex];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 注意下,NSInvocation在有返回值的时候,注意用__unsafe_unretained 这种类型来获取,不然会被释放掉,报内存错误。
// 手动调用,获取返回值,最好不要弄返回值,因为返回值对象是不安全类型,没有被强引用,要么会报内存错误,要么或者随便乱指一个对象
// 将它放到队列中执行,获取返回值不会有问题
__unsafe_unretained NSString *returnValue;
[invation getReturnValue:&returnValue];
NSLog(@"returnValue:%@", returnValue);
}
@end
自定义NSOperation
- 只是重写start方法,并不会调用dealloc方法,因为start方法中没有对方法做一个结束的通知,_finished = NO,所以dealloc 没被执行。
- 只是重写main方法,会调用dealloc方法。
- 如果start和main方法都重写,需要在start方法中手动调用main方法。
- start方法一般做异常处理,main方法做主要的业务逻辑处理。
- 如果要开启定时器之类的,我们还需要在main方法中开启RunLoop,定时器才会执行。
#import "EocOperation.h"
/*
改变_finished状态 YES
*/
@implementation EocOperation{
NSTimer *_timer;
}
// 前面finished是属性,后面_finished是变量,我们可以通过后面的变量来对前面的属性进行操作。
// 因为self.finished属性是仅读的,所以我们用这种方法来修改
@synthesize finished = _finished;
- (void)main{
// 主要的业务逻辑放到这里处理
NSLog(@"main2");
_finished = YES;
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeCount) userInfo:nil repeats:YES];
// 还是要管理Operation的生命周期,开启RunLoop,定时器才会执行
[[NSRunLoop currentRunLoop] run];
}
- (void)timeCount{
static int count = 0;
count++;
if (count > 5) {
[_timer invalidate];
}
NSLog(@"timeCount");
}
- (void)start{
//异常处理
NSLog(@"%@", [NSThread currentThread]);
// 重写start和main方法,需要在start方法中主动调用main方法,不然不会主动执行,方法有下面两种:
/*
法一:
[NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
法二:
[self main];
*/
// 防止手动调用多次,已经结束和正在执行就返回。
if (_finished) {
return;
}
if (self.isExecuting) {
return;
}
NSLog(@"start");
[self main];
// [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
}
/*
只是重写start方法,start没有对方法做一个结束的通知,_finished = NO,所以dealloc 没被执行。
dealloc 没被执行,是因为EocOperation的状态为未完成状态 _finished = NO;
self.finished属性是仅读的,所以重新赋值一个_finished来改变
*/
- (void)dealloc{
NSLog(@"start::%s", __func__);
}
NSOperation依赖关系
当一个线程A要另一个线程B结束过后才能开启,我们就说线程A依赖于线程B。并且可以对线程A和B添加观察者KVO,来对当线程结束过后进行的操作。
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"依赖";
queue = [NSOperationQueue new];
NSBlockOperation *eocOperation = [[NSBlockOperation alloc] init];
[eocOperation addExecutionBlock:^{
NSLog(@"one block task");
}];
[eocOperation addExecutionBlock:^{
NSLog(@"two block task");
}];
NSBlockOperation *twoOperation = [[NSBlockOperation alloc] init];
[twoOperation addExecutionBlock:^{
NSLog(@"Three block task");
}];
[twoOperation addExecutionBlock:^{
NSLog(@"Four block task");
}];
// 先后顺序关系 twoOperation 先,然后再eocOperation
// 在执行之前,把依赖关系建立好
[eocOperation addDependency:twoOperation];
// 也有对应的移除依赖
// [eocOperation removeDependency:twoOperation];
[queue addOperation:eocOperation];
[queue addOperation:twoOperation];
// 对twoOperation的完成状态,添加监听
[twoOperation addObserver:self forKeyPath:@"finished" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
NSLog(@"%@", keyPath);
}
僵尸线程
在当前线程开启的定时器这类的,需要在当前线程结束定时器这些操作,不然会造成线程不能结束,我们说着就是僵尸线程。
在当前RunLoop里面添加的端口,也需要在当前RunLoop中移除端口。
在子线程中开启的定时器,后面需要跟上运行当前RunLoop的代码,才会执行定时器。
#import "ThreadDeadViewCtr.h"
@interface ThreadDeadViewCtr (){
NSPort *port;
NSTimer *_timer;
}
@end
@implementation ThreadDeadViewCtr
- (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:@selector(ThreadTwo) toTarget:self withObject:nil];
}
- (void)ThreadOne{
NSLog(@"start thread");
port = [NSPort new];
[self performSelector:@selector(endThread:) withObject:nil afterDelay:2];
[[NSRunLoop currentRunLoop] addPort:port forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"finish thread");
// 扫尾工作,RunLoop结束过后,需要进行的操作
for (int i = 0; i < 5; i ++) {
NSLog(@"%d", i);
}
}
- (void)ThreadTwo{
NSLog(@"start thread");
_timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(stopTimer:) userInfo:nil repeats:YES];
// 因为是在子线程添加的定时器,需要运行当前RunLoop,定时器才会运行
[[NSRunLoop currentRunLoop] run];
NSLog(@"finish thread");
// 扫尾工作,RunLoop结束过后,需要进行的操作
for (int i = 0; i < 5; i ++) {
NSLog(@"%d", i);
}
}
- (IBAction)startThread:(id)sender{
// 在主线程结束定时器,并不会结束定时器所在的那个子线程,会造成内存泄漏
// 因为这里是在主线程结束定时器的
[_timer invalidate];
}
- (IBAction)endThread:(id)sender{
// 在当前线程添加的,需要在当前线程移除,要一一对应
[[NSRunLoop currentRunLoop] removePort:port forMode:NSDefaultRunLoopMode];
NSLog(@"IBAction Two");
}
- (IBAction)stopTimer:(id)sender{
NSLog(@"stopTimer");
static int count = 0;
count++;
if (count > 5) {
// 当前线程开启的,需要在当前线程移除,不然会内存泄漏
// 这是在添加定时器的那个线程中结束的定时器,因为stopTimer这个方法就在那个线程中
[_timer invalidate];
}
}
拓展文献:
iOS开发-Runloop详解()
iOS - RunLoop 深入理解