国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

iOS多線程編程Part2/3-NSOperation

2019-11-14 19:55:04
字體:
來源:轉載
供稿:網友

多線程編程Part 1介紹了NSThread以及NSRunLoop,這篇Blog介紹另一種并發編程技術:NSOperation。

NSOperation & NSOperationQueue

從頭文件NSOperation.h來看接口是非常的簡潔,NSOperation本身是一個抽象類,定義了一個要執行的工作,NSOperationQueue是一個工作隊列,當工作加入到隊列后,NSOperationQueue會自動按照優先順序及工作的從屬依賴關系(如果有的話)組織執行。

NSOperation是沒法直接使用的,它只是提供了一個工作的基本邏輯,具體實現還是需要你通過定義自己的NSOperation子類來獲得。如果有必要也可以不將NSOperation加入到一個NSOperationQueue中去執行,直接調用起-start也可以直接執行。

在繼承NSOpertaion后,對于非并發的工作,只需要實現NSOperation子類的main方法:

1234567891011
-(void)main {   @try    {      // 處理工作任務   }   @catch(...)    {      // 處理異常,但是不能再重新拋出異常   }}

由于NSOperation的工作是可以取消Cancel的,那么你在main方法處理工作時就需要不斷輪詢[self isCancelled]確認當前的工作是否被取消了。

如果要支持并發工作,那么NSOperation子類需要至少override這四個方法:

  • start
  • isConcurrent
  • isExecuting
  • isFinished

實現了一個基于Operation的下載器,在Sample Code中可以下載。

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
- (void)operationDidStart{    [self.lock lock];    NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:self.URL                                                                cachePolicy:NSURLRequestReloadIgnoringCacheData                                                            timeoutInterval:self.timeoutInterval];    [request setHTTPMethod: @"GET"];    self.connection =[[NSURLConnection alloc] initWithRequest:request                                                     delegate:self                                             startImmediately:NO];    [self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];    [self.connection start];    [self.lock unlock];}- (void)operationDidFinish{    [self.lock lock];    [self willChangeValueForKey:@"isFinished"];    [self willChangeValueForKey:@"isExecuting"];    self.executing = NO;    self.finished = YES;    [self didChangeValueForKey:@"isExecuting"];    [self didChangeValueForKey:@"isFinished"];    [self.lock unlock];}- (void)start{    [self.lock lock];    if ([self isCancelled])    {        [self willChangeValueForKey:@"isFinished"];        self.finished = YES;        [self didChangeValueForKey:@"isFinished"];        return;    }    [self willChangeValueForKey:@"isExecuting"];    [self performSelector:@selector(operationDidStart) onThread:[[self class] networkThread] withObject:nil waitUntilDone:NO];    self.executing = YES;    [self didChangeValueForKey:@"isExecuting"];    [self.lock unlock];}- (void)cancel{    [self.lock lock];    [super cancel];    if (self.connection)    {        [self.connection cancel];        self.connection = nil;    }    [self.lock unlock];}- (BOOL)isConcurrent {    return YES;}- (BOOL)isExecuting {    return self.executing;}- (BOOL)isFinished {    return self.finished;}

start方法是工作的入口,通常是你用來設置線程或者其他執行工作任務需要的運行環境的,注意不要調用[super start];isConcurrent是標識這個Operation是否是并發執行的,這里曾經是個坑,如果你沒有實現isConcurrent,默認是返回NO,那么你的NSOperation就不是并發執行而是串行執行的,不過在iOS5.0和OS X10.6之后,已經會默認忽略這個返回值,最終和Queue的maxConcurrentOperationCount最大并發操作值相關;isExecuting和isFinished是用來報告當前的工作執行狀態情況的,注意必須是線程訪問安全的。

注意你的實現要發出合適的KVO通知,因為如果你的NSOperation實現需要用到工作依賴從屬特性,而你的實現里沒有發出合適的“isFinished”KVO通知,依賴你的NSOperation就無法正常執行。NSOperation支持KVO的屬性有:

  • isCancelled
  • isConcurrent
  • isExecuting
  • isFinished
  • isReady
  • dependencies
  • queuePRiority
  • completionBlock

當然也不是說所有的KVO通知都需要自己去實現,例如通常你用不到addObserver到你工作的“isCancelled”屬性,你只需要直接調用cancel方法就可以取消這個工作任務。

實現NSOperation子類后,可以直接調用start或者添加到一個NSOperationQueue里:

123
NSOperationQueue *queue = [[NSOperationQueue alloc] init];[queue addOperation:downloader];

NSOperation和NSOperationQueue其他特性

工作是有優先級的,可以通過NSOperation的一下兩個接口讀取或者設置:

12
- (NSOperationQueuePriority)queuePriority;- (void)setQueuePriority:(NSOperationQueuePriority)p;

工作之間也可有從屬依賴關系,只有依賴的工作完成后才會執行:

12
- (void)addDependency:(NSOperation *)op;- (void)removeDependency:(NSOperation *)op;

還可以通過下面接口設置運行NSOpration的子線程優先級:

1
- (void)setQueuePriority:(NSOperationQueuePriority)priority;

如果要設置Queue的并發操作數:

1
- (void)setMaxConcurrentOperationCount:(NSInteger)cnt;

iOS4之后還可以往NSOperation上添加一個結束block,用于在工作執行結束之后的操作:

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

如果需要阻塞等待NSOperation工作結束(別在主線程這么干),可以使用接口:

1
- (void)waitUntilFinished;

NSOperationQueue除了添加NSOperation外,也支持直接添加一個Block(iOS4之后):

1
- (void)addOperationWithBlock:(void (^)(void))block

NSOperationQueue可以取消所有添加的工作:

1
- (void)cancelAllOperations;

也可以阻塞式的等待所有工作結束(別在主線程這么干):

1
- (void)waitUntilAllOperationsAreFinished;

在NSOperation對象中獲得被添加的NSOperationQueue隊列:

1
+ (id)currentQueue

要獲得一個綁定在主線程的NSOperationQueue隊列:

1
+ (id)mainQueue

還有些接口參考頭文件NSOperation.h和NSOperation Class Reference,Apple的Class Reference文檔描述還是很清晰的。

NSInvocationOperation & NSBlockOperation

其實除非必要,簡單的工作完全可以使用官方提供的NSOperation兩個子類NSInvocationOperation和NSBlockOperation來實現。

NSInvocationOperation:

1234
NSInvocationOperation* theOp = [[NSInvocationOperation alloc]                        initWithTarget:self                                            selector:@selector(myTaskMethod:)                                                                          object:data];

NSBlockOperation:

1234
NSBlockOperation* theOp = [NSBlockOperation blockOperationWithBlock: ^{      NSLog(@"Beginning operation./n");      // Do some work.   }];

接口非常簡單,一看便會。

Sample Code

本文例子放在Github上(工程NSURLConnectionExample中的PTOperationDownloader)。

參考資料

Concurrency Programming Guide

NSOperation Class Reference

 

前言

1.上一講簡單介紹了NSThread的使用,雖然也可以實現多線程編程,但是需要我們去管理線程的生命周期,還要考慮線程同步、加鎖問題,造成一些性能上的開銷。我們也可以配合使用NSOperation和NSOperationQueue實現多線程編程,實現步驟大致是這樣的:

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

2> 然后將NSOperation對象添加到NSOperationQueue中

3> 系統會自動將NSOperation中封裝的操作放到一條新線程中執行

在此過程中,我們根本不用考慮線程的生命周期、同步、加鎖等問題

下面列舉一個應用場景,比如微博的粉絲列表:

每一行的頭像肯定要從新浪服務器下載圖片后才能顯示的,而且是需要異步下載。這時候你就可以把每一行的圖片下載操作封裝到一個NSOperation對象中,上面有6行,所以要創建6個NSOperation對象,然后添加到NSOperationQueue中,分別下載不同的圖片,下載完畢后,回到對應的行將圖片顯示出來。

 

2.默認情況下,NSOperation并不具備封裝操作的能力,必須使用它的子類,使用NSOperation子類的方式有3種:

1> NSInvocationOperation

2> NSBlockOperation

3> 自定義子類繼承NSOperation,實現內部相應的方法

這講先介紹如何用NSOperation封裝一個操作,后面再結合NSOperationQueue來使用。

 

一、NSInvocationOperation

1 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run:) object:@"mj"] autorelease];2 [operation start];

