4.4 多線程進階篇<下>(NSOperation)

本文並不是最終版本,若有更新或更正會第一時間置頂,聯繫方式詳見文末

若是以爲本文內容過長,請前往本人"簡書"

本文源碼 Demo 詳見 Github
https://github.com/shorfng/iOS-4.0-multithreading.gitgit


1.0 NSOperation 的做用

使用 NSOperation 的目的就是爲了讓開發人員再也不關心線程github

  • 配合使用 NSOperation(任務) 和 NSOperationQueue(隊列) 也能實現多線程編程

NSOperation 和 NSOperationQueue 實現多線程的具體步驟:

(1)先將須要執行的操做封裝到一個NSOperation對象中web

(2)而後將NSOperation對象添加到NSOperationQueue中編程

(3)系統會自動將NSOperationQueue中的NSOperation取出來緩存

(4)將取出的NSOperation封裝的操做放到一條新線程中執行網絡

使用NSOperation子類的方式有3種:

NSOperation是個抽象類,並不具有封裝操做的能力,必須使用它的子類多線程

  1. NSInvocationOperation
  • NSBlockOperation
  • 自定義子類繼承NSOperation,實現內部相應的方法

2.0 NSInvocationOperation

//建立NSInvocationOperation對象
- (id)initWithTarget:(id)target selector:(SEL)sel object:(id)arg;

//調用start方法開始執行操做,一旦執行操做,就會調用target的sel方法
- (void)start;

注意:併發

  • 默認狀況下,操做對象在主線程中執行
  • 調用了start方法後並不會開一條新線程去執行操做,只有添加到隊列中才會開啓新的線程
  • 即默認狀況下,若是操做沒有放到隊列中queue中,都是同步執行。
  • 只有將NSOperation放到一個NSOperationQueue中,纔會異步執行操做

代碼示例:app

#import "ViewController.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

  //建立操做對象,封裝要執行的任務
  NSInvocationOperation *op =
      [[NSInvocationOperation alloc] initWithTarget:self
                                           selector:@selector(run)
                                             object:nil];
  //執行操做
  [op start];
}

- (void)run {
  NSLog(@"------%@", [NSThread currentThread]);
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}
@end

打印結果:框架

NSInvocationOperation[862:29437] ------<NSThread: 0x7f9cea507920>{number = 1, name = main}

3.0 NSBlockOperation

//建立 NSBlockOperation 操做對象
+ (id)blockOperationWithBlock:(void (^)(void))block;

// 添加操做
- (void)addExecutionBlock:(void (^)(void))block;

注意:只要NSBlockOperation封裝的操做數 > 1,就會異步執行操做


代碼示例:

#import "ViewController.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

  // 1.建立 NSBlockOperation 操做對象
  NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
    // 在主線程
    NSLog(@"下載1------%@", [NSThread currentThread]);
  }];

  // 2.添加操做(額外的任務)(在子線程執行)
  [op addExecutionBlock:^{
    NSLog(@"下載2------%@", [NSThread currentThread]);
  }];

  [op addExecutionBlock:^{
    
  [op addExecutionBlock:^{
    NSLog(@"下載2------%@", [NSThread currentThread]);
  }];
  [op addExecutionBlock:^{
    NSLog(@"下載3------%@", [NSThread currentThread]);
  }];
  [op addExecutionBlock:^{
    NSLog(@"下載4------%@", [NSThread currentThread]);
  }];
  // 3.開啓執行操做
  [op start];
}
- (void)run {
  NSLog(@"------%@", [NSThread currentThread]);
}
- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}
@end

打印結果:

NSBlockOperation[1013:37922] 下載1------<NSThread: 0x7feea1c05460>{number = 1, name = main}
NSBlockOperation[1013:37952] 下載2------<NSThread: 0x7feea1f0b790>{number = 2, name = (null)}
NSBlockOperation[1013:37955] 下載3------<NSThread: 0x7feea1c0f8a0>{number = 3, name = (null)}
NSBlockOperation[1013:37951] 下載4------<NSThread: 0x7feea1e0b520>{number = 4, name = (null)}

4.0 NSOperationQueue

