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

来源:http://www.cnblogs.com/shorfng/archive/2016/03/28/5327918.html
-Advertisement-
Play Games

最近在學慣用 Markdown 寫筆記呢,感覺還不錯的樣子,排版挺好看的,再也不用擔心被管理員因為排版問題把我的博客從首頁移除了 !! 1.0 NSOperation 的作用 使用 NSOperation 的目的就是為了讓開發人員不再關心線程 配合使用 NSOperation(任務) 和 NSOpe ...


最近在學慣用 Markdown 寫筆記呢,感覺還不錯的樣子,排版挺好看的,再也不用擔心被管理員因為排版問題把我的博客從首頁移除了 - -!!


1.0 NSOperation 的作用

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

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

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

(1)先將需要執行的操作封裝到一個NSOperation對象中

(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中,才會非同步執行操作

代碼示例:

#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/

如有疑問,請發送郵件至 [email protected]聯繫我。



本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • J2ObjC 是一個Google開發的開源工具,用於將Java代碼轉換為Objective-C代碼。其目的是為了能在iOS平臺上重用Android平臺、web伺服器端的Java代碼。伺服器端代碼的轉換由 GWT 完成。J2ObjC並不轉換UI代碼,這部分需要針對不同平臺分別開發。 我們在2012年發 ...
  • public class NetStateUtils { /** * 對網路連接狀態進行判斷 * * @return true, 可用; false, 不可用 */ public static boolean isNetworkConnected(Context context) { if (con ...
  • 相關的第三方類庫大家可以去github上下載 1.NSJSONSerialization 具體代碼如下 : 2.JSONKit 這是需要導入第三方類庫 3.SBJson 同樣需要導入第三方類庫 4.TouchJson 第三方類庫 ...
  • 文件處理: 常用操作: 增加: 刪除: 修改: 讀取: sd卡處理相關: 獲取: ...
  • 記憶體溢出就是軟體運行需要的記憶體,超出了java虛擬機給他分配的可用的最大記憶體 記憶體泄露就是在緩存圖片文字等等的時候,沒有關閉流所導致的記憶體泄露 ...
  • 當工作線程給主線程發送消息時,因為主線程是有looper的,所以不需要初始化looper,註意給誰發消息就關聯誰的handler,此時用的就是主線程的handler handler會把消息發送到MessageQueue隊列中,looper會不斷的去遍歷MessageQueue隊列,當一有消息時就會回 ...
  • 最終的演示如下 這次是用多線程進行圖片的下載與存儲,而且考慮到下載失敗,占點陣圖片的問題(第一張就是下載失敗的圖片) 閑話少說,上代碼吧,因為有一部分和上次的一樣,所以這裡只上傳不一樣的 依舊都是在ViewController.m中 1. 前兩個和前面的一致 operations使用來存儲下載圖片的線 ...
  • 前言 GCD 全稱 Grand Central DisPath NSOperation便是基於GCD的封裝 基礎知識 1.GCD的優勢 (1)為多核的並行運算提出瞭解決方案 (2)GCD會自動利用更多的CPU內核 比和雙核 四核 (3).GCD自動管理線程的生命周期(創建線程 調度任務 銷毀線程) ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...