* 第1行初始化了一個NSInvocationOperation對象,它是基于一個對象和selector來創建操作

* 第2行調用了start方法,緊接著會馬上執行封裝好的操作,也就是會調用self的run:方法,并且將@"mj"作為方法參數

* 這里要注意:默認情況下,調用了start方法后并不會開一條新線程去執行操作,而是在當前線程同步執行操作。只有將operation放到一個NSOperationQueue中,才會異步執行操作。

 

二、NSBlockOperation

1.同步執行一個操作

1 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){2         NSLog(@"執行了一個新的操作");3 }];4  // 開始執行任務5 [operation start];

* 第1行初始化了一個NSBlockOperation對象,它是用一個Block來封裝需要執行的操作

第2行調用了start方法,緊接著會馬上執行Block中的內容

* 這里還是在當前線程同步執行操作,并沒有異步執行

 

2.并發執行多個操作

復制代碼
 1 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^(){ 2   NSLog(@"執行第1次操作,線程:%@", [NSThread currentThread]); 3 }]; 4  5 [operation addExecutionBlock:^() { 6   NSLog(@"又執行了1個新的操作,線程:%@", [NSThread currentThread]); 7 }]; 8  9 [operation addExecutionBlock:^() {10   NSLog(@"又執行了1個新的操作,線程:%@", [NSThread currentThread]);11 }];12 13 [operation addExecutionBlock:^() {14   NSLog(@"又執行了1個新的操作,線程:%@", [NSThread currentThread]);15 }];16 17 // 開始執行任務18 [operation start];
