聊一聊Block(一)

一. Block的概念

1.1 帶有自動(dòng)變量的匿名函數(shù)纯出。因?yàn)楹瘮?shù)是對(duì)象,所以block本身也是一個(gè)對(duì)象
將Objective-C編譯為C++代碼

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 'class.m' -o 'class.cpp'

如果帶有weak需要運(yùn)行時(shí)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios8.0.0 'class.m' -o 'class.cpp'

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
1.2 Block語法

^``返回值類型``參數(shù)列表``表達(dá)式

^ int (int count) { return count + 1; }

可以省略為:

^``參數(shù)列表``表達(dá)式

注:省略返回值類型時(shí)敷燎,如果表達(dá)式中有retrun語句就使用該返回值的類型暂筝,如果表達(dá)式中沒有return語句就使用void類型。表達(dá)式中含有多個(gè)return語句時(shí)硬贯,所有return的返回值類型必須相同焕襟。

^ (int count){ return count + 1; };

如果沒有參數(shù)還可以省略為以下形式

^``表達(dá)式

^{printf("Blocks\n")}
1.3 完整形式的Block語法與一般的C語言函數(shù)定義相比,僅有兩點(diǎn)不同
  • 沒有函數(shù)名
  • 帶有 ^
// c
void func(int a) {

}
// block
^(int a) {

};

二. Block類型變量

2.1 C語言中將函數(shù)地址賦值給函數(shù)指針類型變量:

Block除了沒有名稱以及帶有^以外鸵赖,其他都與C語言函數(shù)定義相同它褪。在定義C語言函數(shù)時(shí)翘悉,就可以將所定義函數(shù)的地址賦值給函數(shù)指針類型變量中。

int func(int count) {
    return count + 1;
}
int (* funcptr) (int count) = &func;
2.2 聲明Block類型變量包吝,在Block語法中源葫,也可將Block語法賦值給聲明為Block類型的變量中

同樣,在Block語法中息堂,可將Block語法賦值給聲明為block類型的變量中块促。即源代碼中一旦使用Block語法就相當(dāng)于生成了可賦值給Block類型變量的“值”竭翠。Blocks中由Block語法生成的值也被稱為“Block”薇搁。在有Block的文檔中观蜗,“Block”既指源代碼中的Block語法,也指由Block語法所生成的值宏娄。

// Block類型變量結(jié)構(gòu)
return_type (^blockName)(parameters)


// Block類型變量結(jié)構(gòu)舉例
int (^ blockName)(int count);

// Block語法
^ (int count){
    return count + 1;
};

// 使用Block語法將Block賦值為Block類型變量
int (^blockName)(int count) = ^ (int count){
        return count + 1;
};

賦值過程可以描述為:
^開始的Block語法生成的Block被賦值給變量blackName中。

2.3與前面的使用函數(shù)指針的源代碼相比而知粮宛,聲明Block類型變量僅僅是將聲明函數(shù)指針類型變量的^變?yōu)?code>^卖宠。該Block類型變量與一般的C語言變量完全相同,可作為以下用途使用:
  • 自動(dòng)變量(局部變量)

    因?yàn)榕c通常的變量相同筷畦,所以當(dāng)然也可以由Block類型變量向Block類型變量賦值蜒秤。如下:

int (^blackName1)(int count) = blockName;
  • 函數(shù)參數(shù)
    作為函數(shù)參數(shù)

    void func(int (^blockName)(int)) {
    
    }
    

    將Block作為函數(shù)的返回值返回

    int (^func())(int) {
      return ^(int count){return count + 1;};
    }
    

    在使用Block的時(shí)候我們一般 使用typedef 來聲明blockName類型的變量

    void func(int (^blockName)(int)) {
    
    }
    可改寫為:
    typedef int (^blockName)(int);
    void func(blockName) {
    
    }
    
    

    Block類型變量可完全像通常的C語言變量一樣使用作媚,因此也可以使用指向Block類型變量的指針纸泡,即Block的指針類型變量

        typedef int (^blk_t)(int);
        blk_t blk = ^(int count) {return count + 1;};
        __strong blk_t *blkptr = &blk; // C語言中的代碼因?yàn)闆]有引用計(jì)數(shù)內(nèi)存管理赖瞒,在Xcode中需要指定修飾符
        (*blkptr)(10);
    
  • 靜態(tài)變量

  • 靜態(tài)全局變量

  • 全局變量

