***線程,GCD,runloop(2)

第三篇:多線程編程的多種方式

iOS執(zhí)行多線程編程常用的有以下幾種方式

  • NSThread
  • GCD
  • NSOperation
    本文簡(jiǎn)單介紹NSThread和詳細(xì)介紹NSOperation

3.1 NSThread

這套方案是經(jīng)過(guò)蘋(píng)果封裝后的,并且完全面向?qū)ο蟮摹K阅憧梢灾苯硬倏鼐€程對(duì)象,非常直觀和方便。但是,它的生命周期還是需要我們手動(dòng)管理,所以這套方案也是偶爾用用,比如 [NSThread currentThread],它可以獲取當(dāng)前線程類(lèi),你就可以知道當(dāng)前線程的各種屬性,用于調(diào)試十分方便。下面來(lái)看看它的一些用法。
** 創(chuàng)建并啟動(dòng) **

先創(chuàng)建線程類(lèi),再啟動(dòng)

// 創(chuàng)建
  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];

  // 啟動(dòng)
  [thread start];

創(chuàng)建并自動(dòng)啟動(dòng)

[NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];

使用 NSObject 的方法創(chuàng)建并自動(dòng)啟動(dòng)

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

** 其他方法 **
除了創(chuàng)建啟動(dòng)外,NSThread 還以很多方法,下面我列舉一些常見(jiàn)的方法,當(dāng)然我列舉的并不完整,更多方法大家可以去類(lèi)的定義里去看。

//取消線程
- (void)cancel;

//啟動(dòng)線程
- (void)start;

//判斷某個(gè)線程的狀態(tài)的屬性
@property (readonly, getter=isExecuting) BOOL executing;
@property (readonly, getter=isFinished) BOOL finished;
@property (readonly, getter=isCancelled) BOOL cancelled;

//設(shè)置和獲取線程名字
-(void)setName:(NSString *)n;
-(NSString *)name;

//獲取當(dāng)前線程信息
+ (NSThread *)currentThread;

//獲取主線程信息
+ (NSThread *)mainThread;

//使當(dāng)前線程暫停一段時(shí)間,或者暫停到某個(gè)時(shí)刻
+ (void)sleepForTimeInterval:(NSTimeInterval)time;
+ (void)sleepUntilDate:(NSDate *)date;

** GCD見(jiàn)上篇 **

3.2 NSOperation和NSOperationQueue

NSOperation 是蘋(píng)果公司對(duì) GCD 的封裝,完全面向?qū)ο螅允褂闷饋?lái)更好理解。 大家可以看到 NSOperation 和 NSOperationQueue 分別對(duì)應(yīng) GCD 的 任務(wù) 和 隊(duì)列 。操作步驟也很好理解:

  1. 將要執(zhí)行的任務(wù)封裝到一個(gè) NSOperation 對(duì)象中。
  2. 將此任務(wù)添加到一個(gè) NSOperationQueue 對(duì)象中。
    然后系統(tǒng)就會(huì)自動(dòng)在執(zhí)行任務(wù)。至于同步還是異步、串行還是并行請(qǐng)繼續(xù)往下看:

** 添加任務(wù) **
值得說(shuō)明的是,** NSOperation 只是一個(gè)抽象類(lèi),所以不能封裝任務(wù) **。但它有 2 個(gè)子類(lèi)用于封裝任務(wù)。分別是:NSInvocationOperation 和 NSBlockOperation 。創(chuàng)建一個(gè) Operation 后,需要調(diào)用 start 方法來(lái)啟動(dòng)任務(wù),它會(huì) 默認(rèn)在當(dāng)前隊(duì)列同步執(zhí)行。當(dāng)然你也可以在中途取消一個(gè)任務(wù),只需要調(diào)用其 cancel 方法即可。

  • NSInvocationOperation : 需要傳入一個(gè)方法名。
//1.創(chuàng)建NSInvocationOperation對(duì)象
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

  //2.開(kāi)始執(zhí)行
  [operation start];
  • NSBlockOperation
//1.創(chuàng)建NSBlockOperation對(duì)象
  NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"%@", [NSThread currentThread]);
  }];

  //2.開(kāi)始任務(wù)
  [operation start];

之前說(shuō)過(guò)這樣的任務(wù),默認(rèn)會(huì)在當(dāng)前線程執(zhí)行。但是 NSBlockOperation 還有一個(gè)方法:addExecutionBlock: ,通過(guò)這個(gè)方法可以給 Operation 添加多個(gè)執(zhí)行 Block。這樣 Operation 中的任務(wù) 會(huì)并發(fā)執(zhí)行,它會(huì) 在主線程和其它的多個(gè)線程 執(zhí)行這些任務(wù),注意下面的打印結(jié)果:

