iOS-block(一)-初探

基本概念

什么是block?《Objective-C高級編程》這本書里是這樣定義的:

帶有自動變量(局部變量)的匿名函數。顧名思義,匿名函數就是沒有名稱的函數。也被稱為閉包(closure)或者Anonymous function

我們可以理解為block就是一個沒有名稱的函數。定義block的方式和定義函數的方式是相似的,而block還可以作為參數使用。當block被調用其塊內的代碼才會被執行。

定義

根據block的定義,我們可以知道,block的主要組成是返回值和參數。其表達式如下:

^+返回值類型+參數列表+表達式

按照是否存在返回值和參數,我們可以將block的定義分為以下幾種:

    1. 無返回值+無參數
void(^myBlock)(void) = ^void(void) {
    
};
// 可以簡寫成:
void(^myBlock)(void) = ^ {
    
};
    1. 無返回值+有參數
void(^myBlock)(int a) = ^void(int num) {
    
};
// 可以簡寫成:
void(^myBlock)(int a) = ^(int num) {
    
};
    1. 有返回值+無參數
int(^myBlock)(void) = ^int(void) {
    return 10;
};
// 可以簡寫成:
int(^myBlock)(void) = ^int {
    return 10;
};
    1. 有返回值+有參數
int(^myBlock)(int a, int b) = ^int(int a, int b) {
    return a + b;
};

當參數和返回值為void可以忽略。

在實際開發中,我們經常使用typedef來定義block:

typedef int(^myBlock)(int a, int b);
myBlock mb = ^int(int a, int b) {
        return a + b;
};
NSLog(@"==myBlock==%d==", mb(1, 2));

block與外界變量的關聯

下面我們來看一個例子:

typedef int(^myBlock)(int a, int b);

int d = 10;
myBlock mb = ^int(int a, int b) {
    return a + b + d;
};
d = 5;
NSLog(@"==myBlock==%d==", mb(1, 2));

我們在mb內部使用外部變量int d,隨后又給d重新賦值,此時調用mb(1, 2)block內部d的取值是5還是10呢?運行程序,控制臺輸出13。此例子說明了block具有截獲外部變量的能力,而且截獲之后,變量在block的值是固定的,不會隨著外部的改變而改變。

我們接著在其外部定義一個變量e:

int e = 0;

當我們在block內部對e進行賦值操作的時候,編譯器會提示錯誤:

Variable is not assignable (missing __block type specifier)

意為變量不可以被分配使用,是因為缺少__block修飾符。那么我們為上述變量de的聲明加上__block修飾符,我們立刻看到編輯器沒有錯誤了。運行程序,此時控制臺輸出8,這說明此時block中的取值不再是10而是5了。它不但被我們分配使用了,可以隨著外界的改變而改變了,甚至我們可以隨意的在block內部修改d的值了,這到底是為什么呢?

從表面上看,沒有被__block修飾的變量,我們在block內部使用的時候,只是截獲其當時的值,所以其不會再改變,而被__block修飾的變量,我們截獲該變量的地址,所以它不論怎們改變,我們都能截獲到。

block分類

iOS中,我們依據內存情況將block分為6中

  • _NSConcreteGlobalBlock:全局block,不訪問外界變量(包括堆中和棧中的變量)
void (^block)(void) = ^{
    NSLog(@"==block==");
};
  • _NSConcreteMallocBlock:堆block,存在于堆內存中,是帶一個引用計數的對象,需要自己進行內存管理。變量本身在棧中,因為block能夠自動截獲變量,為了訪問到變量,會將變量從堆內存中copy成棧內存中。
int a = 10;
void (^block)(void) = ^{
    NSLog(@"==a==%d==", a);
};
  • _NSConcreteStackBlock:棧block,存于棧內存中,超出其作用域則馬上進行銷毀。作為方法或者函數的參數的時候不會被copy到堆上。
NSLog(@"%@",^{
    NSLog(@"==block==");
});
  • _NSConcreteAutoBlock
  • _NSConcreteFinalizingBlock
  • _NSConcreteWeakBlockVariable

前3種在日常開發中是很常見的,后3種是系統級別的block,一般比較少用。

我們知道程序在編譯的時候內存的分布有:堆區(heap)、棧區(stack)、文字常量區、程序代碼區、全局區/靜態區。

image

這張圖也就解釋了為什么我們使用block作為屬性的時候修飾符都是用copy_NSConcreteGlobalBlock是全局block,它只能存在于一個函數的內部,并不能作為屬性;而_NSConcreteStackBlock是棧block,存于棧內存中,超出其作用域則馬上進行銷毀,當我們使用copy就會將其copy到堆內存中,這樣會延長block的生命周期,防止出現異常;而_NSConcreteMallocBlock本身就是堆block,使用copy也不會對其有影響。

