iOS block從零開始
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從零開始