//1.創(chuàng)建NSBlockOperation對(duì)象
      NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
          NSLog(@"%@", [NSThread currentThread]);
      }];

      //添加多個(gè)Block
      for (NSInteger i = 0; i < 5; i++) {
          [operation addExecutionBlock:^{
              NSLog(@"第%ld次:%@", i, [NSThread currentThread]);
          }];
      }

      //2.開(kāi)始任務(wù)
      [operation start];

打印結(jié)果
2015-07-28 17:50:16.585 test[17527:4095467] 第2次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

2015-07-28 17:50:16.585 test[17527:4095666] 第1次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}

2015-07-28 17:50:16.585 test[17527:4095665] <NSThread: 0x7ff5c961b610>{number = 3, name = (null)}

2015-07-28 17:50:16.585 test[17527:4095662] 第0次 - <NSThread: 0x7ff5c948d310>{number = 2, name = (null)}

2015-07-28 17:50:16.586 test[17527:4095666] 第3次 - <NSThread: 0x7ff5c972caf0>{number = 4, name = (null)}

2015-07-28 17:50:16.586 test[17527:4095467] 第4次 - <NSThread: 0x7ff5c9701910>{number = 1, name = main}

** NOTE:** addExecutionBlock 方法必須在 start() 方法之前執(zhí)行,否則就會(huì)報(bào)錯(cuò):

‘*** -[NSBlockOperation addExecutionBlock:]: blocks cannot be added after the operation has started executing or finished'

NOTE:大家可能發(fā)現(xiàn)了一個(gè)問(wèn)題,為什么我在 Swift 里打印輸出使用 NSLog() 而不是 println() 呢?原因是使用 print() / println() 輸出的話,它會(huì)簡(jiǎn)單地使用 流(stream) 的概念,學(xué)過(guò) C++ 的都知道。它會(huì)把需要輸出的每個(gè)字符一個(gè)一個(gè)的輸出到控制臺(tái)。普通使用并沒(méi)有問(wèn)題,可是當(dāng)多線程同步輸出的時(shí)候問(wèn)題就來(lái)了,由于很多 println() 同時(shí)打印,就會(huì)導(dǎo)致控制臺(tái)上的字符混亂的堆在一起,而NSLog() 就沒(méi)有這個(gè)問(wèn)題。到底是什么樣子的呢?你可以把上面 NSLog() 改為 println() ,然后一試便知。 更多 NSLog() 與 println() 的區(qū)別看這里

  • 自定義Operation

1.創(chuàng)建自定義Operation類(lèi),繼承自NSOperation
2.重寫(xiě)自定義Operation的main方法
重寫(xiě)- (void)main方法,在里面實(shí)現(xiàn)想執(zhí)行的任務(wù)
創(chuàng)建自動(dòng)釋放池(因?yàn)槿绻钱惒讲僮?,無(wú)法訪問(wèn)主線程的自動(dòng)釋放池),新版本已經(jīng)不再需要手動(dòng)創(chuàng)建自動(dòng)釋放池
3.通過(guò) "- (BOOL)isCancelled" 方法檢測(cè)操作是否被取消,對(duì)取消做出響應(yīng)
之前提到過(guò)NSOperation只能對(duì)還未執(zhí)行的任務(wù)做取消操作,是由于系統(tǒng)封裝的方法內(nèi)部我們無(wú)法訪問(wèn),而自定義Operation中,我們需要重寫(xiě)main方法,在這里檢測(cè)到isCancelled屬性為YES時(shí),直接返回,不執(zhí)行后面的操作即可取消正在執(zhí)行的操作,雖然能夠?qū)崿F(xiàn)取消正在執(zhí)行的操作,但并不是立刻停止的,而是在調(diào)用了 operation 的 cancel 方法之后的下一個(gè) isCancelled 的檢查點(diǎn)取消的
4.在controller中調(diào)用start方法,或者添加到隊(duì)列, main方法會(huì)被調(diào)用

通過(guò)自定義Operation, 模擬異步下載圖片
1.自定義DownloadOperation類(lèi),繼承自NSOperation
2.聲明屬性

// url地址
@property (nonatomic,copy) NSString *urlString;
// 完成回調(diào)
@property (nonatomic,copy) void(^completeHandler)(UIImage *img);

