iOS中的線程編程
iOS主線程(UI線程),咱們的大部分業務邏輯代碼運行於主線程中。 沒有特殊需求,不該引入線程增長程序複雜度。 應用場景:邏輯執行時間過長,嚴重影響交互體驗(界面卡死)等。服務器
iOS多線程 有三種主要方法(1)NSThread(2)NSOperation(3)GCD網絡
下面簡單介紹這三個方法多線程
1.NSThread併發
調用方法以下:異步
如函數須要輸入參數,可從object傳進去。函數
( 1 ) [ N S T h r e a d d e t a c h N e w T h r e a d S e l e c t o r : @ s e l e c t o r ( t h r e a d In M a i n M e t h o d : ) t o T a r g e t : s e l f w i t h Object:nil];性能
(2) NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadInM ainMethod:) object:nil];測試
[myThread start];
(3) [obj performSelectorInBackground:@selector(threadMe) withObject:nil];atom
提個問題:若是某個ViewController裏運行了一個Thread,Thread還沒結束的時候,這個ViewC ontroller被Release了,結果會如何?
通過的的測試,Thread不結束,ViewController一直保留,不會執行dealloc方法。
Demo源代碼:
2.NSOperation
Demo源代碼:
|
本文目錄
回到頂部
前言 NSOperation有三種狀態
NSOperationQueue只有當它管理的全部操做的isFinished屬性全標爲YES之後操做才中止出列,也就是隊列中止運行,因此正確實現這個方法對於避免死鎖很關鍵。
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來使用。
回到頂部 1、NSInvocationOperation1 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中,纔會異步執行操做。
回到頂部 2、NSBlockOperation1.同步執行一個操做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對象 * 分別在第五、九、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值都不同,說明是不一樣線程
回到頂部 3、NSOperation的其餘用法1.取消操做operation開始執行以後, 默認會一直執行操做直到完成,咱們也能夠調用cancel方法中途取消操做 [operation cancel];
2.在操做完成後作一些事情若是想在一個NSOperation執行完畢後作一些事情,就調用NSOperation的setCompletionBlock方法來設置想作的事情 operation.completionBlock = ^() {
NSLog(@"執行完畢"); }; 當operation封裝的操做執行完畢後,就會回調Block裏面的內容
NSOperation進階 優先級跟NSThread同樣,NSOpertion也能夠設置優先級。 @property NSOperationQueuePriority queuePriority; 執行順序(依賴)有些時候想要控制執行順序,使用NSOpreation會方便多了,使用NSOpreation的Dependency就能夠實現這種功能。 NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"excute operation2"); }]; NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"excute operation1"); }]; [ope0ration2 addDependency:ope0ration1]; [queue addOperation:ope0ration1]; [queue addOperation:ope0ration2]; 上面先執行第一個operation1,等operation1返回isFinish爲YES,即operation1完成了,纔會執行operation2。 注意死鎖:必定不能夠循環依賴,像A依賴B,B依賴A,必定不要這樣作 CompletionBlock這個比較容易理解,就是每一個NSOperation執行完畢以後,就會執行該block NSOperationQueue *queue = [NSOperationQueue mainQueue]; NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"執行操做"); }]; [operation setCompletionBlock:^{ NSLog(@"執行操做完成"); }]; [queue addOperation:operation]; 執行結果 2015-09-22 23:47:47.640 Thread Learn[21307:662442] 執行操做 2015-09-22 23:47:47.640 Thread Learn[21307:662482] 執行操做完成 取消如前面所說,NSOperation有三種狀態,isReady -> isExecuting -> isFinish, 若是在Ready的狀態中對NSOperation進行取消,NSOperation會進入Finish狀態。可是Operation已經開始執行了,就會一直運行到結束,或者由咱們進行顯示取消。也就是說Operation已經在executing狀態,咱們調用cancle方法系統不會停止線程的,這須要咱們在任務過程當中檢測取消事件,並停止線程的執行,還要注意一點咱們要釋放內存或資源。仍是看一下實例代碼: - (IBAction)startNSOperation:(id)sender { self.blockOperation = [NSBlockOperation blockOperationWithBlock:^{ if ([self.blockOperation isCancelled]) { NSLog(@"取消了"); return; } //若是檢測還沒取消 //TODO:這裏請求網絡,獲取數據.. if ([self.blockOperation isCancelled]) { NSLog(@"取消了"); return; } //若是檢測還沒取消 //TODO:獲取到了數據刷新界面... }]; NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperation:self.blockOperation]; }
這種取消跟NSThread有點類似,調用cancle不會退出線程,須要你自已去停止線程,再exit; 回到頂部 4、自定義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方法:
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)對象
支持併發 自定義併發的NSOperation就麻煩多了,須要實現如下方法,咱們能夠看一下下面這個表(來自蘋果官方):
3.GCD 見單獨一篇文章 |