NSOperationQueue的做用:添加操做到NSOperationQueue中,自動執行操做,自動開啓線程

  • NSOperation 能夠調用 start 方法來執行任務,但默認是同步執行的
  • 若是將 NSOperation 添加到 NSOperationQueue(操做隊列)中,系統會自動異步執行NSOperation中的操做

添加操做到 NSOperationQueue 中:2種方式

- (void)addOperation:(NSOperation *)op;

- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);

代碼示例:

#import "ViewController.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  [self operationQueue2];
}

#pragma mark - 把操做添加到隊列中,方式1:addOperation
- (void)operationQueue1 {
  // 1.建立隊列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  // 2.1 方式1:建立操做(任務)NSInvocationOperation ,封裝操做
  NSInvocationOperation *op1 =
      [[NSInvocationOperation alloc] initWithTarget:self
                                           selector:@selector(download1)
                                             object:nil];

  // 2.2 方式2:建立NSBlockOperation ,封裝操做
  NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
  }];

  // 添加操做
  [op2 addExecutionBlock:^{
    NSLog(@"download3 --- %@", [NSThread currentThread]);
  }];

  // 3.把操做(任務)添加到隊列中,並自動調用 start 方法
  [queue addOperation:op1];
  [queue addOperation:op2];
}

- (void)download1 {
  NSLog(@"download1 --- %@", [NSThread currentThread]);
}

