*block的代碼是內(nèi)聯(lián)的豁状,效率高于函數(shù)調(diào)用
block對于外部變量默認是只讀屬性
block被Objective-C看成是對象處理
block特性
認識block
先從一個簡單的需求來說:傳入兩個數(shù)趣席,并且計算這兩個數(shù)的和螟炫,為此創(chuàng)建了這樣一個block:
int (^sumOfNumbers)(int a, int b) = ^(int a, int b) {
return a + b;
};
這段代碼等號左側(cè)聲明一個名為sumOfNumbers的代碼塊,名稱前用^符號表示后面的字符串是block的名稱杂曲。最左側(cè)的int表示這個block的返回值,括號中間表示這個block的參數(shù)列表袁余,這里接收兩個int類型的參數(shù)擎勘。 而在等號右側(cè)表示這個block的定義,其中返回值是可以省略的颖榜,編譯器會根據(jù)上下文自動補充返回值類型棚饵。使用^符號銜接著一個參數(shù)列表,使用括號包起來掩完,告訴編譯器這是一個block噪漾,然后使用大括號將block的代碼封裝起來。
block代碼結(jié)構(gòu)
捕獲外界變量
block還可以訪問外界的局部變量且蓬,在我的從UIView動畫說起中有這么一段代碼欣硼,其中block內(nèi)部使用到了外部的局部變量:
CGPoint center = cell.center;
CGPoint startCenter = center;
startCenter.y += LXD_SCREEN_HEIGHT;
cell.center = startCenter;
[UIView animateWithDuration: 0.5 delay: 0.35 * indexPath.item usingSpringWithDamping: 0.6 initialSpringVelocity: 0 options: UIViewAnimationOptionCurveLinear animations: ^{
cell.center = center;
} completion: ^(BOOL finished) {
NSLog("animation %@ finished", finished? @"is": @"isn't");
}];
這里面就用到了void(^animations)(void)跟void(^completion)(BOOL finished)兩個block,系統(tǒng)會在動畫開始以及動畫結(jié)束的時候分別調(diào)用者兩個block恶阴。在實現(xiàn)動畫的block內(nèi)部诈胜,代碼訪問了上文中的center屬性——在動畫開始的時候這個動畫函數(shù)的生命周期早已結(jié)束,而block會捕獲代碼外的局部變量冯事,當然這只局限于只讀操作焦匈。如果我們在block中修改外部變量,編譯器將會報錯:
block中修改外界局部變量
對于希望在block中修改的外界局部對象昵仅,我們可以給這些變量加上__block關(guān)鍵字修飾缓熟,這樣就能在block中修改這些變量。在捕獲變量特性中,還有一個有趣的小機制荚虚,我們把上面的代碼改成這樣:
CGPoint center = CGPointZero;
CGPoint (^pointAddHandler)(CGPoint addPoint) = ^(CGPoint addPoint) {
return CGPointMake(center.x + addPoint.x, center.y + addPoint.y);
}
center = CGPointMake(100, 100);
NSLog(@"%@", pointAddHandler(CGPointMake(10, 10)));? ? //輸出{10,10}
block在捕獲變量的時候只會保存變量被捕獲時的狀態(tài)(對象變量除外),之后即便變量再次改變籍茧,block中的值也不會發(fā)生改變版述。所以上面的代碼在計算新的坐標值時center的值依舊等于CGPointZero
循環(huán)引用
開頭說過,block在iOS開發(fā)中被視作是對象寞冯,因此其生命周期會一直等到持有者的生命周期結(jié)束了才會結(jié)束渴析。另一方面,由于block捕獲變量的機制吮龄,使得持有block的對象也可能被block持有俭茧,從而形成循環(huán)引用,導致兩者都不能被釋放:
@implementation LXDObject
{
void (^_cycleReferenceBlock)(void);
}
- (void)viewDidLoad
{
[super viewDidLoad];
_cycleReferenceBlock = ^{
NSLog(@"%@", self);? //引發(fā)循環(huán)引用
};
}
@end
遇到這種代碼編譯器只會告訴你存在警告漓帚,很多時候我們都是忽略警告的母债,這最后會導致內(nèi)存泄露,兩者都無法釋放尝抖。跟普通變量存在__block關(guān)鍵字一樣的毡们,系統(tǒng)提供給我們__weak的關(guān)鍵字用來修飾對象變量,聲明這是一個弱引用的對象昧辽,從而解決了循環(huán)引用的問題:
__weak typeof(*&self) weakSelf = self;
_cycleReferenceBlock = ^{
NSLog(@"%@", weakSelf);? //弱指針引用衙熔,不會造成循環(huán)引用
};
對于block這種有趣的特性,在唐巧的談Objective-C block的實現(xiàn)有詳細介紹block的底層實現(xiàn)代碼搅荞,我在這里就不多說了
使用block
在block出現(xiàn)之前红氯,開發(fā)者實現(xiàn)回調(diào)基本都是通過代理的方式進行的。比如負責網(wǎng)絡請求的原生類NSURLConnection類咕痛,通過多個協(xié)議方法實現(xiàn)請求中的事件處理痢甘。而在最新的環(huán)境下,使用的NSURLSession已經(jīng)采用block的方式處理任務請求了暇检。各種第三方網(wǎng)絡請求框架也都在使用block進行回調(diào)處理产阱。這種轉(zhuǎn)變很大一部分原因在于block使用簡單,邏輯清晰块仆,靈活等原因构蹬。接下來我會完成一次網(wǎng)絡請求,然后通過block進行回調(diào)處理悔据。這些回調(diào)包括請求完成庄敛、下載進度
按照returnValue(^blockName)(parameters)的方式進行block的聲明未免麻煩了些,我們可以通過關(guān)鍵字typedef來為block起類型名稱科汗,然后直接通過類型名進行block的創(chuàng)建:
@interface LXDDownloadManager: NSObject< NSURLSessionDownloadDelegate >
//block重命名
typedef void(^LXDDownloadHandler)(NSData * receiveData, NSError * error);
typedef void(^LXDDownloadProgressHandler)(CGFloat progress);
- (void)downloadWithURL: (NSString *)URL parameters: (NSDictionary *)parameters handler: (LXDDownloadHandler)handler progress: (LXDDownloadProgressHandler)progress;
@end
@implementation LXDDownloadManager
{
LXDDownloadProgressHandler _progress;
}
- (void)downloadWithURL: (NSString *)URL parameters: (NSDictionary *)parameters handler: (LXDDownloadHandler)handler progress: (LXDDownloadProgressHandler)progress
{
//創(chuàng)建請求對象
NSURLRequest * request = [self postRequestWithURL: URL params: parameters];
NSURLSession * session = [NSURLSession sharedSession];
//執(zhí)行請求任務
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];
}
//進度協(xié)議方法
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten // 每次寫入的data字節(jié)數(shù)
totalBytesWritten:(int64_t)totalBytesWritten // 當前一共寫入的data字節(jié)數(shù)
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite // 期望收到的所有data字節(jié)數(shù)
{
double downloadProgress = totalBytesWritten / (double)totalBytesExpectedToWrite;
if (_progress) { _progress(downloadProgress); }
}
@end
上面通過封裝NSURLSession的請求藻烤,傳入一個處理請求結(jié)果的block對象,就會自動將請求任務放到工作線程中執(zhí)行實現(xiàn),我們在網(wǎng)絡請求邏輯的代碼中調(diào)用如下:
#define QQMUSICURL @"https://www.baidu.com/link?url=UTiLwaXdh_-UZG31tkXPU62Jtsg2mSbZgSPSR3ME3YwOBSe97Hw6U6DNceQ2Ln1vXnb2krx0ezIuziBIuL4fWNi3dZ02t2NdN6946XwN0-a&wd=&eqid=ce6864b50004af120000000656fe235f"
[[LXDDownloadManager alloc] downloadWithURL: QQMUSICURL parameters: nil handler ^(NSData * receiveData, NSError * error) {
if (error) { NSLog(@"下載失敳劳ぁ:%@", error) }
else {
//處理下載數(shù)據(jù)
}
} progress: ^(CGFloat progress) {
NSLog(@"下載進度%lu%%", progress*100);
}];
仿swift高階函數(shù)
用過swift的開發(fā)者都知道swift的函數(shù)調(diào)用很好的體現(xiàn)了鏈式編程的思想涎显,即將多個操作通過.連接起來,使得可讀性更強兴猩,比如ocString.stringByAppendingFormat("abc").stringByAppendingFormat("edf")就是連續(xù)調(diào)用了追加字符串的方法期吓。這種編程方式的條件之一是每次函數(shù)調(diào)用必須有返回值。雖然在使用Objective-C開發(fā)的過程中倾芝,方法的調(diào)用是通過[target action]的方式完成的讨勤,但是block本身的調(diào)用方式也是通過blockName(parameters)的方式執(zhí)行的,與這種鏈式函數(shù)有異曲同工之妙晨另。
在swift中提供了包括map潭千、filter、reduce等十分簡潔優(yōu)秀的高階函數(shù)供我們對數(shù)組數(shù)據(jù)進行操作借尿,同樣情況下刨晴,遍歷一個數(shù)組并求和在使用oc(不使用kvc)和swift的環(huán)境下的代碼是這樣的:
#pragma mark - OC code
NSArray numbers = @[@10, @15, @99, @66, @25];
NSInteger totalNumber = 0;
for (NSNumber number in numbers) {
totalNumber += number.integerValue;
}
#pragma mark - swift code
let numbers = [10, 15, 99, 66, 25];
let totalNumber = numbers.reduce(0, { $0+$1 })
無論是代碼量還是簡潔性,此時的oc都比不上swift垛玻。那么接下來就要通過神奇的block來為oc添加這些高階函數(shù)的實現(xiàn)割捅。為此我們需要新建一個NSArray的分類擴展,命名為NSArray+LXDExtension
#import
/// 數(shù)組元素轉(zhuǎn)換
typedef id(^LXDItemMap)(id item);
typedef NSArray *(^LXDArrayMap)(LXDItemMap itemMap);
/// 數(shù)組元素篩選
typedef BOOL(^LXDItemFilter)(id item);
typedef NSArray *(^LXDArrayFilter)(LXDItemFilter itemFilter);
/**
*? 擴展數(shù)組高級方法仿swift調(diào)用
*/
@interface NSArray (LXDExtension)
@property (nonatomic, copy, readonly) LXDArrayMap map;
@property (nonatomic, copy, readonly) LXDArrayFilter filter;
@end
前面說了為了實現(xiàn)鏈式編程帚桩,函數(shù)調(diào)用的前提是具有返回對象亿驾。因此我使用了typedef聲明了幾個不同類型的block。雖然本質(zhì)上LXDArrayMap和LXDArrayFilter兩個block是一樣的账嚎,但是為了區(qū)分它們的功能莫瞬,還是建議這么做。其實現(xiàn)文件如下:
typedef void(^LXDEnumerateHandler)(id item);
@implementation NSArray (LXDTopMethod)
- (LXDArrayMap)map
{
LXDArrayMap map = ^id(LXDItemMap itemMap) {
NSMutableArray * items = @[].mutableCopy;
for (id item in self) {
[items addObject: itemMap(item)];
}
return items;
};
return map;
}
- (LXDArrayFilter)filter
{
LXDArrayFilter filter = ^BOOL(LXDItemFilter itemFilter) {
NSMutableArray * items = @[].mutableCopy;
for (id item in self) {
if (itemFilter(item)) { [items addObject: item]; }
}
return items;
};
return filter;
}
- (void)setFilter:(LXDArrayFilter)filter {}
- (void)setMap:(LXDArrayMap)map {}
@end
我們通過重寫setter方法保證block不會被外部修改實現(xiàn)郭蕉,并且在getter中遍歷數(shù)組的元素并調(diào)用傳入的執(zhí)行代碼來實現(xiàn)map和filter等功能疼邀。對于這兩個功能的實現(xiàn)也很簡單,下面舉出兩個調(diào)用高階函數(shù)的例子:
#pragma mark - 篩選數(shù)組中大于20的數(shù)值并轉(zhuǎn)換成字符串
NSArray * numbers = @[@10, @15, @99, @66, @25, @28.1, @7.5, @11.2, @66.2];
NSArray * result = numbers.filter(^BOOL(NSNumber * item) {
return item.doubleValue > 20
}).map(^id(NSNumber * item) {
return [NSString stringWithFormat: @"string %g", item.doubleValue];
});
#pragma mark - 將數(shù)組中的字典轉(zhuǎn)換成對應的數(shù)據(jù)模型
NSArray * jsons = @[@{ ... }, @{ ... }, @{ ... }];
NSArray * models = jsons.map(^id(id item) {
return [[LXDModel alloc] initWithJSON: item];
})
由于語法上的限制召锈,雖然這樣的調(diào)用跟swift原生的調(diào)用對比起來還是復雜了旁振,但通過block讓oc實現(xiàn)了函數(shù)鏈式調(diào)用的代碼看起來也清爽了很多
總結(jié)
block捕獲變量、代碼傳遞涨岁、代碼內(nèi)聯(lián)等特性賦予了它多于代理機制的功能和靈活性拐袜,盡管它也存在循環(huán)引用、不易調(diào)試追溯等缺陷梢薪,但無可置疑它的優(yōu)點深受碼農(nóng)們的喜愛蹬铺。如何更加靈活的使用block需要我們對它不斷的使用、探究了解才能完成