iOS開發(fā)-block

*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需要我們對它不斷的使用、探究了解才能完成

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秉撇,一起剝皮案震驚了整個濱河市甜攀,隨后出現(xiàn)的幾起案子秋泄,更是在濱河造成了極大的恐慌,老刑警劉巖规阀,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件恒序,死亡現(xiàn)場離奇詭異,居然都是意外死亡谁撼,警方通過查閱死者的電腦和手機奸焙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來彤敛,“玉大人,你說我怎么就攤上這事了赌∧” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵勿她,是天一觀的道長袄秩。 經(jīng)常有香客問我,道長逢并,這世上最難降的妖魔是什么之剧? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮砍聊,結(jié)果婚禮上背稼,老公的妹妹穿的比我還像新娘。我一直安慰自己玻蝌,他們只是感情好蟹肘,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著俯树,像睡著了一般帘腹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上许饿,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天阳欲,我揣著相機與錄音,去河邊找鬼陋率。 笑死球化,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的翘贮。 我是一名探鬼主播赊窥,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼狸页!你這毒婦竟也來了锨能?” 一聲冷哼從身側(cè)響起扯再,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎址遇,沒想到半個月后熄阻,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡倔约,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年秃殉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片浸剩。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡钾军,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出绢要,到底是詐尸還是另有隱情吏恭,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布重罪,位于F島的核電站樱哼,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏剿配。R本人自食惡果不足惜搅幅,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望呼胚。 院中可真熱鬧茄唐,春花似錦、人聲如沸蝇更。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽簿寂。三九已至漾抬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間常遂,已是汗流浹背纳令。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留克胳,地道東北人平绩。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像漠另,于是被迫代替她去往敵國和親捏雌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 本文分兩部分介紹Block: 第一部分:Block基礎知識介紹第二部分:Block經(jīng)常使用的三種情況(方法回調(diào)笆搓,C...
    小小土豆dev閱讀 812評論 2 9
  • 博客地址 Blocks概要 什么是Blocks Blocks是C語言的擴充功能性湿∥嘲粒可以用一句話來表示Blocks的擴...
    換個名字再說閱讀 401評論 0 2
  • 一、基本概念: Block是一種C語言的數(shù)據(jù)類型肤频,指向結(jié)構(gòu)體的指針叹括,平常我們將Block當作一個代碼段使用,相當于...
    常綠籮閱讀 518評論 0 2
  • 1宵荒、block跟swift中的閉包(closure)基本一樣汁雷,都常用于值的回調(diào),特別是在多線程的網(wǎng)絡請求回調(diào)中报咳,使...
    kkj1996閱讀 302評論 0 0
  • 首先來了解下什么是Block (1)Block是OC中的一種數(shù)據(jù)類型侠讯,在iOS開發(fā)中被廣泛使用(2)^是Block...
    愛吃魚的小灰閱讀 2,355評論 2 7