#pragma mark - 把操做添加到隊列中,方式2:addOperationWithBlock
- (void)operationQueue2 {
  // 1.建立隊列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  // 2.添加操做到隊列中
  [queue addOperationWithBlock:^{
    NSLog(@"download1 --- %@", [NSThread currentThread]);
  }];
  [queue addOperationWithBlock:^{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
  }];
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

@end

打印結果:

NSOperationQueue[1658:89517] download2 --- <NSThread: 0x7f88a9e059d0>{number = 3, name = (null)}
 NSOperationQueue[1658:89518] download1 --- <NSThread: 0x7f88a9d901f0>{number = 2, name = (null)}
 NSOperationQueue[1658:89521] download3 --- <NSThread: 0x7f88a9d15d30>{number = 4, name = (null)}

 NSOperationQueue[1704:92509] download2 --- <NSThread: 0x7fd318f06540>{number = 2, name = (null)}
 NSOperationQueue[1704:92513] download1 --- <NSThread: 0x7fd318d0e460>{number = 3, name = (null)}

提示:隊列的取出是有順序的,與打印結果並不矛盾。這就比如,選手A,BC雖然起跑的順序是先A,後B,而後C,可是到達終點的順序卻不必定是A,B在前,C在後。

4.1 最大併發數

併發數:同時執⾏行的任務數 好比,同時開3個線程執行3個任務,併發數就是3

最大併發數:同一時間最多隻能執行的任務的個數

最⼤併發數的相關⽅方法:

//最大併發數,默認爲-1
@property NSInteger maxConcurrentOperationCount;

- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

說明:

  • 若是沒有設置最大併發數,那麼併發的個數是由系統內存和CPU決定的,內存多就開多一點,內存少就開少一點。
  • 最⼤併發數的值並不表明線程的個數,僅僅表明線程的ID。
  • 最大併發數不要亂寫(5之內),不要開太多,通常以2~3爲宜,由於雖然任務是在子線程進行處理的,可是cpu處理這些過多的子線程可能會影響UI,讓UI變卡。
  • 最大併發數的值爲1,就變成了串行隊列

代碼示例:

#import "ViewController.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // 1.建立隊列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  // 2.設置最大併發操做數(大併發操做數 = 1,就變成了串行隊列)
  queue.maxConcurrentOperationCount = 2;

  // 3.添加操做
  [queue addOperationWithBlock:^{
    NSLog(@"download1 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:0.01];
  }];
  [queue addOperationWithBlock:^{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:0.01];
  }];
  [queue addOperationWithBlock:^{
    NSLog(@"download3 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:0.01];
  }];
  [queue addOperationWithBlock:^{
    NSLog(@"download4 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:0.01];
  }];
  [queue addOperationWithBlock:^{
    NSLog(@"download5 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:0.01];
  }];
  [queue addOperationWithBlock:^{
    NSLog(@"download6 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:0.01];
  }];
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

@end

打印結果:

最大併發數[1909:113433] download2 --- <NSThread: 0x7ffef240ba70>{number = 3, name = (null)}
最大併發數[1909:113432] download1 --- <NSThread: 0x7ffef24aee50>{number = 2, name = (null)}
最大併發數[1909:113432] download4 --- <NSThread: 0x7ffef24aee50>{number = 2, name = (null)}
最大併發數[1909:113431] download3 --- <NSThread: 0x7ffef251aa80>{number = 4, name = (null)}
最大併發數[1909:113428] download5 --- <NSThread: 0x7ffef2603d90>{number = 5, name = (null)}
最大併發數[1909:113432] download6 --- <NSThread: 0x7ffef24aee50>{number = 2, name = (null)}

4.2 隊列的暫停和恢復

隊列的暫停:當前任務結束後,暫停執行下一個任務,而非當前任務

//暫停和恢復隊列(YES表明暫停隊列,NO表明恢復隊列)
- (void)setSuspended:(BOOL)b;

//當前狀態
- (BOOL)isSuspended;

暫停和恢復的使用場合:

在tableview界面,開線程下載遠程的網絡界面,對UI會有影響,使用戶體驗變差。那麼這種狀況,就能夠設置在用戶操做UI(如滾動屏幕)的時候,暫停隊列(不是取消隊列),中止滾動的時候,恢復隊列。


代碼示例:

#import "ViewController.h"

@interface ViewController ()
@property(nonatomic, strong) NSOperationQueue *queue; //隊列
@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  // 1.建立隊列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  // 2.設置最大併發操做數(大併發操做數 = 1,就變成了串行隊列)
  queue.maxConcurrentOperationCount = 1;

  // 3.添加操做
  [queue addOperationWithBlock:^{
    NSLog(@"download1 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:3];
  }];

  [queue addOperationWithBlock:^{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:3];
  }];
  [queue addOperationWithBlock:^{
    NSLog(@"download3 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:3];
  }];
  [queue addOperationWithBlock:^{
    NSLog(@"download4 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:3];
  }];

  self.queue = queue;
}

#pragma mark - 暫停和恢復
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  if (self.queue.isSuspended) {
    self.queue.suspended = NO; // 恢復隊列,繼續執行
  } else {
    self.queue.suspended = YES; // 暫停(掛起)隊列,暫停執行
  }
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

@end

打印結果:

隊列的暫停和恢復[2650:156206] download1 --- <NSThread: 0x7fd689f552b0>{number = 3, name = (null)}
隊列的暫停和恢復[2650:156205] download2 --- <NSThread: 0x7fd689c02e70>{number = 2, name = (null)}
隊列的暫停和恢復[2650:156206] download3 --- <NSThread: 0x7fd689f552b0>{number = 3, name = (null)}
隊列的暫停和恢復[2650:156385] download4 --- <NSThread: 0x7fd689ea11c0>{number = 4, name = (null)}

4.3 隊列的取消

取消隊列的全部操做:相等於調用了全部 NSOperation 的 -(void)cancel 方法,
當前任務結束後,取消執行下面的全部任務,而非當前任務

// 也可調用NSOperation的 -(void)cancel 方法取消單個操做
- (void)cancelAllOperations;

代碼示例:

#import "ViewController.h"

@interface ViewController ()
@property(nonatomic, strong) NSOperationQueue *queue; //隊列
@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  // 1.建立隊列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  // 2.設置最大併發操做數(大併發操做數 = 1,就變成了串行隊列)
  queue.maxConcurrentOperationCount = 1;

  // 3.添加操做
  [queue addOperationWithBlock:^{
    NSLog(@"download1 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:3];
  }];

  [queue addOperationWithBlock:^{
    NSLog(@"download2 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:3];
  }];
  [queue addOperationWithBlock:^{
    NSLog(@"download3 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:3];
  }];
  [queue addOperationWithBlock:^{
    NSLog(@"download4 --- %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:3];
  }];

  self.queue = queue;
}

#pragma mark - 取消隊列的全部操做
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // 取消隊列的全部操做(相等於調用了全部NSOperation的-(void)cancel方法)
  [self.queue cancelAllOperations];
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

@end

打印結果:

隊列的取消[3041:167756] download1 --- <NSThread: 0x7fcc09543b40>{number = 3, name = (null)}
隊列的取消[3041:167749] download2 --- <NSThread: 0x7fcc094505f0>{number = 2, name = (null)}

4.4 操做優先級

設置NSOperation在queue中的優先級,能夠改變操做的執行優先級:

@property NSOperationQueuePriority queuePriority;

- (void)setQueuePriority:(NSOperationQueuePriority)p;

優先級的取值:優先級高的任務,調用的概率會更大

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
NSOperationQueuePriorityVeryLow = -8L,
NSOperationQueuePriorityLow = -4L,
NSOperationQueuePriorityNormal = 0,
NSOperationQueuePriorityHigh = 4,
NSOperationQueuePriorityVeryHigh = 8
};

4.5 操做依賴

NSOperation之間能夠設置依賴來保證執行順序:不能循環依賴(不能A依賴於B,B又依賴於A)

// 操做B依賴於操做A(必定要讓操做A執行完後,才能執行操做B)
[operationB addDependency:operationA];

能夠在不一樣queue的NSOperation之間建立依賴關係(跨隊列依賴):

注意:

  • 必定要在把操做添加到隊列以前,進行設置操做依賴。
  • 任務添加的順序並不可以決定執行順序,執行的順序取決於依賴。

代碼示例:

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

  NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  //建立對象,封裝操做
  NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download1----%@", [NSThread currentThread]);
  }];
  NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download2----%@", [NSThread currentThread]);
  }];
  NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download3----%@", [NSThread currentThread]);
  }];
  NSBlockOperation *op4 = [NSBlockOperation blockOperationWithBlock:^{
    for (NSInteger i = 0; i < 5; i++) {
      NSLog(@"download4----%@", [NSThread currentThread]);
    }
  }];

  NSBlockOperation *op5 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"download5----%@", [NSThread currentThread]);
  }];
  //操做的監聽
  op5.completionBlock = ^{
    NSLog(@"op5執行完畢---%@", [NSThread currentThread]);
  };

  //設置操做依賴(op4執行完,才執行 op3)
  [op3 addDependency:op1];
  [op3 addDependency:op2];
  [op3 addDependency:op4];

  //把操做添加到隊列中
  [queue addOperation:op1];
  [queue addOperation:op2];
  [queue addOperation:op3];
  [queue addOperation:op4];
  [queue addOperation:op5];
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