三. Block的分類 NSGlobalBlock、NSMallocBlock吧兔、NSStackBlock

image.png

NSGlobalBlock 調(diào)用copy 還是 NSGlobalBlock
NSStackBlock 調(diào)用copy 變成 NSMallocBlocl
NSMallocBlock 調(diào)用copy 還是 NSMallocBlock 只不過是引用計(jì)數(shù)加一

  • NSGlobalBlock

    void (^globalBlock)(void) = ^{
          
      };
      NSLog(@"%@",globalBlock);
      <__NSGlobalBlock__: 0x107bf6060>
    

    存儲(chǔ)在靜態(tài)區(qū)

  • NSMallocBlock

    __block int a = 10;
      void (^mallocBlock)(void) = ^ {
          a++;
      };
      mallocBlock();
      NSLog(@"%@",mallocBlock);
    

    存在堆區(qū)

  • NSStackBlock

    int a = 10;
    NSLog(@"%@",^{
          NSLog(@"%d",a);
      });
    

    存儲(chǔ)在棧區(qū)

四. 解決循環(huán)引用的三種方式

4.1 __weak 并使用 strong引用

self.string = @"helloWorld";
__weak typeof(self)weakSelf = self;
    
self.blockName = ^{
    __strong typeof(weakSelf)strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@",strongSelf.string);
    });
    
};
self.blockName();

此種方式會(huì)延遲delloc的執(zhí)行時(shí)間境蔼,但是能夠保證strongSelf.string正常輸出,輸出順序?yàn)?br> helloWorld
delloc
如果不用__strong強(qiáng)引用weakSelf的話逢享,weakSelf.string的輸出會(huì)為nill,輸出順序?yàn)閐elloc
null

4.2 __block

self.string = @"helloWorld";
__block XSYViewController *vc = self;
    self.blockName = ^{
        NSLog(@"%@",vc.string);
        vc = nil;
    };
self.blockName();

因?yàn)開_block修飾符會(huì)截獲vc的指針吴藻,self->block->vc->self。形成了死循環(huán)侧但,但是vc置為nil,環(huán)被打開弦叶,所以此方式可以防止死循環(huán)

4.3 傳參數(shù)

self.blockName = ^(XSYViewController *vc) {
        NSLog(@"%@",vc.string);
    };
self.blockName(self);

五. block底層源碼分析

5.1 沒有截獲自動(dòng)變量時(shí)的Block

block的內(nèi)存布局


image.png

void (^blockName)(void) = ^{
    printf("hello world");
}
blockName();

NSGlobalBlock源碼分析:

struct __block_impl {
  void *isa;           //
  int Flags;           //
  int Reserved;        //
  void *FuncPtr;       //
};

// 
struct __main_block_impl_0 {
  struct __block_impl impl;,
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

/*
Block語法轉(zhuǎn)換的C語言函數(shù)
^ {printf("hello word");};的源碼
*/
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
   printf("hello word");
}


static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

// block的執(zhí)行過程
int main() {

  /*
struct __main_block_impl_0 *tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blockName = &tmp;
描述:該源代碼將__main_block_impl_0結(jié)構(gòu)體類型的自動(dòng)變量燕侠,即棧上生成的__main_block_impl_0結(jié)構(gòu)體實(shí)例的指針,賦值給__main_block_impl_0結(jié)構(gòu)體指針類型的變量blackName立莉。一下為這部分代碼對(duì)應(yīng)的最初源代碼:
void (^blockName)(void) = ^{
        printf("hello world");
     };
這句話也可描述為:將Block語法生成的Block賦值給Block類型的變量blockName。它等同于將__main_blocl_impl_0結(jié)構(gòu)體實(shí)例的指針賦值給變量__main_blocl_impl_0結(jié)構(gòu)體類型的變量blockName茫舶。
*/ 
  void(*blockName)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  
  /*
  blockName() 的源碼刹淌, 去掉轉(zhuǎn)換部分(*blockName->impl.FuncPtr)(blockName);
  */ 
  ((void (*)(__block_impl *))((__block_impl *)blockName)->FuncPtr)((__block_impl *)blockName);
  return 0;
}

