关于多线程的简要梳理(网上看到的知识整理)

多线程编程

 

前言

每个iOS应用程序都有个专门用来更新显示UI界面、处理用户触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来极坏的用户体验。一般的解决方案就是将那些耗时的操作放到另外一个线程中去执行,多线程编程是防止主线程堵塞,增加运行效率的最佳方法。

iOS中有3种常见的多线程编程方法:

1.NSThread

这种方法需要管理线程的生命周期、同步、加锁问题,会导致一定的性能开销

 

2.NSOperationNSOperationQueue

是基于OC实现的。NSOperation以面向对象的方式封装了需要执行的操作,然后可以将这个操作放到一个NSOperationQueue中去异步执行。不必关心线程管理、同步等问题。

 

3.Grand Centeral Dispatch

简称GCDiOS4才开始支持,是纯C语言的API。自iPad2开始,苹果设备开始有了双核CPU,为了充分利用这2个核,GCD提供了一些新特性来支持多核并行编程

 

这篇文章简单介绍NSThread这个类,一个NSThread实例就代表着一条线程


一、获取当前线程

NSThread *current = [NSThread currentThread];<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>


二、获取主线程

NSThread *main = [NSThread mainThread];
NSLog(@"主线程:%@", main);


打印结果是:

2013-04-1821:36:38.599 thread[7499:c07] 主线程:<NSThread: 0x71434e0>{name= (null), num = 1}

num相当于线程的id,主线程的num是为1

 


三、NSThread的创建

1.动态方法

- (id)initWithTarget:(id)target selector:(SEL)selectorobject:(id)argument;

* 在第2行创建了一条新线程,然后在第4行调用start方法启动线程,线程启动后会调用selfrun:方法,并且将@"mj"作为方法参数

1// 初始化线程

2NSThread *thread = [[[NSThread alloc] initWithTarget:selfselector:@selector(run:) object:@"mj"]autorelease];

3 // 开启线程

4 [threadstart];
假如 run: 方法是这样的:

 - (void)run:(NSString *)string {
     NSThread *current = [NSThreadcurrentThread];
     NSLog(@"执行了run:方法-参数:%@,当前线程:%@", string, current);
}

打印结果为:

2013-04-1821:40:33.102 thread[7542:3e13] 执行了run:方法-参数:mj,当前线程:<NSThread: 0x889e8d0>{name= (null), num = 3}

可以发现,这条线程的num值为3,说明不是主线程,主线程的num1

 

2.静态方法

+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)targetwithObject:(id)argument;
[NSThread detachNewThreadSelector:@selector(run:)toTarget:self withObject:@"mj"];

执行完上面代码后会马上启动一条新线程,并且在这条线程上调用selfrun:方法,以@"mj"为方法参数

3.隐式创建线程

[self performSelectorInBackground:@selector(run:) withObject:@"mj"];
会隐式地创建一条新线程, 并且在这条线程上调用 self run: 方法,以 @"mj" 为方法参数

 


四、暂停当前线程

[NSThread sleepForTimeInterval:2];
NSDate *date = [NSDate dateWithTimeInterval:2 sinceDate:[NSDate date]]; 
[NSThread sleepUntilDate:date];

上面两种做法都是暂停当前线程2 


五、线程的其他操作

1.在指定线程上执行操作

[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES];

* 上面代码的意思是在 thread 这条线程上调用 self run 方法

* 最后的YES代表:上面的代码会阻塞,等run方法在thread线程执行完毕后,上面的代码才会通过

 

2.在主线程上执行操作