@end

打印結果:

操做依賴[4196:150518] download5----<NSThread: 0x7ffa61d177d0>{number = 3, name = (null)}
操做依賴[4196:150506] download1----<NSThread: 0x7ffa61ca6b90>{number = 4, name = (null)}
操做依賴[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
操做依賴[4196:150510] download2----<NSThread: 0x7ffa61f0e800>{number = 5, name = (null)}
操做依賴[4196:150518] op5執行完畢---<NSThread: 0x7ffa61d177d0>{number = 3, name = (null)}
操做依賴[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
操做依賴[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
操做依賴[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
操做依賴[4196:150509] download4----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}
操做依賴[4196:150509] download3----<NSThread: 0x7ffa61f0e470>{number = 2, name = (null)}

操做的監聽

能夠監聽一個操做的執行完畢:

@property (nullable, copy) void (^completionBlock)(void);

- (void)setCompletionBlock:(void (^)(void))block;

代碼詳見4.5 操做依賴 示例代碼

5.0 線程間通訊(圖片下載示例)

#import "ViewController.h"

@interface ViewController ()
@property(weak, nonatomic) IBOutlet UIImageView *imageView;

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  [self test2];
}

#pragma mark - 線程間通訊(圖片合成)
- (void)test1 {
  // 1.隊列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  __block UIImage *image1 = nil;
  // 2.下載圖片1
  NSBlockOperation *download1 = [NSBlockOperation blockOperationWithBlock:^{
    // 圖片的網絡路徑
    NSURL *url =
        [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/"
                             @"8/1/9981681/200910/11/1255259355826.jpg"];
    // 加載圖片
    NSData *data = [NSData dataWithContentsOfURL:url];
    // 生成圖片
    image1 = [UIImage imageWithData:data];
  }];

  __block UIImage *image2 = nil;
  // 3.下載圖片2
  NSBlockOperation *download2 = [NSBlockOperation blockOperationWithBlock:^{
    // 圖片的網絡路徑
    NSURL *url = [NSURL
        URLWithString:
            @"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
    // 加載圖片
    NSData *data = [NSData dataWithContentsOfURL:url];
    // 生成圖片
    image2 = [UIImage imageWithData:data];
  }];

  // 4.合成圖片
  NSBlockOperation *combine = [NSBlockOperation blockOperationWithBlock:^{
    // 開啓新的圖形上下文
    UIGraphicsBeginImageContext(CGSizeMake(100, 100));

    // 繪製圖片1
    [image1 drawInRect:CGRectMake(0, 0, 50, 100)];
    image1 = nil;

    // 繪製圖片2
    [image2 drawInRect:CGRectMake(50, 0, 50, 100)];
    image2 = nil;

    // 取得上下文中的圖片
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

    // 結束上下文
    UIGraphicsEndImageContext();

    // 5.回到主線程
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
      self.imageView.image = image;
    }];
  }];

  // 設置依賴操做
  [combine addDependency:download1];
  [combine addDependency:download2];

  //把操做添加到隊列中
  [queue addOperation:download1];
  [queue addOperation:download2];
  [queue addOperation:combine];
}

#pragma mark - 線程間通訊(圖片下載)
- (void)test2 {
  [[[NSOperationQueue alloc] init] addOperationWithBlock:^{
    // 圖片的網絡路徑
    NSURL *url =
        [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/"
                             @"8/1/9981681/200910/11/1255259355826.jpg"];

    // 加載圖片
    NSData *data = [NSData dataWithContentsOfURL:url];

    // 生成圖片
    UIImage *image = [UIImage imageWithData:data];

    // 回到主線程
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
      self.imageView.image = image;
    }];
  }];
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

