標籤:使用 問題 block 任務 return fine dha exp data
iOS block從零開始
在iOS4.0之後,block橫空出世,它本身封裝了一段代碼並將這段代碼當做變數,通過block()的方式進行回調。
block的結構
先來一段簡單的代碼看看:
void (^myBlock)(int a) = ^(int a){ NSLog(@"%zd",a); }; NSLog(@"旭寶愛吃魚"); myBlock(999);
輸出結果:
2016-05-03 11:27:18.571 block[5340:706252] 旭寶愛吃魚
2016-05-03 11:27:18.571 block[5340:706252] 999
下面我們解析一下:
- void :返回的參數 void為沒有傳回值
- (^myBlock):myBlock 為 block 的名稱
- (int a):此為參數列表
- ^(int a):傳入參數
通過上面的簡單介紹可以簡單瞭解到block的結構那麼下面便產生了四種格式的block。
四種block
有傳回值無參:
int (^myBlock)() = ^(){ return 999; };
有傳回值有參:
int (^myBlock)(int a) = ^(int a){ NSLog(@"%zd",a); return a; };
無傳回值無參:
void (^myBlock)() = ^(){ NSLog(@"旭寶愛吃魚"); };
無傳回值有參:
void (^myBlock)(int a) = ^(int a){ NSLog(@"%zd",a); };
block 捕獲外界局部變數
範例程式碼:
int a = 10; void (^myBlock)() = ^(){ NSLog(@"旭寶愛吃魚"); NSLog(@"%zd",a); }; NSLog(@"旭寶愛吃魚"); myBlock();
運行結果:
2016-05-03 11:43:32.680 block[5406:713702] 旭寶愛吃魚
2016-05-03 11:43:32.681 block[5406:713702] 旭寶愛吃魚
2016-05-03 11:43:32.681 block[5406:713702] 10
通過範例程式碼不難發現我們可以擷取局部變數,那麼改變局部變數是否也會改變block內的值呢。
範例程式碼:
int a = 10; void (^myBlock)() = ^(){ NSLog(@"旭寶愛吃魚"); NSLog(@"%zd",a); }; a = 20; NSLog(@"旭寶愛吃魚"); myBlock();
運行結果:
2016-05-03 11:47:07.669 block[5425:715861] 旭寶愛吃魚
2016-05-03 11:47:07.670 block[5425:715861] 旭寶愛吃魚
2016-05-03 11:47:07.670 block[5425:715861] 10
正如結果顯示,block內部的值是不會改變的,為什麼呢????
範例程式碼:
int a = 10; a = 20; void (^myBlock)() = ^(){ NSLog(@"旭寶愛吃魚"); NSLog(@"%zd",a); }; NSLog(@"旭寶愛吃魚"); myBlock();
運行結果:
2016-05-03 11:49:19.749 block[5450:717309] 旭寶愛吃魚
2016-05-03 11:49:19.750 block[5450:717309] 旭寶愛吃魚
2016-05-03 11:49:19.750 block[5450:717309] 20
情理之中的答案。
之所以block內部的值不會改變是因為block copy了局部變數。
這個問題解決後嘗試在block中改變局部變數。
很不幸失敗了,當我在block內改變外界的局部變數時,報錯了。
可視化我想改變外界的局部變數我該怎麼辦呢???
改變外界的局部變數
範例程式碼:
__block int a = 10; void (^myBlock)() = ^(){ NSLog(@"旭寶愛吃魚"); NSLog(@"%zd",a); a = 20; }; NSLog(@"旭寶愛吃魚"); myBlock(); NSLog(@"%zd",a);
運行結果:
2016-05-03 11:55:02.736 block[5490:721033] 旭寶愛吃魚
2016-05-03 11:55:02.737 block[5490:721033] 旭寶愛吃魚
2016-05-03 11:55:02.737 block[5490:721033] 10
2016-05-03 11:55:02.737 block[5490:721033] 20
我們只需要在局部變數前加__block 即可。
循環參考(我在前面的部落格有所提及)
block在iOS開發中被視作是對象,因此其生命週期會一直等到持有人的生命週期結束了才會結束。另一方面,由於block捕獲變數的機制,使得持有block的對象也可能被block持有,從而形成循環參考,導致兩者都不能被釋放:
@implementation CXObject{ void (^_cycleReferenceBlock)(void);}- (void)viewDidLoad{ [super viewDidLoad]; _cycleReferenceBlock = ^{ NSLog(@"%@", self); //引發循環參考 };}@end
遇到這種代碼編譯器只會告訴你存在警告,很多時候我們都是忽略警告的,這最後會導致記憶體泄露,兩者都無法釋放。跟普通變數存在block關鍵字一樣的,系統提供給我們weak的關鍵字用來修飾物件變數,聲明這是一個弱引用的對象,從而解決了循環參考的問題:
__weak typeof(*&self) weakSelf = self;_cycleReferenceBlock = ^{ NSLog(@"%@", weakSelf); //弱指標引用,不會造成循環參考};
使用block
在block出現之前,開發人員實現回調基本都是通過代理的方式進行的。比如負責網路請求的原生類NSURLConnection類,通過多個協議方法實現請求中的事件處理。而在最新的環境下,使用的NSURLSession已經採用block的方式處理工作要求了。各種第三方網路請求架構也都在使用block進行回調處理。這種轉變很大一部分原因在於block使用簡單,邏輯清晰,靈活等原因。接下來我會完成一次網路請求,然後通過block進行回調處理。這些回調包括請求完成、下載進度
按照returnValue(^blockName)(parameters)的方式進行block的聲明未免麻煩了些,我們可以通過關鍵字typedef來為block起類型名稱,然後直接通過類型名進行block的建立:
@interface CXDownloadManager: NSObject//block重新命名typedef void(^CXDownloadHandler)(NSData * receiveData, NSError * error);typedef void(^CXDownloadProgressHandler)(CGFloat progress);- (void)downloadWithURL: (NSString *)URL parameters: (NSDictionary *)parameters handler: (CXDownloadHandler)handler progress: (CXDownloadProgressHandler)progress;@end@implementation CXDownloadManager{ CXDownloadProgressHandler _progress;}- (void)downloadWithURL: (NSString *)URL parameters: (NSDictionary *)parameters handler: (CXDownloadHandler)handler progress: (CXDownloadProgressHandler)progress{ //建立請求對象 NSURLRequest * request = [self postRequestWithURL: URL params: parameters]; NSURLSession * session = [NSURLSession sharedSession]; //執行請求任務 NSURLSessionDataTask * task = [session dataTaskWithRequest: request completionHandler: ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (handler) { dispatch_async(dispatch_get_main_queue(), ^{ handler(data, error); }); } }]; [task resume];}//進度協議方法- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten // 每次寫入的data位元組數 totalBytesWritten:(int64_t)totalBytesWritten // 當前一共寫入的data位元組數 totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite // 期望收到的所有data位元組數 { double downloadProgress = totalBytesWritten / (double)totalBytesExpectedToWrite; if (_progress) { _progress(downloadProgress); }} @end
上面通過封裝NSURLSession的請求,傳入一個處理請求結果的block對象,就會自動將請求任務放到背景工作執行緒中執行實現,我們在網路請求邏輯的代碼中調用如下:
#define QQMUSICURL @"https://www.baidu.com/link?url=UTiLwaXdh_-UZG31tkXPU62Jtsg2mSbZgSPSR3ME3YwOBSe97Hw6U6DNceQ2Ln1vXnb2krx0ezIuziBIuL4fWNi3dZ02t2NdN6946XwN0-a&wd=&eqid=ce6864b50004af120000000656fe235f"[[CXDownloadManager alloc] downloadWithURL: QQMUSICURL parameters: nil handler ^(NSData * receiveData, NSError * error) { if (error) { NSLog(@"下載失敗:%@", error) } else { //處理下載資料 }} progress: ^(CGFloat progress) { NSLog(@"下載進度%lu%%", progress*100);}];
總結
block捕獲變數、代碼傳遞、代碼內聯等特性賦予了它多於代理機制的功能和靈活性,儘管它也存在循環參考、不易調試追溯等缺陷,但無可置疑它的優點深受碼農們的喜愛。如何更加靈活的使用block需要我們對它不斷的使用、探究瞭解才能完成
iOS block從零開始