[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; 

在主线程调用 self run 方法

 

3.在当前线程执行操作

[self performSelector:@selector(run) withObject:nil];

在当前线程调用 self run 方法

 


六、优缺点

1.优点:NSThread比其他多线程方案较轻量级,更直观地控制线程对象

2.缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销

 

 

 

多线程编程2-NSOperation

 

前言

1.上一讲简单介绍了NSThread的使用,虽然也可以实现多线程编程,但是需要我们去管理线程的生命周期,还要考虑线程同步、加锁问题,造成一些性能上的开销。我们也可以配合使用NSOperationNSOperationQueue实现多线程编程,实现步骤大致是这样的:

1> 先将需要执行的操作封装到一个NSOperation对象中

2> 然后将NSOperation对象添加到NSOperationQueue

3> 系统会自动将NSOperation中封装的操作放到一条新线程中执行

在此过程中,我们根本不用考虑线程的生命周期、同步、加锁等问题

 

下面列举一个应用场景,比如微博的粉丝列表:

每一行的头像肯定要从新浪服务器下载图片后才能显示的,而且是需要异步下载。这时候你就可以把每一行的图片下载操作封装到一个NSOperation对象中,上面有6行,所以要创建6NSOperation对象,然后添加到NSOperationQueue中,分别下载不同的图片,下载完毕后,回到对应的行将图片显示出来。

 

2.默认情况下,NSOperation并不具备封装操作的能力,必须使用它的子类,使用NSOperation子类的方式有3种:

1> NSInvocationOperation

2> NSBlockOperation

3> 自定义子类继承NSOperation,实现内部相应的方法

这讲先介绍如何用NSOperation封装一个操作,后面再结合NSOperationQueue来使用。

 


一、NSInvocationOperation

NSInvocationOperation *operation = [[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(run:) object:@"mj"] autorelease];
[operation start];


* 1行初始化了一个NSInvocationOperation对象,它是基于一个对象和selector来创建操作

* 2行调用了start方法,紧接着会马上执行封装好的操作,也就是会调用selfrun:方法,并且将@"mj"作为方法参数

* 这里要注意:默认情况下,调用了start方法后并不会开一条新线程去执行操作,而是在当前线程同步执行操作。只有将operation放到一个NSOperationQueue中,才会异步执行操作。

 


二、NSBlockOperation

1.同步执行一个操作

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){
         NSLog(@"执行了一个新的操作");
 }];
  // 开始执行任务
[operation start];

* 1行初始化了一个NSBlockOperation对象,它是用一个Block来封装需要执行的操作

*2行调用了start方法,紧接着会马上执行Block中的内容

* 这里还是在当前线程同步执行操作,并没有异步执行

 

2.并发执行多个操作

  NSBlockOperation *operation =[NSBlockOperation blockOperationWithBlock:^(){
 NSLog(@"执行第1次操作,线程:%@",[NSThread currentThread]);
  }];
 
 [operation addExecutionBlock:^() {
 NSLog(@"又执行了1个新的操作,线程:%@",[NSThread currentThread]);
  }];
 
 [operation addExecutionBlock:^() {
NSLog(@"又执行了1个新的操作,线程:%@",[NSThread currentThread]);
 }];

[operation addExecutionBlock:^() {
NSLog(@"又执行了1个新的操作,线程:%@",[NSThread currentThread]);
 }];

// 开始执行任务
[operation start];


* 1行初始化了一个NSBlockOperation对象

* 分别在第5913行通过addExecutionBlock:方法添加了新的操作,包括第1行的操作,一共封装了4个操作

* 在第18行调用start方法后,就会并发地执行这4个操作,也就是会在不同线程中执行

12013-02-0221:38:46.102 thread[4602:c07] 又执行了1个新的操作,线程:<NSThread: 0x7121d50>{name= (null), num = 1}

22013-02-0221:38:46.102 thread[4602:3f03] 又执行了1个新的操作,线程:<NSThread: 0x742e1d0>{name= (null), num = 5}

32013-02-0221:38:46.102 thread[4602:1b03] 执行第1次操作,线程:<NSThread: 0x742de50>{name= (null), num = 3}

42013-02-0221:38:46.102 thread[4602:1303] 又执行了1个新的操作,线程:<NSThread: 0x7157bf0>{name= (null), num = 4}

可以看出,每个操作所在线程的num值都不一样,说明是不同线程

 


三、NSOperation的其他用法

1.取消操作

operation开始执行之后, 默认会一直执行操作直到完成,我们也可以调用cancel方法中途取消操作

[operation cancel];

 

2.在操作完成后做一些事情

如果想在一个NSOperation执行完毕后做一些事情,就调用NSOperationsetCompletionBlock方法来设置想做的事情

operation.completionBlock = ^() {
    NSLog(@"执行完毕");
};