@end

6.0 自定義NSOperation

自定義NSOperation的步驟很簡單:

  • 重寫- (void)main方法,在裏面實現想執行的任務

重寫- (void)main方法的注意點:

  • 本身建立自動釋放池(由於若是是異步操做,沒法訪問主線程的自動釋放池)
  • 常常經過- (BOOL)isCancelled方法檢測操做是否被取消,對取消作出響應

ViewController.m

#import "TDOperation.h"
#import "ViewController.h"

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

  // 1.建立隊列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  // 2.建立自定義 TDGOperation
  TDOperation *op = [[TDOperation alloc] init];

  // 3.把操做(任務)添加到隊列中,並自動調用 start 方法
  [queue addOperation:op];
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}
@end

TDOperation.h(繼承自:NSOperation)

#import <Foundation/Foundation.h>

@interface TDOperation : NSOperation
@end

TDOperation.m

#import "TDOperation.h"

@implementation TDOperation
//須要執行的任務
- (void)main {
  for (NSInteger i = 0; i < 3; i++) {
    NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人爲的判斷是否執行取消操做,若是執行取消操做,就直接 return 不往下執行
  if (self.isCancelled)
    return;

  for (NSInteger i = 0; i < 3; i++) {
    NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人爲的判斷是否執行取消操做,若是執行取消操做,就直接 return 不往下執行
  if (self.isCancelled)
    return;

  for (NSInteger i = 0; i < 3; i++) {
    NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人爲的判斷是否執行取消操做,若是執行取消操做,就直接 return 不往下執行
  if (self.isCancelled)
    return;
}
@end

運行結果:

自定義NSOperation[1567:84075] download1 -0-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定義NSOperation[1567:84075] download1 -1-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定義NSOperation[1567:84075] download1 -2-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定義NSOperation[1567:84075] download2 -0-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定義NSOperation[1567:84075] download2 -1-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定義NSOperation[1567:84075] download2 -2-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定義NSOperation[1567:84075] download3 -0-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定義NSOperation[1567:84075] download3 -1-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}
自定義NSOperation[1567:84075] download3 -2-- <NSThread: 0x7fb6ba4109b0>{number = 2, name = (null)}

6.1 自定義NSOperation隊列的取消操做

代碼示例:

ViewController.m

#import "TDOperation.h"
#import "ViewController.h"

@interface ViewController ()
@property(nonatomic, strong) NSOperationQueue *queue; //隊列
@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  // 1.建立隊列
  NSOperationQueue *queue = [[NSOperationQueue alloc] init];

  // 2.設置最大併發操做數(大併發操做數 = 1,就變成了串行隊列)
  queue.maxConcurrentOperationCount = 2;

  // 3.添加操做 - 自定義 NSOperation
  [queue addOperation:[[TDOperation alloc] init]];

  self.queue = queue;
}

#pragma mark - 取消隊列的全部操做
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
  // 取消隊列的全部操做(相等於調用了全部NSOperation的-(void)cancel方法)
  [self.queue cancelAllOperations];
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

@end

TDOperation.h

#import <Foundation/Foundation.h>

@interface TDOperation : NSOperation

@end

TDOperation.m

#import "TDOperation.h"

@implementation TDOperation
//須要執行的任務
- (void)main {
  for (NSInteger i = 0; i < 1000; i++) {
    NSLog(@"download1 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人爲的判斷是否執行取消操做,若是執行取消操做,就直接 return 不往下執行
  if (self.isCancelled)
    return;

  for (NSInteger i = 0; i < 1000; i++) {
    NSLog(@"download2 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人爲的判斷是否執行取消操做,若是執行取消操做,就直接 return 不往下執行
  if (self.isCancelled)
    return;

  for (NSInteger i = 0; i < 1000; i++) {
    NSLog(@"download3 -%zd-- %@", i, [NSThread currentThread]);
  }
  // 人爲的判斷是否執行取消操做,若是執行取消操做,就直接 return 不往下執行
  if (self.isCancelled)
    return;
}
@end

6.2 多圖下載

沙盒結構:

Documents
 Library
    - Caches
    - Preference
 tmp

自定義NSOperation下載圖片思路 – 有沙盒緩存


代碼示例:

ViewController.m

#import "TDApp.h"
#import "ViewController.h"

@interface ViewController ()
@property(nonatomic, strong) NSArray *apps;                   //全部數據
@property(nonatomic, strong) NSMutableDictionary *imageCache; //內存緩存的圖片
@property(nonatomic, strong) NSOperationQueue *queue;         //隊列對象
@property(nonatomic, strong) NSMutableDictionary *operations; //全部的操做對象

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

#pragma mark - 數據源方法
- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section {
  return self.apps.count;
}

#pragma mark - Cell
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  // 重用標識
  static NSString *ID = @"app";
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

  TDApp *app = self.apps[indexPath.row];

#pragma mark - app 名稱
  cell.textLabel.text = app.name;

#pragma mark - 下載量
  cell.detailTextLabel.text = app.download;

#pragma mark - 圖片
  // 1.先從內存緩存中取出圖片
  UIImage *image = self.imageCache[app.icon];

  // 2.判斷內存中是否有圖片
  if (image) {
    // 2.1 內存中有圖片,直接設置圖片
    cell.imageView.image = image;
  } else {
    // 2.2 內存中沒有圖片,將圖片文件數據寫入沙盒中

    //(1)得到Library/Caches文件夾
    NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(
        NSCachesDirectory, NSUserDomainMask, YES) firstObject];
    //(2)得到文件名
    NSString *filename = [app.icon lastPathComponent];
    //(3)計算出文件的全路徑
    NSString *file = [cachesPath stringByAppendingPathComponent:filename];
    //(4)加載沙盒的文件數據
    NSData *data = [NSData dataWithContentsOfFile:file];

    // 2.3 判斷沙盒中是否有圖片
    if (data) {

      // 有圖片,直接利用沙盒中圖片,設置圖片
      UIImage *image = [UIImage imageWithData:data];
      cell.imageView.image = image;
      // 並將圖片存到字典中
      self.imageCache[app.icon] = image;

    } else {

      // 沒有圖片,先設置一個佔位圖
      cell.imageView.image = [UIImage imageNamed:@"placeholder"];

      // 取出圖片,並判斷這張圖片是否有下載操做
      NSOperation *operation = self.operations[app.icon];
      if (operation == nil) {
        // 若是這張圖片暫時沒有下載操做,則須要建立一個下載操做
        // 下載圖片是耗時操做,放到子線程
        operation = [NSBlockOperation blockOperationWithBlock:^{
          // 下載圖片
          NSData *data =
              [NSData dataWithContentsOfURL:[NSURL URLWithString:app.icon]];
          // 若是數據下載失敗
          if (data == nil) {
            // 下載失敗,移除操做
            [self.operations removeObjectForKey:app.icon];
            return;
          }

            // 下載成功,將圖片放在 image 中
          UIImage *image = [UIImage imageWithData:data];
          // 存到字典中
          self.imageCache[app.icon] = image;

          //回到主線程顯示圖片
          [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [tableView reloadRowsAtIndexPaths:@[ indexPath ]
                             withRowAnimation:UITableViewRowAnimationNone];
          }];

          // 將圖片文件數據寫入沙盒中
          [data writeToFile:file atomically:YES];
          // 下載完畢,移除操做
          [self.operations removeObjectForKey:app.icon];
        }];

        // 添加到隊列中(隊列的操做不須要移除,會自動移除)
        [self.queue addOperation:operation];
        // 並將圖片存到字典中
        self.operations[app.icon] = operation;
      }
    }
  }

  return cell;
}

#pragma mark - 數據懶加載
- (NSArray *)apps {
  if (!_apps) {
    NSArray *dictArray =
        [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
                                             pathForResource:@"apps.plist"
                                                      ofType:nil]];

    NSMutableArray *appArray = [NSMutableArray array];
    for (NSDictionary *dict in dictArray) {
      [appArray addObject:[TDApp appWithDict:dict]];
    }
    _apps = appArray;
  }
  return _apps;
}

#pragma mark - 懶加載
- (NSMutableDictionary *)imageCache {
  if (!_imageCache) {
    _imageCache = [NSMutableDictionary dictionary];
  }
  return _imageCache;
}

#pragma mark - 懶加載
- (NSOperationQueue *)queue {
  if (!_queue) {
    _queue = [[NSOperationQueue alloc] init];
    _queue.maxConcurrentOperationCount = 3;
  }
  return _queue;
}

#pragma mark - 懶加載
- (NSMutableDictionary *)operations {
  if (!_operations) {
    _operations = [NSMutableDictionary dictionary];
  }
  return _operations;
}

- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
  // Dispose of any resources that can be recreated.
}