block的使用

block既然是一個匿名函數,那么它就可以作為函數使用,當然,它也可以作為作為函數的參數、或者函數的返回值調用。

上面的例子大多數都是block作為函數使用,我們也就不再贅述了。下面我們就看看其他兩種情況:

首先我們先給block設置一個別名,這樣看著簡單明了一些。

typedef int(^SumBlock)(int a, int b);
  1. block作為函數的參數
- (void)blockAsParameter:(SumBlock)mb {
    NSLog(@"==blockAsParameter實現==%@==", mb);
    mb(1, 2);
}

可以調用一下,然后運行程序:

[self blockAsParameter:^int(int a, int b) {
        NSLog(@"==blockAsParameter調用==%d==", a + b);
        return a + b;
}];

// 控制臺輸出
==blockAsParameter實現==<__NSGlobalBlock__: 0x106a4d090>==
==blockAsParameter調用==3==
  1. block作為函數的返回值
- (SumBlock)blockAsReturns {
    NSLog(@"==blockAsReturns==");
    return ^int(int a, int b) {
        return a + b;
    };
}

SumBlock mb2 = [self blockAsReturns];
NSLog(@"==blockAsReturns==%d==", mb2(1, 2));

==blockAsReturns==
==blockAsReturns==3==

其實說的通俗點,block就是封裝一段代碼塊,這段代碼塊可以像變量一樣被使用。

block的循環引用問題

我們在日常使用block的時候一定要注意一個問題,那就是循環引用。因為block經常是作為變量被self持有,或者是block的持有者被self作為變量持有,然而當我們在block內部使用self的時候就會造成循環引用。我們來看一個例子:

@property (nonatomic, assign) int num1;
@property (nonatomic, assign) int num2;
@property (nonatomic, copy) SumBlock propertyBlock;

self.num1 = 1;
self.num2 = 2;
self.propertyBlock = ^int(int a, int b) {
    NSLog(@"==a==%d==b==%d==", self.num1, self.num2);
    return a + b;
};

編譯器會直接提示:

Capturing 'self' strongly in this block is likely to lead to a retain cycle

意為在block中強引用self可能會造成循環引用。上述例子中,num1num2propertyBlock作為屬性被self持有,而我們又在block里使用了num1num2,這就相當propertyBlock又持有了self,這就造成了循環引用。強持有會對引用計數進行處理,循環引用會導致對象無法被釋放,就影響了對象的引用計數,會造成內存問題。那么這個問題如何解決呢?

__weak typeof(self) weakSelf = self;
self.propertyBlock = ^int(int a, int b) {
    NSLog(@"==a==%d==b==%d==", weakSelf.num1, weakSelf.num2);
    return a + b;
};

這樣就解決了循環引用問題。那么weakSelf是如何解決循環引用的呢?由于現在引入了weakSelf,持有的情況就變成了weakSelf持有selfself持有block,而block又持有weakSelf,但是需要注意的是weakSelf持有self是弱引用,只是一個指向,引用計數并沒有發生改變,所以就打破了循環引用。

但是使用__weak需要注意一點,就是對象的釋放時間。

self.propertyBlock = ^int(int a, int b) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"==a==%d==b==%d==", weakSelf.num1, weakSelf.num2);
    });
}

如果我們進入頁面之后,立即退出頁面,控制臺輸出:

==a==0==b==0==

這說明,dealloc之后持有的對象卻是已經被釋放了。如果我們想等到打印結果輸出之后,再進行dealloc該怎么處理。

  1. 使用__strong

我們可以在block里面使用__strong再將weakSelf轉化為強引用即可。此時雖然strongSelf為強引用,但是只是在block作用域內的,當block內任務執行完畢,自然也會釋放,和外界并沒有任何關系。

__strong typeof(weakSelf) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"==num==%d==", strongSelf.num);
});
  1. self作為參數傳入block

blcok具有截獲變量的能力,當參數傳入blockblockcopy一份使用,此時copy出來的變量和原來的就沒有關系。這樣也打破了循環引用。

typedef int(^MinusBlock)(int a, int b, ViewController *vController);

self.num = 10;
self.mb = ^int(int a, int b, ViewController *vController) {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"==num==%d==", vController.num);
    });
    return a - b;
};
self.mb(2, 1, self);

block的基礎就介紹到這里,下一章我們再來看看block的底層分析。

參考文獻:
Objective-C高級編程 iOSOS X多線程和內存管理》

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容