復制代碼

* 第1行初始化了一個NSBlockOperation對象

* 分別在第5、9、13行通過addExecutionBlock:方法添加了新的操作,包括第1行的操作,一共封裝了4個操作

* 在第18行調用start方法后,就會并發地執行這4個操作,也就是會在不同線程中執行

1 2013-02-02 21:38:46.102 thread[4602:c07] 又執行了1個新的操作,線程:<NSThread: 0x7121d50>{name = (null), num = 1}2 2013-02-02 21:38:46.102 thread[4602:3f03] 又執行了1個新的操作,線程:<NSThread: 0x742e1d0>{name = (null), num = 5}3 2013-02-02 21:38:46.102 thread[4602:1b03] 執行第1次操作,線程:<NSThread: 0x742de50>{name = (null), num = 3}4 2013-02-02 21:38:46.102 thread[4602:1303] 又執行了1個新的操作,線程:<NSThread: 0x7157bf0>{name = (null), num = 4}

可以看出,每個操作所在線程的num值都不一樣,說明是不同線程

 

三、NSOperation的其他用法

1.取消操作

operation開始執行之后, 默認會一直執行操作直到完成,我們也可以調用cancel方法中途取消操作

[operation cancel];

 

2.在操作完成后做一些事情

如果想在一個NSOperation執行完畢后做一些事情,就調用NSOperation的setCompletionBlock方法來設置想做的事情

operation.completionBlock = ^() {    NSLog(@"執行完畢");};

當operation封裝的操作執行完畢后,就會回調Block里面的內容

 

四、自定義NSOperation

如果NSInvocationOperation和NSBlockOperation不能滿足需求,我們可以直接新建子類繼承NSOperation,并添加任何需要執行的操作。如果只是簡單地自定義NSOperation,只需要重載-(void)main這個方法,在這個方法里面添加需要執行的操作。

下面寫個子類DownloadOperation來下載圖片

1.繼承NSOperation,重寫main方法

DownloadOperation.h

復制代碼
#import <Foundation/Foundation.h>@protocol DownloadOperationDelegate;@interface DownloadOperation : NSOperation// 圖片的url路徑@property (nonatomic, copy) NSString *imageUrl;// 代理@property (nonatomic, assign) id<DownloadOperationDelegate> delegate;- (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate;@end// 圖片下載的協議@protocol DownloadOperationDelegate <NSObject>- (void)downloadFinishWithImage:(UIImage *)image;@end
復制代碼