@end

TDApp.h

#import <Foundation/Foundation.h>

@interface TDApp : NSObject

@property(nonatomic, strong) NSString *icon;     // 圖片
@property(nonatomic, strong) NSString *download; //下載量
@property(nonatomic, strong) NSString *name;     // 名字

+ (instancetype)appWithDict:(NSDictionary *)dict;

@end

TDApp.m

#import "TDApp.h"

@implementation TDApp

+ (instancetype)appWithDict:(NSDictionary *)dict {
  TDApp *app = [[self alloc] init];
  [app setValuesForKeysWithDictionary:dict];
  return app;
}

@end

6.3 多圖下載 - SDWebImage

SDWebImage:

  • iOS中著名的網絡圖片處理框架
  • 包含的功能:圖片下載、圖片緩存、下載進度監聽、gif處理等等
  • 框架地址:https://github.com/rs/SDWebImage

  • SDWebImage的圖片緩存週期是:1周


代碼示例:

ViewController.m

#import "TDApp.h"
#import "UIImageView+WebCache.h"
#import "ViewController.h"

@interface ViewController ()
@property(nonatomic, strong) NSArray *apps;                   //全部數據
@property(nonatomic, strong) NSMutableDictionary *imageCache; //內存緩存的圖片
@property(nonatomic, strong) NSOperationQueue *queue;         //隊列對象
@property(nonatomic, strong) NSMutableDictionary *operations; //全部的操做對象

