iOS - Block用法

前言

1、理解
Block其實(shí)就是一個(gè)代碼塊。本質(zhì)上來說,一個(gè)Block就是一段能夠在將來被執(zhí)行的代碼。然而Block又是一個(gè)普通的Objective-C對(duì)象,正因?yàn)樗菍?duì)象,Block可以被作為參數(shù)傳遞,可以作為返回值從一個(gè)方法返回,可以用來給變量賦值。
2、特點(diǎn)
將代碼放在Block中,使代碼更簡(jiǎn)潔緊湊,易于閱讀, 而且比函數(shù)使用更方便、更美觀。Block其實(shí)是對(duì)閉包的實(shí)現(xiàn)。
3、Block的優(yōu)勢(shì)
在Block之前,如果我們想要調(diào)用一段代碼,然后之后一段時(shí)間,讓它給我們返回,我們一般會(huì)使用delegate或者NSNotification。但是使用過 delegate 和 NSNotification 大家就應(yīng)該會(huì)感覺到——我們會(huì)不可避免的將代碼寫的到處都是,我們需要在某處開始一個(gè)任務(wù),在另外一個(gè)地方來處理這個(gè)返回結(jié)果。使用 Block 就可以在一定程度上避免這個(gè)問題。

下面進(jìn)入主題

Block是什么

Block : 帶有自動(dòng)變量(局部變量)的匿名函數(shù)

匿名函數(shù) :沒有函數(shù)名的函數(shù),一對(duì){}包裹的內(nèi)容是匿名函數(shù)的作用域
自動(dòng)變量:棧上聲明的一個(gè)變量不是靜態(tài)變量和全局變量,是不可以在這個(gè)棧內(nèi)聲明的匿名函數(shù)中使用的,但在Block中卻可以。

關(guān)于帶有自動(dòng)變量的含義,這是因?yàn)锽lock有捕獲外部變量的功能。能夠保存外部變量的瞬間值,所以即便在block外修改變量的值,也不會(huì)對(duì)Block截獲的自動(dòng)變量的值產(chǎn)生影響。

例 一道比較經(jīng)典的面試題:

int val = 10;
void (^blk)(void) = ^{ 
    printf("val=%d\n",val);
}; 
val = 2; 
blk(); 

我們都知道這段代碼 輸出val的值為 10 而不是2,這是因?yàn)锽lock截獲變量并保存變量的瞬間值

Block 語法

  1. Block的聲明及定義
//返回值(^Blok名字)(參數(shù)列表) = ^返回值(參數(shù)列表) {實(shí)現(xiàn)};
    //標(biāo)準(zhǔn)的定義和聲明
    int(^blk)(int count) = ^int(int count) {
        return count++;
    };
    blk(1);
    //當(dāng)返回值為void  實(shí)現(xiàn)部分可以忽略void不寫 即 ^(int count){}
    void(^blk1)(int count) = ^void(int count) {
        count++;
    };
    blk1(1);
    //當(dāng)參數(shù)為void 實(shí)現(xiàn)部分可以忽略參數(shù)不寫 即 ^int{}
    int(^blk2)(void) = ^int(void) {
        return 1;
    };
    blk2();
    //當(dāng)參數(shù)和返回值都為void 實(shí)現(xiàn)部分可以簡(jiǎn)寫為 ^{}
    void(^blk3)(void) = ^void(void) {
        NSLog(@"參數(shù)和返回值都為void");
    };
    blk3();
    //匿名Block 只有實(shí)現(xiàn)部分 沒有函數(shù)名
//    ^int(int count) {
//        return count;
//    };
  1. typedef簡(jiǎn)化Block的聲明
//typedef 定義block  返回值(^Blok名字)(參數(shù)列表)
typedef void(^blk)(void); //無返回值  無參數(shù)
typedef void(^blk1)(NSString *name);//無返回值  有參數(shù)
typedef int(^blk2)(void);//有返回值 無參數(shù)
typedef int(^blk3)(int count);//有返回值 有參數(shù)

Block類型