DownloadOperation.m

復制代碼
 1 #import "DownloadOperation.h" 2  3 @implementation DownloadOperation 4 @synthesize delegate = _delegate; 5 @synthesize imageUrl = _imageUrl; 6  7 // 初始化 8 - (id)initWithUrl:(NSString *)url delegate:(id<DownloadOperationDelegate>)delegate { 9     if (self = [super init]) {10         self.imageUrl = url;11         self.delegate = delegate;12     }13     return self;14 }15 // 釋放內存16 - (void)dealloc {17     [super dealloc];18     [_imageUrl release];19 }20 21 // 執行主任務22 - (void)main {23     // 新建一個自動釋放池,如果是異步執行操作,那么將無法訪問到主線程的自動釋放池24     @autoreleasepool {25         // ....26     }27 }28 @end
復制代碼

* 在第22行重載了main方法,等會就把下載圖片的代碼寫到這個方法中

* 如果這個DownloadOperation是在異步線程中執行操作,也就是說main方法在異步線程調用,那么將無法訪問主線程的自動釋放池,所以在第24行創建了一個屬于當前線程的自動釋放池

 

2.正確響應取消事件

* 默認情況下,一個NSOperation開始執行之后,會一直執行任務到結束,就比如上面的DownloadOperation,默認會執行完main方法中的所有代碼。

* NSOperation提供了一個cancel方法,可以取消當前的操作。

* 如果是自定義NSOperation的話,需要手動處理這個取消事件。比如,一旦調用了cancel方法,應該馬上終止main方法的執行,并及時回收一些資源。

* 處理取消事件的具體做法是:在main方法中定期地調用isCancelled方法檢測操作是否已經被取消,也就是說是否調用了cancel方法,如果返回YES,表示已取消,則立即讓main方法返回。

* 以下地方可能需要調用isCancelled方法:

  • 在執行任何實際的工作之前,也就是在main方法的開頭。因為取消可能發生在任何時候,甚至在operation執行之前。
  • 執行了一段耗時的操作之后也需要檢測操作是否已經被取消
復制代碼
 1 - (void)main { 2     // 新建一個自動釋放池,如果是異步執行操作,那么將無法訪問到主線程的自動釋放池 3     @autoreleasepool { 4         if (self.isCancelled) return; 5          6         // 獲取圖片數據 7         NSURL *url = [NSURL URLWithString:self.imageUrl]; 8         NSData *imageData = [NSData dataWithContentsOfURL:url]; 9         10         if (self.isCancelled) {11             url = nil;12             imageData = nil;13             return;14         }15         16         // 初始化圖片17         UIImage *image = [UIImage imageWithData:imageData];18         19         if (self.isCancelled) {20             image = nil;21             return;22         }23         24         if ([self.delegate respondsToSelector:@selector(downloadFinishWithImage:)]) {25             // 把圖片數據傳回到主線程26             [(NSObject *)self.delegate performSelectorOnMainThread:@selector(downloadFinishWithImage:) withObject:image waitUntilDone:NO];27         }28     }29 }
復制代碼

* 在第4行main方法的開頭就先判斷operation有沒有被取消。如果被取消了,那就沒有必要往下執行了

* 經過第8行下載圖片后,在第10行也需要判斷操作有沒有被取消

* 總之,執行了一段比較耗時的操作之后,都需要判斷操作有沒有被取消

* 圖片下載完畢后,在第26行將圖片數據傳遞給了代理(delegate)對象


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 石台县| 扎囊县| 鹤山市| 宕昌县| 富顺县| 河北省| 芜湖市| 张家界市| 牙克石市| 宁武县| 平武县| 清水县| 广平县| 大荔县| 桂林市| 灵山县| 黄冈市| 盐城市| 西安市| 确山县| 乐东| 含山县| 云安县| 庆城县| 集贤县| 香格里拉县| 滦南县| 东辽县| 景德镇市| 宁陵县| 新丰县| 如东县| 枣庄市| 武川县| 新绛县| 汶川县| 称多县| 平阴县| 山东省| 南京市| 信阳市|