5.2 截獲自動(dòng)變量時(shí)的Block

int a = 10;
int (^block)(void) = ^{
    NSLog(@"%d",a);
};
block();

六. “id”這一變量類型用于存儲(chǔ)Objective-C對(duì)象。

id 為objc_object結(jié)構(gòu)體的指針類型疹启。

typedef struct objc_object {
    Class isa;
} id;

Class為objc_class結(jié)構(gòu)體的指針類型

typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

objc_object 結(jié)構(gòu)體和objc_class結(jié)構(gòu)體歸根到底是在各個(gè)對(duì)象和類的實(shí)現(xiàn)中使用的最基本的結(jié)構(gòu)體喊崖。下面我們通過編寫簡單的Objective-C類聲明來確認(rèn)下:

@interface MyObject : NSObject
{
    int val0,
    int val1,
}
基于objc_object結(jié)構(gòu)體雇逞,該類的對(duì)象的結(jié)構(gòu)如下:
struct MyObject {
    Class isa;
    int val0;
    int val1;
}

對(duì)象都是結(jié)構(gòu)體實(shí)例:
MyObject類的實(shí)例變量val0和val1被直接聲明為對(duì)象的結(jié)構(gòu)體成員〗诜拢“Objective-C中由類生成對(duì)象”意味著掉蔬,像該結(jié)構(gòu)體這樣“生成由該類生成的對(duì)象的結(jié)構(gòu)體實(shí)例”查近。生成的各個(gè)對(duì)象霜威,即由該類生成的對(duì)象的各個(gè)結(jié)構(gòu)體實(shí)例,通過成員變量isa保持該類的結(jié)構(gòu)體實(shí)例指針戈泼。如圖:


image.png

5. 如何區(qū)分三種NSGlobalBlock大猛、NSMallocBlock淀零、NSStackBlock

6. 執(zhí)行delloc方法會(huì)讓自動(dòng)釋放池工作嗎?

7. 自動(dòng)釋放池是每個(gè)對(duì)象都有的嗎驾中?

8. __strong 修飾 帶有__weak修飾符的變量原理

9. 在block里改變基本類型的變量也需要添加__block

int a = 10
void(^block)(void) = ^ {
    a++; // 會(huì)報(bào)錯(cuò)
};
block()

原因:因?yàn)閍在棧區(qū)肩民,需要拷貝到堆區(qū)
有__block底層進(jìn)行了地址的傳遞(指針傳遞)
沒有__block底層進(jìn)行了值的傳遞(值傳遞)

10.查看block的源碼步驟

  1. cd XcodeDemo/
  2. mkdir testBlock
  3. cd testBlock/
  4. vim block.c
  5.  #include "stdio.h"
     int main() {
       void(^block)(void) = ^{
        printf("hello word");
     };
       block();
       return 0;
     }
    
  6. gcc block.c // 編譯
  7. ./a.out // 執(zhí)行
  8. clang -rewrite-objc block.c -o blockCPP.cpp // 用于指定輸出(out)文件名
  9. 完成

block的copy操作,在堆上

  1. block作為返回值灶搜,在arc中會(huì)自動(dòng)copy
  2. 當(dāng)block被_strong指向是工窍,在arc中自動(dòng)copy
  3. enumerateObjectsUsingBlock:帶有UsingBlock參數(shù)名的也會(huì)自動(dòng)copy
  4. 作為gcd API的參數(shù)自動(dòng)copy

被__block修飾的對(duì)象類型

  1. 當(dāng)__block變量在棧上時(shí)不會(huì)對(duì)指向的類型產(chǎn)生強(qiáng)引用
  2. 當(dāng)block變量被拷貝到堆上時(shí)
    會(huì)調(diào)用block內(nèi)部的copy函數(shù),
    copy內(nèi)部會(huì)調(diào)用_block_objct_assing函數(shù)鹏溯,
    _block_objct_assign 函數(shù)會(huì)根據(jù)指向?qū)ο蟮男揎椃?_strong, __weak, __unsafe_unretain)做出響應(yīng)的操作纵苛,形成強(qiáng)引用(retain)或弱引用(注意:這里僅限于ARC中時(shí)會(huì)retain言津,MRC時(shí)不會(huì)retain)
  3. 如果__block變量在堆中移除,
    會(huì)調(diào)用block內(nèi)部的dispose函數(shù)悬槽,
    dispose函數(shù)內(nèi)部會(huì)調(diào)用__block_object_dispose函數(shù)
    __block_object_dispose函數(shù)會(huì)自動(dòng)釋放指向的對(duì)象(release)