根據(jù)Block在內(nèi)存中存儲(chǔ)的位置分為三種類型:

  • NSGlobalBlock是位于全局區(qū)的block
  • NSMallocBlock是位于堆區(qū)的block
  • NSStackBlock是位于棧區(qū)的block
    //全局block NSGlobalBlock
    void(^blk)(void) = ^{
        NSLog(@"blk");
    };
    blk();
    NSLog(@" -- %@",blk);
    
    void(^blk3)(int count) = ^(int count) {
        NSLog(@"%d",count);
    };
    blk3(20);
    NSLog(@" -- %@",blk3);
    NSLog(@"-----------------------------------------");
    
    //堆block NSMallockBlock
    int a = 10;
    void(^blk1)(void) = ^{
        NSLog(@"a = %d",a);
    };
    blk1();
    NSLog(@" -- %@",blk1);
    
    __block int b = 30;
    void(^blk4)(void) = ^{
        b = 40;
        NSLog(@"%d",b);
    };
    blk4();
    NSLog(@" -- %@",blk4);
    NSLog(@"-----------------------------------------");
    
    //棧block  NSStackBlock  當(dāng)不捕獲變量a時(shí) 該block為全局block
    NSLog(@" -- %@", [^{NSLog(@"Stack Block:%d",a);} class]);

Block 用法 (傳遞數(shù)據(jù) 傳遞事件)

  1. 作為屬性
@interface ViewController ()
@property (copy, nonatomic) void(^blkCall)(NSInteger index);
@end
  1. 作為參數(shù)
@interface ViewController : UIViewController
- (void)viewController:(UIViewController *)vc callBack:(void(^)(NSString *name)) callBack;
@end
  1. 作為返回值
- (void(^)(int count))func {
    return ^(int count) {
        NSLog(@"返回");
    };
}

作為返回值的情況我沒怎么用過,前兩種用法詳見 demo

Block 內(nèi)存問題

在Block中某個(gè)對(duì)象持有Block本身,而Block又持有該對(duì)象就會(huì)引起內(nèi)存泄漏(通常是引用self),這里介紹幾種解決循環(huán)引用的方法

  1. 我們最常用的 使用__weak typeOf(self)
__weak typeof(self) weakSelf = self;
self.blk = ^{
    NSLog(@"In Block : %@",weakSelf);
 };
  1. 使用 __block ClassName
__block XXViewController* blockSelf = self;
self.blk = ^{
    NSLog(@"In Block : %@",blockSelf);
    blkSelf = nil;//不能省略
 };
self.blk();//該block必須執(zhí)行一次,否則還是內(nèi)存泄露

使用該方法解決內(nèi)存問題,一定要注意在block代碼塊內(nèi),使用完使用完__block變量后將其設(shè)為nil,并且該block必須至少執(zhí)行一次后,不存在內(nèi)存泄露

  1. 將在Block內(nèi)要使用到的對(duì)象(一般為self對(duì)象),以Block參數(shù)的形式傳入,Block就不會(huì)捕獲該對(duì)象,而將其作為參數(shù)使用,其生命周期系統(tǒng)的棧自動(dòng)管理,不造成內(nèi)存泄露。
   self.blk = ^(UIViewController *vc) {
        NSLog(@"Use Property:%@", vc.name);
    };
    self.blk(self);

附: Block內(nèi)修改外部變量的值 __block修飾符

 __block int a = 0;
 void (^foo)(void) = ^{
     a = 1;
 };
 foo(); //這里,a的值被修改為1

__block保證了棧上和Block內(nèi)(通常在堆上)可以訪問和修改“同一個(gè)變量”,__block是如何實(shí)現(xiàn)這一功能的?

__block發(fā)揮作用的原理:將棧上用__block修飾的自動(dòng)變量封裝成一個(gè)結(jié)構(gòu)體,讓其在堆上創(chuàng)建,以方便從棧上或堆上訪問和修改同一份數(shù)據(jù)。

這里沒有探究Block底層的實(shí)現(xiàn),如果有研究底層很厲害的大神請(qǐng)賜教,另外有錯(cuò)誤的或者遺漏的地方,請(qǐng)指教?。?!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 《目標(biāo),下一站》 文/鐵牟 遠(yuǎn)方,一條游馳的巨蟒 一路翻山越嶺的奔波,呼哧 你看它慢下來了 慢下來,等著啜息 穿著...
    鐵牟閱讀 280評(píng)論 0 1
  • 自從上段感情結(jié)束,我一直都沒有承認(rèn),他到底帶給我了什么?我一直覺得他給我的是焦慮,除了我覺得他不可靠外,我也沒覺得...
    尹莉莎閱讀 808評(píng)論 0 1
  • ?隨著企業(yè)規(guī)模的不斷擴(kuò)大,員工的不斷增多,漸漸對(duì)管理人員的執(zhí)行力提出了新的要求。的確,沒有執(zhí)行,浮在腦海里的藍(lán)圖永...
    163誠信品牌講師閱讀 354評(píng)論 0 1

友情鏈接更多精彩內(nèi)容