最近在學慣用 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是個抽象類,並不具備封裝操作的能力,必須使用它的子類
- 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處理等等
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]聯繫我。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。