3.在DownloadOperation類(lèi)中重寫(xiě)main方法:

#import "DownloadOperation.h"

@implementation DownloadOperation

- (void)main{

    //@autoreleasepool {
    // 更新后不再需要手動(dòng)創(chuàng)建,系統(tǒng)內(nèi)部做過(guò)優(yōu)化
    //}
    NSAssert(self.completeHandler != nil, @"completeHandler == nil");

    // 下載圖片
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:self.urlString]];
    UIImage *image = [UIImage imageWithData:data];

    // 取消操作
    if (self.isCancelled) {
        NSLog(@"取消操作");
        return;
    }
    // 返回主線程刷新UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

        self.completeHandler(image);
    }];
}

@end

4.使用:

#import "ViewController.h"
#import "DownloadOperation.h"


@interface ViewController ()

@end

@implementation ViewController{

    NSOperationQueue *_queue;
    UIImageView *_imageView;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    _queue = [[NSOperationQueue alloc] init];
    _imageView = [[UIImageView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    _imageView.contentMode = UIViewContentModeCenter;
    [self.view addSubview:_imageView];

    // 1.創(chuàng)建自定義的Operation對(duì)象 (下載圖片操作)
    DownloadOperation *operation = [[DownloadOperation alloc] init];

    operation.urlString = @"http://t1.mmonly.cc/mmonly/2014/201407/129/7.jpg";
    [operation setCompleteHandler:^(UIImage *img) {

        // 更新UI
        _imageView.image = img;
    }];

    // 2.把操作添加到隊(duì)列中
    [_queue addOperation:operation];

}


@end

示例代碼中先通過(guò)alloc]init實(shí)例化了操作對(duì)象,再通過(guò)屬性完成參數(shù)傳遞和下載圖片的回調(diào)
在使用系統(tǒng)為我們提供的NSOperation子類(lèi)中,系統(tǒng)提供了類(lèi)方法直接使用,所以為了方便使用,同樣可以自定義一個(gè)類(lèi)方法,完成參數(shù)傳遞和回調(diào), 結(jié)合操作的取消一同進(jìn)行演示

代碼優(yōu)化(自定義類(lèi)方法):

.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface DownloadOperation : NSOperation

// url地址
@property (nonatomic,copy) NSString *urlString;
// 完成回調(diào)
@property (nonatomic,copy) void(^completeHandler)(UIImage *img);

// 下載圖片類(lèi)方法
+(instancetype)downloadImageUrlString:(NSString *)urlString completeHandler:(void(^)(UIImage *img))completeHandler;

@end

.m

#import "DownloadOperation.h"


@implementation DownloadOperation

- (void)main{

    NSAssert(self.completeHandler != nil, @"completeHandler == nil");

    // 下載圖片
    NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:self.urlString]];
    UIImage *image = [UIImage imageWithData:data];

    // 取消操作
    if (self.isCancelled) {
        NSLog(@"取消操作");
        return;
    }
    // 返回主線程刷新UI
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{

        self.completeHandler(image);
    }];
}

// 自定義類(lèi)方法下載圖片

+ (instancetype)downloadImageUrlString:(NSString *)urlString completeHandler:(void (^)(UIImage *))completeHandler{

    DownloadOperation *operation = [[DownloadOperation alloc] init];
    operation.urlString = urlString;
    operation.completeHandler = completeHandler;
    return operation;

}

@end

外部調(diào)用

#import "ViewController.h"
#import "DownloadOperation.h"

@interface ViewController ()

@end

@implementation ViewController{

    NSOperationQueue *_queue;
}

- (void)viewDidLoad {
    [super viewDidLoad];

    _queue = [[NSOperationQueue alloc] init];
    _queue.maxConcurrentOperationCount = 3;

    for (int i = 0; i < 20; i ++) {

        // 1.創(chuàng)建自定義的Operation對(duì)象
        DownloadOperation *operation = [DownloadOperation downloadImageUrlString:@"http://t1.mmonly.cc/uploads/tu/201607/tt/1alqhs1gwxo.jpg" completeHandler:^(UIImage *img) {
            NSLog(@"%d --> (%@)",i,[NSThread currentThread]);
        }];


        // 2.把操作添加到隊(duì)列中
        [_queue addOperation:operation];
    }

}

// 觸摸屏幕時(shí)取消所有操作(包括正在執(zhí)行的操作)
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    [_queue cancelAllOperations];
}

@end
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容