@end

@implementation ViewController

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
}

#pragma mark - 數據源方法
- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section {
  return self.apps.count;
}

#pragma mark - Cell
- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  // 重用標識
  static NSString *ID = @"app";
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];

  TDApp *app = self.apps[indexPath.row];

#pragma mark - app 名稱
  cell.textLabel.text = app.name;

#pragma mark - 下載量
  cell.detailTextLabel.text = app.download;

#pragma mark - 圖片
  // expectedSize: 圖片的總字節數  receivedSize: 已經接收的圖片字節數
  [cell.imageView sd_setImageWithURL:[NSURL URLWithString:app.icon]
      placeholderImage:[UIImage imageNamed:@"placeholder"]
      options:0 // 0 表示什麼都不作
      progress:^(NSInteger receivedSize, NSInteger expectedSize) {

        NSLog(@"下載進度:%f", 1.0 * receivedSize / expectedSize);
      }
      completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType,
                  NSURL *imageURL) {
        NSLog(@"下載完圖片");
      }];
  return cell;
}

#pragma mark - 數據懶加載
- (NSArray *)apps {
  if (!_apps) {
    NSArray *dictArray =
        [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle]
                                             pathForResource:@"apps.plist"
                                                      ofType:nil]];

    NSMutableArray *appArray = [NSMutableArray array];
    for (NSDictionary *dict in dictArray) {
      [appArray addObject:[TDApp appWithDict:dict]];
    }
    _apps = appArray;
  }
  return _apps;
}