block的原理是什么?

帶有自動(dòng)變量的匿名函數(shù)
封裝了函數(shù)調(diào)用的oc對(duì)象

__block 的作用是什么蓬坡?有什么注意點(diǎn)

一旦使用了__block,會(huì)將變量包裝成帶有此變量的結(jié)構(gòu)體屑咳。不管是int還是對(duì)象,都會(huì)產(chǎn)生內(nèi)存管理杖爽。

為了修改變量的值

block的屬性修飾詞為什么是copy紫皇?調(diào)用block有哪些使用注意

block不經(jīng)過copy,會(huì)存在于棧上或全局區(qū)聪铺,為了將block拷貝到堆上
避免循環(huán)引用

block在修改NSMutableArray時(shí),需不需要添加__block?

不需要撒桨,只有在重新賦值的時(shí)候才需要番宁。能不加盡量不加,因?yàn)闀?huì)生成新的對(duì)象踱蠢,耗費(fèi)內(nèi)存

參考文獻(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棋电,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子企锌,更是在濱河造成了極大的恐慌于未,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抖坪,死亡現(xiàn)場離奇詭異闷叉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)蚯瞧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來埋合,“玉大人,你說我怎么就攤上這事坦喘。” “怎么了瓣铣?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵棠笑,是天一觀的道長。 經(jīng)常有香客問我蓖救,道長印屁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任从橘,我火速辦了婚禮础钠,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旗吁。我一直安慰自己,他們只是感情好香府,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布码倦。 她就那樣靜靜地躺著,像睡著了一般柠硕。 火紅的嫁衣襯著肌膚如雪运提。 梳的紋絲不亂的頭發(fā)上蝗柔,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天癣丧,我揣著相機(jī)與錄音栈妆,去河邊找鬼。 笑死嬉橙,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的市框。 我是一名探鬼主播糕韧,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼粪滤!你這毒婦竟也來了雀扶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤窍侧,失蹤者是張志新(化名)和其女友劉穎转绷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體议经,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡煞肾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了习绢。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梧却,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出放航,到底是詐尸還是另有隱情圆裕,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布赊时,位于F島的核電站,受9級(jí)特大地震影響蛋叼,放射性物質(zhì)發(fā)生泄漏剂陡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一歌馍、第九天 我趴在偏房一處隱蔽的房頂上張望晕鹊。 院中可真熱鬧,春花似錦溅话、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躁锁。三九已至卵史,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間以躯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國打工刁标, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人命雀。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓吏砂,卻偏偏與公主長得像乘客,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子易核,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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

  • 使用block已經(jīng)有一段時(shí)間了牡直,感覺自己了解的還行,但是幾天前看到CocoaChina上一個(gè)關(guān)于block的小測試...
    心愿2016閱讀 345評(píng)論 0 0
  • 關(guān)于block總結(jié) block有三種: NSGlobalBlock靜態(tài)區(qū)(全局區(qū))block乡小,這是一種特殊的blo...
    finn90閱讀 238評(píng)論 0 0
  • Block簡介(copy一段) Block作為C語言的擴(kuò)展饵史,并不是高新技術(shù),和其他語言的閉包或lambda表達(dá)式是...
    qui丶MyLove閱讀 424評(píng)論 0 0
  • Block 梳理與疑問 時(shí)隔一年湃番,再次讀 《Objective-C 高級(jí)編程》,看到 block 一章吠撮,這一次從頭...
    DeerRun閱讀 636評(píng)論 0 2
  • 前言 深究block可以說會(huì)涉及不少東西讲竿,筆者欲通過循序漸進(jìn)的方式來談及block相關(guān),略陳固陋戴卜。閱讀本文前,希望...
    陸辭書閱讀 1,115評(píng)論 6 22