operation封装的操作执行完毕后,就会回调Block里面的内容


四、自定义NSOperation

如果NSInvocationOperationNSBlockOperation不能满足需求,我们可以直接新建子类继承NSOperation,并添加任何需要执行的操作。如果只是简单地自定义NSOperation,只需要重载-(void)main这个方法,在这个方法里面添加需要执行的操作。

下面写个子类DownloadOperation来下载图片

1.继承NSOperation,重写main方法

DownloadOperation.h

#import<Foundation/Foundation.h>
@protocolDownloadOperationDelegate;
 
@interface DownloadOperation : NSOperation
// 图片的url路径
@property(nonatomic, copy) NSString *imageUrl;
// 代理
@property(nonatomic, assign) id<DownloadOperationDelegate> delegate;
 
- (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate;
@end
 
// 图片下载的协议
@protocolDownloadOperationDelegate <NSObject>
- (void)downloadFinishWithImage:(UIImage *)image;
@end
DownloadOperation.m
 1#import"DownloadOperation.h"
 2
 3@implementationDownloadOperation
 4@synthesizedelegate = _delegate;
 5@synthesizeimageUrl = _imageUrl;
 6
 7// 初始化
 8 - (id)initWithUrl:(NSString*)url delegate:(id<DownloadOperationDelegate>)delegate {
 9     if (self = [super init]) {
10         self.imageUrl = url;
11         self.delegate = delegate;
12     }
13     return self;
14 }
15// 释放内存
16 - (void)dealloc {
17     [super dealloc];
18     [_imageUrl release];
19 }
20
21// 执行主任务
22 - (void)main {
23     // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池
24     @autoreleasepool {
25         // ....
26     }
27 }
28@end


* 在第22行重载了main方法,等会就把下载图片的代码写到这个方法中

* 如果这个DownloadOperation是在异步线程中执行操作,也就是说main方法在异步线程调用,那么将无法访问主线程的自动释放池,所以在第24行创建了一个属于当前线程的自动释放池

 

2.正确响应取消事件

* 默认情况下,一个NSOperation开始执行之后,会一直执行任务到结束,就比如上面的DownloadOperation,默认会执行完main方法中的所有代码。

* NSOperation提供了一个cancel方法,可以取消当前的操作。

* 如果是自定义NSOperation的话,需要手动处理这个取消事件。比如,一旦调用了cancel方法,应该马上终止main方法的执行,并及时回收一些资源。

* 处理取消事件的具体做法是:在main方法中定期地调用isCancelled方法检测操作是否已经被取消,也就是说是否调用了cancel方法,如果返回YES,表示已取消,则立即让main方法返回。

* 以下地方可能需要调用isCancelled方法:

在执行任何实际的工作之前,也就是在main方法的开头。因为取消可能发生在任何时候,甚至在operation执行之前。

执行了一段耗时的操作之后也需要检测操作是否已经被取消

 1 

- (void)main {
 2     // 新建一个自动释放池,如果是异步执行操作,那么将无法访问到主线程的自动释放池
 3    @autoreleasepool {
 4        if (self.isCancelled) return;
 5        
 6        // 获取图片数据
 7        NSURL *url = [NSURL URLWithString:self.imageUrl];
 8        NSData *imageData = [NSData dataWithContentsOfURL:url];
 9        
10         if(self.isCancelled) {
11             url = nil;
12             imageData = nil;
13             return;
14         }
15        
16         // 初始化图片
17         UIImage *image = [UIImageimageWithData:imageData];
18        
19         if(self.isCancelled) {
20             image = nil;
21             return;
22         }
23        
24         if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]){
25             // 把图片数据传回到主线程
26             [(NSObject *)self.delegateperformSelectorOnMainThread:@selector(downloadFinishWithImage:)withObject:image waitUntilDone:NO];
27         }
28     }
29 }

* 在第4main方法的开头就先判断operation有没有被取消。如果被取消了,那就没有必要往下执行了

* 经过第8行下载图片后,在第10行也需要判断操作有没有被取消

* 总之,执行了一段比较耗时的操作之后,都需要判断操作有没有被取消

* 图片下载完毕后,在第26行将图片数据传递给了代理(delegate)对象

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值