#pragma mark - 懶加載
- (NSMutableDictionary *)imageCache {
  if (!_imageCache) {
    _imageCache = [NSMutableDictionary dictionary];
  }
  return _imageCache;
}

#pragma mark - 懶加載
- (NSOperationQueue *)queue {
  if (!_queue) {
    _queue = [[NSOperationQueue alloc] init];
    _queue.maxConcurrentOperationCount = 3;
  }
  return _queue;
}

#pragma mark - 懶加載
- (NSMutableDictionary *)operations {
  if (!_operations) {
    _operations = [NSMutableDictionary dictionary];
  }
  return _operations;
}

#pragma mark - 設置控制器的內存警告
- (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];

  self.imageCache = nil;
  self.operations = nil;
  [self.queue cancelAllOperations];
}

@end

TDApp.h

#import <Foundation/Foundation.h>

@interface TDApp : NSObject

@property(nonatomic, strong) NSString *icon;     // 圖片
@property(nonatomic, strong) NSString *download; //下載量
@property(nonatomic, strong) NSString *name;     // 名字

+ (instancetype)appWithDict:(NSDictionary *)dict;

@end

TDApp.m

#import "TDApp.h"

@implementation TDApp

+ (instancetype)appWithDict:(NSDictionary *)dict {
  TDApp *app = [[self alloc] init];
  [app setValuesForKeysWithDictionary:dict];
  return app;
}

@end

7.0【區別】GCD & NSOperationQueue 隊列類型的建立方式

GCD 隊列類型的建立方式:

(1)併發隊列:手動建立、全局

(2)串行隊列:手動建立、主隊列


NSOperationQueue的隊列類型的建立方法:

(1)主隊列:[NSOperationQueue mainQueue]

  • 凡是添加到主隊列中的任務(NSOperation),都會放到主線程中執行

(2)其餘隊列(同時包含了串行、併發功能):[NSOperationQueue alloc]init]

  • 添加到這種隊列中的任務(NSOperation),就會自動放到子線程中執行




注:關於SDWebImage框架的詳解會另外再寫博客




若是你以爲本篇文章對你有所幫助,請點擊文章末尾右下角「推薦」,^_^

做者:藍田(Loto)

出處:http://www.cnblogs.com/shorfng/

若有疑問,請在下方 評論區回覆 OR 發送郵件shorfng@126.com聯繫我。 本文版權歸做者和本網站共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。

相關文章
相關標籤/搜索