歡迎閱讀iOS探索系列(按序閱讀食用效果更加)git
- iOS探索 alloc流程
- iOS探索 內存對齊&malloc源碼
- iOS探索 isa初始化&指向分析
- iOS探索 類的結構分析
- iOS探索 cache_t分析
- iOS探索 方法的本質和方法查找流程
- iOS探索 動態方法解析和消息轉發機制
- iOS探索 淺嘗輒止dyld加載流程
- iOS探索 類的加載過程
- iOS探索 分類、類拓展的加載過程
- iOS探索 isa面試題分析
- iOS探索 runtime面試題分析
- iOS探索 KVC原理及自定義
- iOS探索 KVO原理及自定義
- iOS探索 多線程原理
- iOS探索 多線程之GCD應用
- iOS探索 多線程之GCD底層分析
- iOS探索 多線程之NSOperation
- iOS探索 多線程面試題分析
- iOS探索 八大鎖分析
與GCD
同樣,NSOperation
也是咱們平常開發中常常用到的多線程技術。本文將會介紹NSOperation
的基本使用、添加依賴、自定義github
NSOperation
是個抽象類,依賴於子類NSInvocationOperation
、NSBlockOperation
去實現面試
下面是開發者文檔上對NSOperation
的一段描述 編程
- (void)test {
// 處理事務
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(handleInvocation:) object:@"Felix"];
// 建立隊列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 操做加入隊列
[queue addOperation:op];
}
- (void)handleInvocation:(id)operation {
NSLog(@"%@ --- %@",op, [NSThread currentThread]);
}
--------------------輸出結果:-------------------
Felix --- <NSThread: 0x6000000422c0>{number = 3, name = (null)}
--------------------輸出結果:-------------------
複製代碼
- (void)test {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"Felix"];
[op start];
}
複製代碼
接下來就會引伸出下面一段錯誤使用代碼緩存
- (void)test {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"Felix"];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:op];
[op start];
}
--------------------錯誤日誌:-------------------
something is trying to start the receiver simultaneously from more than one thread'
--------------------錯誤日誌:-------------------
複製代碼
上述代碼之因此會崩潰,是由於線程生命週期:bash
queue addOperation:op
已經將處理事務的操做任務加入到隊列中,並讓線程運行op start
將已經運行的線程再次運行會形成線程混亂NSInvocationOperation
和NSBlockOperation
二者的區別在於:網絡
target
形式block
形式——函數式編程,業務邏輯代碼可讀性更高- (void)test {
// 初始化添加事務
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任務1————%@",[NSThread currentThread]);
}];
// 添加事務
[bo addExecutionBlock:^{
NSLog(@"任務2————%@",[NSThread currentThread]);
}];
// 回調監聽
bo.completionBlock = ^{
NSLog(@"完成了!!!");
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo];
NSLog(@"事務添加進了NSOperationQueue");
}
--------------------輸出結果:-------------------
事務添加進了NSOperationQueue
任務1————<NSThread: 0x6000032dc1c0>{number = 5, name = (null)}
任務2————<NSThread: 0x6000032a1880>{number = 4, name = (null)}
完成了!!!
--------------------輸出結果:-------------------
複製代碼
NSOperationQueue
是異步執行的,因此任務一
、任務二
的完成順序不肯定多線程
下列代碼能夠證實操做與隊列的執行效果是異步併發
的併發
- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{
NSLog(@"%@---%d", [NSThread currentThread], i);
}];
}
}
--------------------輸出結果:-------------------
<NSThread: 0x600002771600>{number = 3, name = (null)}---0
<NSThread: 0x60000277ac80>{number = 7, name = (null)}---3
<NSThread: 0x600002774840>{number = 6, name = (null)}---2
<NSThread: 0x600002776a80>{number = 8, name = (null)}---4
<NSThread: 0x60000270c540>{number = 5, name = (null)}---1
--------------------輸出結果:-------------------
複製代碼
- (void)test {
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
//sleep(1);
NSLog(@"第一個操做 %d --- %@", i, [NSThread currentThread]);
}
}];
// 設置最高優先級
bo1.qualityOfService = NSQualityOfServiceUserInteractive;
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
NSLog(@"第二個操做 %d --- %@", i, [NSThread currentThread]);
}
}];
// 設置最低優先級
bo2.qualityOfService = NSQualityOfServiceBackground;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo1];
[queue addOperation:bo2];
}
複製代碼
NSOperation
設置優先級只會讓CPU有更高的概率調用,不是說設置高就必定所有先完成異步
sleep
——高優先級的任務一
先於低優先級的任務二
第一個操做 0 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一個操做 1 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一個操做 2 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一個操做 3 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第一個操做 4 --- <NSThread: 0x600002254280>{number = 6, name = (null)}
第二個操做 0 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二個操做 1 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二個操做 2 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二個操做 3 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
第二個操做 4 --- <NSThread: 0x600002240340>{number = 7, name = (null)}
複製代碼
sleep
進行延時——高優先級的任務一
慢於低優先級的任務二
第二個操做 0 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二個操做 1 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二個操做 2 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二個操做 3 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第二個操做 4 --- <NSThread: 0x600002b35840>{number = 7, name = (null)}
第一個操做 0 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一個操做 1 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一個操做 2 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一個操做 3 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
第一個操做 4 --- <NSThread: 0x600002b3c700>{number = 5, name = (null)}
複製代碼
GCD
中使用異步進行網絡請求,而後回到主線程刷新UINSOperation
中也有相似在線程間通信的操做- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"Felix";
[queue addOperationWithBlock:^{
NSLog(@"請求網絡%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
NSLog(@"刷新UI%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]);
}];
}];
}
--------------------輸出結果:-------------------
請求網絡<NSOperationQueue: 0x7ff4a240bae0>{name = 'Felix'}--<NSThread: 0x6000007dcf00>{number = 5, name = (null)}
刷新UI<NSOperationQueue: 0x7ff4a24087d0>{name = 'NSOperationQueue Main Queue'}--<NSThread: 0x60000078c8c0>{number = 1, name = main}
--------------------輸出結果:-------------------
複製代碼
GCD
中只能使用信號量來設置併發數NSOperation
輕易就能設置併發數
maxConcurrentOperationCount
來控制單次出隊列去執行的任務數- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"Felix";
queue.maxConcurrentOperationCount = 2;
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{ // 一個任務
[NSThread sleepForTimeInterval:2];
NSLog(@"%d-%@",i,[NSThread currentThread]);
}];
}
}
--------------------輸出結果:-------------------
1-<NSThread: 0x6000009290c0>{number = 5, name = (null)}
0-<NSThread: 0x6000009348c0>{number = 8, name = (null)}
3-<NSThread: 0x6000009290c0>{number = 5, name = (null)}
2-<NSThread: 0x60000094b0c0>{number = 7, name = (null)}
4-<NSThread: 0x6000009348c0>{number = 8, name = (null)}
--------------------輸出結果:-------------------
複製代碼
在NSOperation
中添加依賴能很好的控制任務執行的前後順序
- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"請求token");
}];
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"拿着token,請求數據1");
}];
NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@"拿着數據1,請求數據2");
}];
[bo2 addDependency:bo1];
[bo3 addDependency:bo2];
[self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
NSLog(@"執行完了?我要幹其餘事");
}
--------------------輸出結果:-------------------
請求token
拿着token,請求數據1
拿着數據1,請求數據2
執行完了?我要幹其餘事
--------------------輸出結果:-------------------
複製代碼
注意不要添加依賴致使循環運用,會致使依賴無效並會在控制檯打印出"XPC connection interrupted"
// 掛起
queue.suspended = YES;
// 繼續
queue.suspended = NO;
// 取消
[queue cancelAllOperations];
複製代碼
可是使用中常常會遇到一些匪夷所思的問題——明明已經掛起了任務,可仍是繼續執行了幾個任務才中止執行
這幅圖是併發量爲2的狀況:任務3
、任務4
等待被調度任務3
、任務4
已經被調度出隊列,準備執行,此時它們是沒法掛起的任務3
、任務4
被線程執行,而原來的隊列被掛起不能被調度咱們平常開發中常常用SDWebImage
去加載網絡圖片,其中又是什麼原理呢?若是要咱們本身來實現又該怎麼去作呢?
NSURL *imageURL = [NSURL URLWithString:model.imageUrl];
[cell.imageView sd_setImageWithURL:imageURL placeholderImage:[UIImage imageNamed:@"Felix"]];
return cell;
複製代碼
使用圖片地址去下載NSData
數據,再轉成相應的UIImage
圖片
cell.imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
複製代碼
Q1:主線程使用NSData
轉UIImage
會形成卡頓,必需要解決這個問題
使用NSBlockOperation
去異步處理數據,而後在主線程刷新UI,從而解決了卡頓的問題
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下載圖片:%@", model.title);
// 延遲
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
複製代碼
Q2:因爲cell的緩存機制,圖片每次都要去下載會形成浪費,因此要想辦法存起來
模型
中有數據,則從模型
中取出圖片加載,節約內存消耗模型
中if (model.image) {
NSLog(@"從模型獲取圖片:%@",model.title);
cell.imageView.image = model.image;
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下載圖片:%@", model.title);
// 延遲
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
複製代碼
Q3:可是存到model
裏會致使內存暴漲,此時只能清理model
,但model
中不僅有圖片數據,因此得另闢蹊徑處理緩存問題
內存
中有數據,則從內存
中取出圖片加載,節約內存消耗全局可變字典(內存)
中UIImage *cacheImage = self.imageCacheDict[model.imageUrl];
if (cacheImage) {
NSLog(@"從內存獲取圖片:%@", model.title);
cell.imageView.image = cacheImage;
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下載圖片:%@", model.title);
// 延遲
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
[self.imageCacheDict setValue:image forKey:model.imageUrl];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
複製代碼
Q4:可是內存會在App關閉時回收內存,致使每次重啓都要從新下載圖片
本地緩存
中本地緩存
中的數據,節約性能消耗// 這裏對路徑進行了封裝處理,並進行了md5處理
UIImage *diskImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
if (diskImage) {
NSLog(@"從沙盒獲取圖片:%@",model.title);
cell.imageView.image = diskImage;
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下載圖片:%@", model.title);
// 延遲
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 存內存
[data writeToFile:[model.imageUrl getDowloadImagePath] atomically:YES];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
複製代碼
Q5:沙盒的效率沒有內存高,因此還得進行優化
內存
中有數據,則從內存
中取出圖片來展現沙盒
中有數據,則從沙盒
中取出圖片來展現並存一份到內存中本地緩存
和內存緩存
中UIImage *cacheImage = self.imageCacheDict[model.imageUrl];
if (cacheImage) {
NSLog(@"從內存獲取圖片:%@", model.title);
cell.imageView.image = cacheImage;
return cell;
}
UIImage *diskImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
if (diskImage) {
NSLog(@"從沙盒獲取image:%@",model.title);
cell.imageView.image = diskImage;
[self.imageCacheDict setValue:diskImage forKey:model.imageUrl];
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"去下載圖片:%@", model.title);
// 延遲
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
// 存內存
[self.imageCacheDict setValue:image forKey:model.imageUrl];
[data writeToFile:[model.imageUrl getDowloadImagePath] atomically:YES];
// 更新UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
複製代碼
這就是
SDWebImage
最簡易的步驟
筆者將文中內容封裝成一個Demo,有興趣能夠下載看看