iOS開發(fā)---Block詳解

iOS開發(fā)---Block詳解

Block的基礎(chǔ)

什么是Blocks恋日?

  • 用一句話來描述:帶有自動(dòng)變量的匿名函數(shù)(是不是一臉懵逼彩郊,不要擔(dān)心讥耗,整篇博客都會(huì)圍繞這句話展開)顧名思義:Block沒有函數(shù)名毒坛,另外Block帶有"^"標(biāo)記,插入記號(hào)便于查找到Block
  • Blocks 也被稱作閉包望伦、代碼塊。展開來講煎殷,Blocks就是一個(gè)代碼塊屯伞,把你想要執(zhí)行的代碼封裝在這個(gè)代碼塊里,等到需要的時(shí)候再去調(diào)用豪直。
  • Block 共享局部作用域的數(shù)據(jù)劣摇。Block 的這項(xiàng)特征非常有用,因?yàn)槿绻鷮?shí)現(xiàn)一個(gè)方法弓乙,并且該方法定義一個(gè)塊末融,則該塊可以訪問該方法的局部變量和參數(shù)(包括堆棧變量),以及函數(shù)和全局變量(包括實(shí)例變量)暇韧。這種訪問是只讀的勾习,但如果使用 __block 修飾符聲明變量,則可在 Block 內(nèi)更改其值懈玻。即使包含有塊的方法或函數(shù)已返回巧婶,并且其局部作用范圍已銷毀,但是只要存在對(duì)該塊的引用涂乌,局部變量仍作為塊對(duì)象的一部分繼續(xù)存在艺栈。

Blocks的語法

  • Block的完整語法格式如下:
^ returnType (argument list) {
  expressions
}

用一張圖來表示

Block語法
  • 也可以寫省略格式的Block,比如省略返回值類型:
^ (int x) {
  return x;
}

Block省略返回值類型時(shí),如果表達(dá)式中有return語句就使用該返回值的類型骂倘,沒有return語句就使用void類型眼滤。

  • 如果沒有參數(shù)列表,也可以省略參數(shù)列表:
^ {
  NSLog(@"hello world");
}
  • 與c語言的區(qū)別
    1. 沒有函數(shù)名
    2. 帶有"^"符號(hào)

Block類型變量

  • Block類型變量與一般的C語言變量完全相同历涝,可以作為自動(dòng)變量诅需,函數(shù)參數(shù),靜態(tài)變量荧库,全局變量使用
  • C語言函數(shù)將是如何將所定義的函數(shù)的地址賦值給函數(shù)指針類型變量中
int func (int count)
{
    return count + 1;
}

int (*funcptr) (int) = &func;
  • 使用Block語法就相當(dāng)于生成了可賦值給Block類型變量的值堰塌。
//Blocks 變量聲明與賦值
int (^blk) (int) = ^int (int count) {
    return count + 1;
};
//把Block語法生成的值賦值給Block類型變量
int (^myBlock)(int) = blk; 

與前面的使用函數(shù)指針的源代碼對(duì)比可知,聲明Block類型變量僅僅是將聲明函數(shù)指針類型變量的""變?yōu)?“^”*

  • 在函數(shù)返回值中指定Block類型分衫,可以將Block作為函數(shù)的返回值返回场刑。
int (^func()(int)) {
    return ^(int count){
        return count + 1;
    }
}

Block在oc中的使用

  • 通過property聲明成員變量:@property (nonatomic, copy) 返回值類型 (^變量名) (參數(shù)列表);
@property (nonatomic, copy) void (^callBack) (NSString *);

- (void)useBlock {
  self.callBack = ^ (NSString *str){
    NSLog(@"useBlock %@", str);
  };
  self.callBack(@"hello world");
}
  • 作為方法參數(shù):- (void)someMethodThatTaksesABlock:(返回值類型 (^)(參數(shù)列表)) 變量名;
- (void)callBackAsAParameter:(void (^)(NSString *print))callBack {
  callBack(@"i am alone");
}

//調(diào)用該函數(shù)
[self callbackAsAParameter:^(NSString *print) {
    NSLog(@"here is %@",print);
}];
  • 通過typedef定義變量類型
//typedef 返回值類型 (^聲明名稱)(參數(shù)列表);
//聲明名稱 變量名 = ^返回值類型(參數(shù)列表) { 表達(dá)式 };
typedef void (^callBlock)(NSSting *);

callBlock block = ^(NSString *str) {
  NSLog(@"%@", str);
}

與上一個(gè)知識(shí)點(diǎn)中指定Block為函數(shù)返回值對(duì)比

//原來的記述方式
void func(void (^blk)(NSString 8))
//用了 typedef 定義后的記述方式
void func(callBlock blk)

//原來的記述方式
void (^func()(NSString *)) 
//用了 typedef 定義后的記述方式
callBlock func()

Block截取變量

截獲自動(dòng)變量的值

  • 我們先看一個(gè)??
// 使用 Blocks 截獲局部變量值
- (void)useBlockInterceptLocalVariables {
    int a = 10, b = 20;

    void (^myLocalBlock)(void) = ^{
        printf("a = %d, b = %d\n",a, b);
    };

    myLocalBlock();    // 打印結(jié)果:a = 10, b = 20

    a = 20;
    b = 30;

    myLocalBlock();    // 打印結(jié)果:a = 10, b = 20
}

為什么兩次打印結(jié)果都是 a = 10, b = 20

明明在第一次調(diào)用 myLocalBlock();之后已經(jīng)重新給變量 a蚪战、變量 b 賦值了牵现,為什么第二次調(diào)用 myLocalBlock();的時(shí)候铐懊,使用的還是之前對(duì)應(yīng)變量的值?

因?yàn)?Block 語法的表達(dá)式使用的是它之前聲明的局部變量a瞎疼、變量 b科乎。Blocks 中,Block 表達(dá)式截獲所使用的局部變量的值贼急,保存了該變量的瞬時(shí)值茅茂。所以在第二次執(zhí)行 Block 表達(dá)式時(shí),即使已經(jīng)改變了局部變量 ab 的值太抓,也不會(huì)影響 Block 表達(dá)式在執(zhí)行時(shí)所保存的局部變量的瞬時(shí)值空闲。

這就是 Blocks 變量截獲局部變量值的特性。

通過__block說明符賦值

  • 上面我們想修改變量a走敌,變量b的值碴倾,但是有沒有成功,那么我們難道就沒有方法來修改了么悔常?別急影斑,__block來也,只要用這個(gè)說明符修飾變量机打,就可以在塊中修改矫户。
// 使用 __block 說明符修飾,更改局部變量值
- (void)useBlockQualifierChangeLocalVariables {
    __block int a = 10, b = 20;
    void (^myLocalBlock)(void) = ^{
        a = 20;
        b = 30;
        printf("a = %d, b = %d\n",a, b);  // 打印結(jié)果:a = 20, b = 30
    };
    
    myLocalBlock();
}

可以看到残邀,使用__block說明符修飾之后皆辽,我們?cè)?Block表達(dá)式中,成功的修改了局部變量值芥挣。

使用__block修飾符的自動(dòng)變量被稱為__blcok變量

Block的實(shí)現(xiàn)

在上一部分我們知道了Blocks帶有局部變量的匿名函數(shù)驱闷。但是 Block 的實(shí)質(zhì)究竟是什么呢?類型空免?變量空另?

要想了解 Block 的本質(zhì),就需要從 Block 對(duì)應(yīng)的 C++ 源碼來入手蹋砚。

下面我們通過一步步的源碼剖析來了解 Block 的本質(zhì)扼菠。

Block的實(shí)質(zhì)是什么?

準(zhǔn)備工作

  1. 在項(xiàng)目中添加 blocks.m 文件坝咐,并寫好 block 的相關(guān)代碼析命。
  2. 打開『終端』粥帚,執(zhí)行 cd XXX/XXX 命令铐达,其中 XXX/XXXblock.m所在的目錄凤类。
  3. 繼續(xù)執(zhí)行clang -rewrite-objc block.m
  4. 執(zhí)行完命令之后,block.m 所在目錄下就會(huì)生成一個(gè)block.cpp文件,這就是我們需要的 block 相關(guān)的 C++源碼盗尸。

Block源碼預(yù)覽

  • 轉(zhuǎn)換前OC代碼:
int main () {
    void (^myBlock)(void) = ^{
        printf("myBlock\n");
    };
    myBlock();
    return 0;
}
  • 轉(zhuǎn)換后c++代碼:
/* 包含 Block 實(shí)際函數(shù)指針的結(jié)構(gòu)體 */
struct __block_impl {
    void *isa;
    int Flags;               
    int Reserved;       // 今后版本升級(jí)所需的區(qū)域大小
    void *FuncPtr;      // 函數(shù)指針
};

/* Block 結(jié)構(gòu)體 */
struct __main_block_impl_0 {
    // impl:Block 的實(shí)際函數(shù)指針柑船,指向包含 Block 主體部分的 __main_block_func_0 結(jié)構(gòu)體
    struct __block_impl impl;
    // Desc:Desc 指針,指向包含 Block 附加信息的 __main_block_desc_0() 結(jié)構(gòu)體
    struct __main_block_desc_0* Desc;
    // __main_block_impl_0:Block 構(gòu)造函數(shù)
    __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 主體部分結(jié)構(gòu)體 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("myBlock\n");
}

/* Block 附加信息結(jié)構(gòu)體:包含今后版本升級(jí)所需區(qū)域大小振劳,Block 的大小*/
static struct __main_block_desc_0 {
    size_t reserved;      // 今后版本升級(jí)所需區(qū)域大小
    size_t Block_size;    // Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

/* main 函數(shù) */
int main () {
    //myBlock的初始化
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    //myBlock的調(diào)用
    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);

    return 0;
}

下面我們一步一步來拆解轉(zhuǎn)換后的源碼

Block結(jié)構(gòu)體
  • 我們先來看看 __main_block_impl_0 結(jié)構(gòu)體( Block 結(jié)構(gòu)體)
/* Block 結(jié)構(gòu)體 */
struct __main_block_impl_0 {
    // impl:Block 的實(shí)際函數(shù)指針椎组,指向包含 Block 主體部分的 __main_block_func_0 結(jié)構(gòu)體
    struct __block_impl impl;
    // Desc:Desc 指針,指向包含 Block 附加信息的 __main_block_desc_0() 結(jié)構(gòu)體
    struct __main_block_desc_0* Desc;
    // __main_block_impl_0:Block 構(gòu)造函數(shù)
    __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;
    }
};

從上邊我們可以看出历恐,__main_block_impl_0 結(jié)構(gòu)體(Block 結(jié)構(gòu)體)包含了三個(gè)部分:

  1. 成員變量 impl;
  2. 成員變量 Desc 指針;
  3. __main_block_impl_0 構(gòu)造函數(shù)。
struct __block_impl 結(jié)構(gòu)

我們先看看第一部分impl__block_impl結(jié)構(gòu)體類型的成員變量

/* 包含 Block 實(shí)際函數(shù)指針的結(jié)構(gòu)體 */
struct __block_impl {
    void *isa;          // 用于保存 Block 結(jié)構(gòu)體的實(shí)例指針
    int Flags;          // 標(biāo)志位
    int Reserved;       // 今后版本升級(jí)所需的區(qū)域大小
    void *FuncPtr;      // 函數(shù)指針
};
  • __block_impl包含了 Block 實(shí)際函數(shù)指針 FuncPtr专筷,FuncPtr指針指向 Block 的主體部分弱贼,也就是 Block 對(duì)應(yīng) OC 代碼中的 ^{ printf("myBlock\n"); };部分。
  • 還包含了標(biāo)志位 Flags磷蛹,在實(shí)現(xiàn)block的內(nèi)部操作時(shí)會(huì)用到
  • 今后版本升級(jí)所需的區(qū)域大小 Reserved
  • __block_impl結(jié)構(gòu)體的實(shí)例指針 isa吮旅。
static struct __main_block_desc_0結(jié)構(gòu)

第二部分 Desc 是指向的是 __main_block_desc_0 類型的結(jié)構(gòu)體的指針型成員變量,__main_block_desc_0 結(jié)構(gòu)體用來描述該 Block 的相關(guān)附加信息:

/* Block 附加信息結(jié)構(gòu)體:包含今后版本升級(jí)所需區(qū)域大小味咳,Block 的大小*/
static struct __main_block_desc_0 {
    size_t reserved;      // 今后版本升級(jí)所需區(qū)域大小
    size_t Block_size;  // Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
__main_block_impl_0 構(gòu)造函數(shù)

第三部分是 __main_block_impl_0 結(jié)構(gòu)體(Block 結(jié)構(gòu)體) 的構(gòu)造函數(shù)庇勃,負(fù)責(zé)初始化 __main_block_impl_0 結(jié)構(gòu)體(Block 結(jié)構(gòu)體) 的成員變量。

__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;
}
  • 關(guān)于結(jié)構(gòu)體構(gòu)造函數(shù)中對(duì)各個(gè)成員變量的賦值槽驶,我們需要先來看看 main()函數(shù)中责嚷,對(duì)該構(gòu)造函數(shù)的調(diào)用。
  void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
  • 我們可以把上面的代碼稍微轉(zhuǎn)換一下掂铐,去掉不同類型之間的轉(zhuǎn)換罕拂,使之簡潔一點(diǎn):
struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *myBlock = &temp;
  • 這樣,就容易看懂了全陨。該代碼將通過 __main_block_impl_0構(gòu)造函數(shù)爆班,生成的 __main_block_impl_0結(jié)構(gòu)體(Block 結(jié)構(gòu)體)類型實(shí)例的指針,賦值給 __main_block_impl_0結(jié)構(gòu)體(Block 結(jié)構(gòu)體)類型的指針變量 myBlock辱姨。

可以看到柿菩, 調(diào)用 __main_block_impl_0構(gòu)造函數(shù)的時(shí)候,傳入了兩個(gè)參數(shù)雨涛。

  1. 第一個(gè)參數(shù):__main_block_func_0枢舶。

    • 其實(shí)就是 Block 對(duì)應(yīng)的主體部分,可以看到下面關(guān)于 __main_block_func_0結(jié)構(gòu)體的定義 镜悉,和OC 代碼中 ^{ printf("myBlock\n"); };部分具有相同的表達(dá)式祟辟。
    • 這里參數(shù)中的 __cself是指向 Block 的值的指針變量,相當(dāng)于 OC 中的 self侣肄。
    /* Block 主體部分結(jié)構(gòu)體 */
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("myBlock\n");
    }
    
  2. 第二個(gè)參數(shù):__main_block_desc_0_DATA__main_block_desc_0_DATA包含該 Block 的相關(guān)信息旧困。
    我們?cè)賮斫Y(jié)合之前的 __main_block_impl_0結(jié)構(gòu)體定義。

    • __main_block_impl_0結(jié)構(gòu)體(Block 結(jié)構(gòu)體)可以表述為:
    struct __main_block_impl_0 {
        void *isa;          // 用于保存 Block 結(jié)構(gòu)體的實(shí)例指針
        int Flags;          // 標(biāo)志位
        int Reserved;       // 今后版本升級(jí)所需的區(qū)域大小
        void *FuncPtr;      // 函數(shù)指針
        struct __main_block_desc_0* Desc;        // Desc:Desc 指針
    };
    
    • __main_block_impl_0構(gòu)造函數(shù)可以表述為:
    impl.isa = &_NSConcreteStackBlock;     // isa 保存 Block 結(jié)構(gòu)體實(shí)例
    impl.Flags = 0;        // 標(biāo)志位賦值
    impl.FuncPtr = __main_block_func_0;    // FuncPtr 保存 Block 結(jié)構(gòu)體的主體部分
    Desc = &__main_block_desc_0_DATA;      // Desc 保存 Block 結(jié)構(gòu)體的附加信息
    
調(diào)用

在分析了Block結(jié)構(gòu)體和成員變量,現(xiàn)在我們看看main函數(shù)中是如何調(diào)用block的

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
  • myBlock結(jié)構(gòu)體的第一個(gè)成員變量為__block_impl吼具,所以myBlock首地址僚纷,就是__block_impl impl 的首地址,即可以直接轉(zhuǎn)換為__block_impl 類型
  • (void (*)(__block_impl *))__block_implFunc 的類型
  • ((__block_impl *)myBlock)->FuncPtr() 調(diào)用函數(shù)
  • ((__block_impl *)myBlock) 函數(shù)參數(shù)
Block的實(shí)質(zhì)總結(jié)

用一句話來說拗盒,Block是個(gè)對(duì)象

  • 在C語言的底層實(shí)現(xiàn)里怖竭,它是一個(gè)結(jié)構(gòu)體。這個(gè)結(jié)構(gòu)體相當(dāng)于objc_class的類對(duì)象結(jié)構(gòu)體陡蝇,用_NSConcreteStackBlock對(duì)其中成員變量isa初始化痊臭,_NSConcreteStackBlock相當(dāng)于class_t結(jié)構(gòu)體實(shí)例(在我的理解中就是該 block 實(shí)例的元類)。在將 Block 作為OC對(duì)象處理時(shí)登夫,關(guān)于該類的信息放置于_NSConcreteStackBlock中广匙。
  • 是對(duì)象:其內(nèi)部第一個(gè)成員為 isa 指針;
  • 封裝了函數(shù)調(diào)用:Block內(nèi)代碼塊恼策,封裝了函數(shù)調(diào)用鸦致,調(diào)用Block,就是調(diào)用該封裝的函數(shù)涣楷;
  • 執(zhí)行上下文:Block 還有一個(gè)描述 Desc分唾,該描述對(duì)象包含了Block的信息以及捕獲變量的內(nèi)存相關(guān)函數(shù),及Block所在的環(huán)境上下文狮斗;

Block的類型

前面已經(jīng)說過了绽乔,Block的本質(zhì)就是一個(gè)OC對(duì)象,既然它是OC對(duì)象情龄,那么它就有類型迄汛。

準(zhǔn)備工作:

  • 先把ARC關(guān)掉,因?yàn)锳RC幫我們做了太多的事骤视,不方便我們觀察結(jié)果鞍爱。關(guān)掉ARC的方法在Build Settings里面搜索Objective-C Automatic Reference Counting,把這一項(xiàng)置為NO专酗。
static int weight = 20;

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void (^block)(void) = ^{
            NSLog(@"%d %d", height, age);
        };
        
        NSLog(@"%@\n %@\n %@\n %@", [block class], [[block class] superclass], [[[block class] superclass] superclass], [[[[block class] superclass] superclass] superclass]);
        
        return 0;
    }
}

//打印結(jié)果
 __NSStackBlock__
 __NSStackBlock
 NSBlock
 NSObject
  • 這說明上面定義的這個(gè)block的類型是NSStackBlock睹逃,并且它最終繼承自NSObject也說明Block的本質(zhì)是OC對(duì)象。

Block的三種類型

  • Block有三種類型祷肯,分別是NSGlobalBlock,MallocBlock,NSStackBlock沉填。
    這三種類型的Block對(duì)象的存儲(chǔ)區(qū)域如下:
對(duì)象的存儲(chǔ)域
NSStackBlock(_NSConcreteStackBlock)
NSGlobalBlock(_NSConcreteGlobalBlock) 程序的數(shù)據(jù)區(qū)域(.data區(qū))
NSMallocBlock(_NSConcreteMallocBlock)

截獲了自動(dòng)變量的Block是NSStackBlock類型,沒有截獲自動(dòng)變量的Block則是NSGlobalStack類型,NSStackBlock類型的Block進(jìn)行copy操作之后其類型變成了NSMallocBlock類型佑笋。

  • 下面我們用一張圖來看一下不同block的存儲(chǔ)區(qū)域
Block的存儲(chǔ)區(qū)域
  • 從上圖可以發(fā)現(xiàn)翼闹,根據(jù)block的類型不同,block存放在不同的區(qū)域中蒋纬。
    1. 數(shù)據(jù)段中的__NSGlobalBlock__直到程序結(jié)束才會(huì)被回收猎荠,不過我們很少使用到__NSGlobalBlock__類型的block坚弱,因?yàn)檫@樣使用block并沒有什么意義。
    2. __NSStackBlock__類型的block存放在棧中关摇,我們知道棧中的內(nèi)存由系統(tǒng)自動(dòng)分配和釋放荒叶,作用域執(zhí)行完畢之后就會(huì)被立即釋放,而在相同的作用域中定義block并且調(diào)用block似乎也多此一舉输虱。
    3. __NSMallocBlock__是在平時(shí)編碼過程中最常使用到的些楣。存放在堆中需要我們自己進(jìn)行內(nèi)存管理。
  1. _NSConcreteGlobalBlock

    • 在以下兩種情況下使用 Block 的時(shí)候宪睹,BlockNSConcreteGlobalBlock類對(duì)象愁茁。
    1. 記述全局變量的地方,使用 Block 語法時(shí)亭病;
    2. Block 語法的表達(dá)式中沒有截獲的自動(dòng)變量時(shí)埋市。
    • NSConcreteGlobalBlock類的 Block 存儲(chǔ)在『程序的數(shù)據(jù)區(qū)域』。因?yàn)榇娣旁诔绦虻臄?shù)據(jù)區(qū)域命贴,所以即使在變量的作用域外,也可以通過指針安全的使用食听。

    • 記述全局變量的地方胸蛛,使用 Block 語法示例代碼:

    void (^myGlobalBlock)(void) = ^{
        printf("GlobalBlock\n");
    };
    
    int main() {
        myGlobalBlock();
    
        return 0;
    }
    

    通過對(duì)應(yīng) C++ 源碼,我們可以發(fā)現(xiàn):Block 結(jié)構(gòu)體的成員變量 isa賦值為:impl.isa = &_NSConcreteGlobalBlock;樱报,說明該 BlockNSConcreteGlobalBlock類對(duì)象葬项。

  2. _NSConcreteStackBlock

除了_NSConcreteGlobalBlock中提到的兩種情形,其他情形下創(chuàng)建的 Block 都是 NSConcreteStackBlock對(duì)象迹蛤,平常接觸的 Block 大多屬于 NSConcreteStackBlock對(duì)象民珍。

NSConcreteStackBlock類的 Block 存儲(chǔ)在『棧區(qū)』的。如果其所屬的變量作用域結(jié)束盗飒,則該 Block 就會(huì)被廢棄嚷量。如果 Block 使用了 __block變量,則當(dāng) __block變量的作用域結(jié)束逆趣,則 __block變量同樣被廢棄蝶溶。

棧上Block隨著作用域結(jié)束而廢棄
  1. _NSConcreteMallocBlock

為了解決棧區(qū)上的 Block 在變量作用域結(jié)束被廢棄這一問題,Block 提供了 『復(fù)制』功能宣渗《端可以將 Block 對(duì)象和 __block變量從棧區(qū)復(fù)制到堆區(qū)上。當(dāng) Block 從棧區(qū)復(fù)制到堆區(qū)后痕囱,即使棧區(qū)上的變量作用域結(jié)束時(shí)田轧,堆區(qū)上的 Block__block變量仍然可以繼續(xù)存在,也可以繼續(xù)使用鞍恢。

Block從棧復(fù)制到堆

此時(shí)傻粘,『堆區(qū)』上的 Block 為 NSConcreteMallocBlock 對(duì)象每窖,Block 結(jié)構(gòu)體的成員變量 isa 賦值為:impl.isa = &_NSConcreteMallocBlock;

那么,什么時(shí)候才會(huì)將 Block 從棧區(qū)復(fù)制到堆區(qū)呢抹腿?

這就涉及到了 Block 的自動(dòng)拷貝和手動(dòng)拷貝岛请。

Block的自動(dòng)拷貝和手動(dòng)拷貝

Block的自動(dòng)拷貝

在使用ARC 時(shí),大多數(shù)情形下編譯器會(huì)自動(dòng)進(jìn)行判斷警绩,自動(dòng)生成將 Block 從棧上復(fù)制到堆上的代碼:

  1. Block 作為函數(shù)返回值返回時(shí)崇败,會(huì)自動(dòng)拷貝;
  2. 向方法或函數(shù)的參數(shù)中傳遞 Block 時(shí)肩祥,使用以下兩種方法的情況下后室,會(huì)進(jìn)行自動(dòng)拷貝,否則就需要手動(dòng)拷貝:
    1. Cocoa 框架的方法且方法名中含有 usingBlock等時(shí)混狠;
    2. Grand Central Dispatch(GCD)的 API岸霹。
  3. Block 賦值給類的附有 __strong修飾符的id類型或 Block 類型成員變量時(shí)
Block的手動(dòng)拷貝

我們可以通過『copy 實(shí)例方法(即 alloc / new / copy / mutableCopy)』來對(duì) Block 進(jìn)行手動(dòng)拷貝。當(dāng)我們不確定 Block 是否會(huì)被遺棄将饺,需不需要拷貝的時(shí)候贡避,直接使用 copy 實(shí)例方法即可,不會(huì)引起任何的問題予弧。

關(guān)于 Block 不同類的拷貝效果總結(jié)如下:

Block 類 存儲(chǔ)區(qū)域 拷貝效果
_NSConcreteStackBlock 棧區(qū) 從椆伟桑拷貝到堆
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域 不做改變
_NSConcreteMallocBlock 堆區(qū) 引用計(jì)數(shù)增加
__block變量的拷貝

在使用 __block變量的 Block 從棧復(fù)制到堆上時(shí),__block變量也會(huì)受到如下影響:

__block 變量的配置存儲(chǔ)區(qū)域 Block 從棧復(fù)制到堆時(shí)的影響
堆區(qū) 從棧復(fù)制到堆掖蛤,并被 Block 所持有
棧區(qū) 被 Block 所持有

當(dāng)然杀捻,如果不再有 Block 引用該 __block變量,那么 __block變量也會(huì)被廢除蚓庭。

Block截獲變量實(shí)質(zhì)

我們下面見根據(jù)變量修飾符致讥,來探查 Block 如何捕獲不同修飾符的類型變量。

  • auto:自動(dòng)變量修飾符
  • static:靜態(tài)修飾符
  • const:常量修飾符

在這三種修飾符器赞,我們又細(xì)分為全局變量和局部變量垢袱。

Block截獲自動(dòng)局部變量的實(shí)質(zhì)

// 使用 Blocks 截獲局部變量值
int c = 30;//全局變量
- (void)useBlockInterceptLocalVariables {
    int a = 10, b = 20;//局部變量

    void (^myLocalBlock)(void) = ^{
        printf("a = %d, b = %d, c = %d\n",a, b, c);
    };
    void (^Block)(int, int, int) = ^(int a, int b, int c){
        printf("a = %d, b = %d, c = %d\n",a, b, c);
    };

    myLocalBlock();    // 輸出結(jié)果:a = 10, b = 20, c = 30

    a = 20;
    b = 30;

    myLocalBlock();    // 輸出結(jié)果:a = 10, b = 20, c = 30
    Block(a, b, c);              // 輸出結(jié)果:a = 20, b = 30, c = 30
}
Block塊中直接調(diào)用局部變量
  • 從中我們可以看出拳魁,我們?cè)诘谝淮握{(diào)用 myLocalBlock(); 之后已經(jīng)重新給變量 a惶桐、變量 b 賦值了,但是第二次調(diào)用 myLocalBlock(); 的時(shí)候潘懊,使用的還是之前對(duì)應(yīng)變量的值姚糊。

這是因?yàn)锽lock 語法的表達(dá)式使用的是它之前申明的局部變量a、變量b授舟。Blocks中救恨,Block表達(dá)式截獲所使用的局部變量的值,保存了該變量的瞬時(shí)值释树。所以再第二次執(zhí)行Block表達(dá)式的時(shí)候肠槽,即使已經(jīng)改變了局部變量ab的值擎淤,也不會(huì)影響B(tài)lock表達(dá)式在執(zhí)行時(shí)所保存的局部變量的瞬時(shí)值。

這就是Block變量截獲局部變量值的特性

  • ??:Block 語法表達(dá)式中沒有使用的自動(dòng)變量不會(huì)被追加到結(jié)構(gòu)體中秸仙,Blocks 的自動(dòng)變量截獲只針對(duì) Block使用的自動(dòng)變量嘴拢。

  • 可是,為什么 Blocks 變量使用的是局部變量的瞬時(shí)值寂纪,而不是局部變量的當(dāng)前值呢席吴?讓我們看一下對(duì)應(yīng)的C++代碼

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int a;
    int b;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    int b = __cself->b; // bound by copy

    printf("a = %d, b = %d, c = %d\n",a, b, c);
}

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)};


int main () {
    int a = 10, b = 20;

    void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
    ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);

    a = 20;
    b = 30;

    ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
}
  1. 可以看到 __main_block_impl_0 結(jié)構(gòu)體(Block 結(jié)構(gòu)體)中多了兩個(gè)成員變量 ab,這兩個(gè)變量就是 Block 截獲的局部變量捞蛋。 ab 的值來自與 __main_block_impl_0 構(gòu)造函數(shù)中傳入的值孝冒。
  struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        int a;    // 增加的成員變量 a
        int b;    // 增加的成員變量 b
        __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {    
            impl.isa = &_NSConcreteStackBlock;
            impl.Flags = flags;
            impl.FuncPtr = fp;
            Desc = desc;
        }
    };
  1. 還可以看出 __main_block_func_0(保存 Block 主體部分的結(jié)構(gòu)體)中,變量 a拟杉、b 的值使用的 __cself獲取的值庄涡。
    __cself->a__cself->b 是通過值傳遞的方式傳入進(jìn)來的搬设,而不是通過指針傳遞穴店。這也就說明了 ab 只是 Block 內(nèi)部的變量拿穴,改變 Block 外部的局部變量值迹鹅,并不能改變 Block 內(nèi)部的變量值。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    int b = __cself->b; // bound by copy
    printf("a = %d, b = %d\n",a, b);
}
  1. 我們可以看出全局變量并沒有存儲(chǔ)在Block的結(jié)構(gòu)體中贞言,而是在調(diào)用的時(shí)候通過直接訪問的方式來調(diào)用。
  • 下面用一張圖我們把上面所說的全局作用域和局部作用域做一個(gè)總結(jié)
變量類型 抓獲到Block對(duì)象內(nèi)部 訪問方式
局部變量 指傳遞
全局變量 直接訪問
Block通過傳值間接訪問局部變量
// 使用 Blocks 截獲局部變量值
int c = 30;
- (void)useBlockInterceptLocalVariables {
    int a = 10, b = 20;

    void (^Block)(void) = ^(int a, int b){
        printf("a = %d, b = %d\n",a, b);
    };

    a = 20;
    b = 30;
    Block(a,b);              // 輸出結(jié)果:a = 20, b = 30, c = 30
}
  • 我們來看看直接傳值和通過block截獲局部變量的區(qū)別
struct __main_block_impl_1 {
    struct __block_impl impl;
    struct __main_block_desc_1* Desc;
    __main_block_impl_1(void *fp, struct __main_block_desc_1 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_1(struct __main_block_impl_1 *__cself, int a, int b, int c) {

    printf("a = %d, b = %d, c = %d\n",a, b, c);
}

static struct __main_block_desc_1 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_1_DATA = { 0, sizeof(struct __main_block_impl_1)};


int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 10, b = 20;
                            
        void (*Block)(int,int) = ((void (*)(int, int))&__main_block_impl_1((void *)__main_block_func_1, &__main_block_desc_1_DATA));

        a = 20;
        b = 30;

        ((void (*)(__block_impl *, int, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, a, b, c);
    }
    return 0;
}
  1. 可以看見__main_block_impl_1結(jié)構(gòu)體中沒有變量a阀蒂、變量b,說明通過直接傳值的方式该窗,變量并沒有存進(jìn)Block的結(jié)構(gòu)體中。
  2. __main_block_func_1函數(shù)中蚤霞,發(fā)現(xiàn)參數(shù)列表中多了int a, int b這兩個(gè)參數(shù)酗失,還有調(diào)用Block的時(shí)候,直接把變量a昧绣、b的值傳入進(jìn)去了规肴。

Block截獲static修飾變量的實(shí)質(zhì)

下面我們定義了三個(gè)變量:

  • 全局
    • 變量:c
  • 局部
    • 常量:a
    • 變量:b
static int c = 30;
- (void)useBlockInterceptLocalVariables {
    static const int a = 10;
    static int b = 20;

    void (^Block)(void) = ^{
        b = 50;
        c = 60;
        printf("a = %d, b = %d, c = %d\n",a, b, c);
    };
  
    b = 30;
    c = 40;
    Block();                 // 輸出結(jié)果:a = 10, b = 50, c = 60
}
  • 從以上測試結(jié)果我們可以得出:
    • Block 對(duì)象能獲取最新的靜態(tài)全局變量靜態(tài)局部變量
    • 靜態(tài)局部常量由于值不會(huì)更改夜畴,故沒有變化拖刃;
  • 我們來看看c++代碼,到底發(fā)生了什么
static int c = 30;//全局靜態(tài)變量

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int *b;//捕獲變量贪绘,獲取變量地址
    const int *a;//捕獲變量兑牡,獲取變量地址
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_b, const int *_a, int flags=0) : b(_b), a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    //2.通過Block對(duì)象獲取到b和a的指針
    int *b = __cself->b; // bound by copy
    const int *a = __cself->a; // bound by copy
        //通過b指針,修改b指向的值
    (*b) = 50;
    c = 60;
    printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
}

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)};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        static const int a = 10;
        static int b = 20;
                            
                //1.傳入&a, &b地址進(jìn)行Blcok對(duì)象的初始化
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &b, &a));

        b = 30;
        c = 40;
        ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
    }
    return 0;
}
  1. 可以看到在 __main_block_impl_0 結(jié)構(gòu)體中税灌,靜態(tài)局部變量static int b以指針的形式添加為成員變量均函,而靜態(tài)局部常量static const int aconst int *的形式添加為成員變量亿虽。而全局靜態(tài)變量static int c 并沒有添加為成員變量
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int *b;//捕獲變量,獲取變量地址
    const int *a;//捕獲變量苞也,獲取變量地址
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_b, const int *_a, int flags=0) : b(_b), a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
  1. 再來看看 __main_block_func_0 結(jié)構(gòu)體部分洛勉,靜態(tài)全局變量static int c是直接訪問的,靜態(tài)局部變量static int b是通過『指針傳遞』的方式進(jìn)行訪問如迟,靜態(tài)局部常量static const int a也是通過『指針傳遞』的方式進(jìn)行訪問收毫,但是它是通過const修飾的無法進(jìn)行賦值操作。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    //2.通過Block對(duì)象獲取到b和a的指針
    int *b = __cself->b; // bound by copy
    const int *a = __cself->a; // bound by copy
        //通過b指針氓涣,修改b指向的值
    (*b) = 50;
    c = 60;
    printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
}
  • 我們?yōu)槭裁茨塬@取 static 變量最新的值牛哺?

    1. static 修飾的,其作用區(qū)域不管是全局還是局部劳吠,不管是常量還是變量引润,均存儲(chǔ)在全局存儲(chǔ)區(qū)中,存在全局存儲(chǔ)區(qū)痒玩,該地址在程序運(yùn)行過程中一直不會(huì)改變淳附,所以能訪問最新值。
    2. static 修飾后:
    • 全局變量蠢古,直接訪問奴曙。
    • 局部變量,指針訪問草讶。其中在局部變量中洽糟,又有局部靜態(tài)常量,即被const 修飾的堕战。
      • const 存放在 text 段中坤溃,即使被 static 同時(shí)修飾,也存放text 中的常量區(qū)嘱丢;

Block截獲const修飾變量的實(shí)質(zhì)

  • 如下定義:
    • const 全局變量:a
    • const 局部變量:b
const int a = 10;
- (void)useBlockInterceptLocalVariables {
    const int b = 20;

    void (^Block)(void) = ^{
        printf("a = %d, b = %d\n",a, b);
    };
  
    Block();                 // 輸出結(jié)果:a = 10, b = 20
}
  • 看看轉(zhuǎn)換后的c++代碼
static int a = 10;

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

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    const int b = __cself->b; // bound by copy
    printf("a = %d, b = %d\n",a, b);
}

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)};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        const int b = 20;
        void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, b));
        ((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);

    }
    return 0;
}
  • 從上面看出:

    • const 全局變量直接訪問薪介;
    • const 局部變量,其實(shí)仍然是 auto 修飾越驻,值傳遞汁政;
  • 最后我們用一張圖來總結(jié)一下這一節(jié)所學(xué)的內(nèi)容

Block不同作用域的訪問方式

Block截獲對(duì)象實(shí)質(zhì)

  • 在前一節(jié)我們學(xué)習(xí)了Block如何截獲基本類型,這一節(jié)我們主要學(xué)習(xí)截獲對(duì)象類型的auto變量
Person *person = [[Person alloc] init];
person.age = 20;
void (^block)(void) = ^{
  NSLog(@"age = %d", person.age);
};
block();
  • 根據(jù)Block捕獲基本變量的規(guī)律缀旁,針對(duì)對(duì)象记劈,仍然適用
    • auto 變量捕獲后,Block 中變量的類型和變量原類型一致并巍;
    • static 變量捕獲后抠蚣,Block 對(duì)應(yīng)的變量是對(duì)應(yīng)變量的指針類型;

那么履澳,auto 對(duì)象與基本類型在 Block 內(nèi)部有什么區(qū)別呢嘶窄。

  • 我們把上述代換轉(zhuǎn)換成C++
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    Person *person;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    Person *person = __cself->person; // bound by copy

    NSLog((NSString *)&__NSConstantStringImpl__var_folders_bp_sg6dyc5957s2j2v4l6z9k4dm0000gn_T_main_f5936b_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)person, sel_registerName("setAge:"), 20);
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
  1. 我們看到__main_block_impl_0結(jié)構(gòu)體中多了一個(gè)一個(gè)成員變量Person *person怀跛,因?yàn)?code>person是自動(dòng)變量,所以這里捕獲了自動(dòng)變量person作為_main_block_impl_0結(jié)構(gòu)體的成員變量柄冲。而且還要注意的是吻谋,由于是自動(dòng)變量,所以在block外面现横,自動(dòng)變量是什么類型漓拾,在結(jié)構(gòu)體里面作為成員變量就是什么類型。person在結(jié)構(gòu)體外面作為自動(dòng)變量是指針類型戒祠,所以作為結(jié)構(gòu)體里面的成員變量也是指針類型骇两。
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    Person *person;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *_person, int flags=0) : person(_person) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
  1. 我們看到__main_block_desc_0結(jié)構(gòu)中多了兩個(gè)函數(shù)指針void (*copy)void (*dispose)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

針對(duì)這兩個(gè)函數(shù),它們的作用就是:

函數(shù) 作用 調(diào)用時(shí)機(jī)
__main_block_copy_0 調(diào)用 _Block_object_assign姜盈,相當(dāng)于 retain低千,將對(duì)象賦值在對(duì)象類型的結(jié)構(gòu)體變量 __main_block_impl_0 中。 棧上的 Block 復(fù)制到堆時(shí)
__main_block_dispose_0 調(diào)用 _Block_object_dispose馏颂,相當(dāng)于 release示血,釋放賦值在對(duì)象類型的結(jié)構(gòu)體變量中的對(duì)象。 堆上 Block 被廢棄時(shí)

Blocks內(nèi)改寫被截獲變量的值的方式

在Block中修飾被截獲變量的值只有一下兩種情況救拉,我們先分析通過__block修飾符來修改截獲變量的方式

__block修飾符

  • __block 說明符類似于 static难审、autoregister 說明符亿絮,它們用于指定將變量值設(shè)置到哪個(gè)存儲(chǔ)域中告喊。例如auto 表示作為自動(dòng)變量存儲(chǔ)在中, static表示作為靜態(tài)變量存儲(chǔ)在數(shù)據(jù)區(qū)中派昧。

__block修飾符又分為修飾局部變量和修飾對(duì)象

__blcok修飾局部變量
- (void)useBlockQualifierChangeLocalVariables {
    __block int a = 10, b = 20;

    void (^myLocalBlock)(void) = ^{
        a = 20;
        b = 30;

        printf("a = %d, b = %d\n",a, b);    // 輸出結(jié)果:a = 20, b = 30
    };

    myLocalBlock();
}
  • 從中我們可以發(fā)現(xiàn):通過 __block 修飾的局部變量葱绒,可以在 Block 的主體部分中改變值。

  • 我們來轉(zhuǎn)換下源碼斗锭,分析一下:

struct __Block_byref_a_0 {
    void *__isa;
    __Block_byref_a_0 *__forwarding;
    int __flags;
    int __size;
    int a;
};

struct __Block_byref_b_1 {
    void *__isa;
    __Block_byref_b_1 *__forwarding;
    int __flags;
    int __size;
    int b;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_a_0 *a; // by ref
    __Block_byref_b_1 *b; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_a_0 *a = __cself->a; // bound by ref
    __Block_byref_b_1 *b = __cself->b; // bound by ref

    (a->__forwarding->a) = 20;
    (b->__forwarding->b) = 30;

    printf("a = %d, b = %d\n",(a->__forwarding->a), (b->__forwarding->b));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main() {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    __Block_byref_b_1 b = {(void*)0,(__Block_byref_b_1 *)&b, 0, sizeof(__Block_byref_b_1), 20};

    void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_b_1 *)&b, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);

    return 0;
}
  • 可以看到,只是加上了一個(gè) __block失球,代碼量就增加了很多岖是。

  • 我們從 __main_block_impl_0 開始說起:

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_a_0 *a; // by ref
    __Block_byref_b_1 *b; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
  • 我們?cè)?__main_block_impl_0結(jié)構(gòu)體中可以看到: 原 OC 代碼中,被 __block修飾的局部變量 __block int a实苞、__block int b分別變成了 __Block_byref_a_0豺撑、__Block_byref_b_1類型的結(jié)構(gòu)體指針 a、結(jié)構(gòu)體指針 b黔牵。這里使用結(jié)構(gòu)體指針 a聪轿、結(jié)構(gòu)體指針 b說明 _Block_byref_a_0__Block_byref_b_1類型的結(jié)構(gòu)體并不在 __main_block_impl_0結(jié)構(gòu)體中猾浦,而只是通過指針的形式引用陆错,這是為了可以在多個(gè)不同的 Block 中使用 __block修飾的變量灯抛。

  • __Block_byref_a_0__Block_byref_b_1 類型的結(jié)構(gòu)體聲明如下:

struct __Block_byref_a_0 {
    void *__isa;
    __Block_byref_a_0 *__forwarding;
    int __flags;
    int __size;
    int a;
};

struct __Block_byref_b_1 {
    void *__isa;
    __Block_byref_b_1 *__forwarding;
    int __flags;
    int __size;
    int b;
};
  • 拿第一個(gè) __Block_byref_a_0結(jié)構(gòu)體定義來說明音瓷,__Block_byref_a_0有 5 個(gè)部分:

    1. __isa:標(biāo)識(shí)對(duì)象類的 isa實(shí)例變量

    2. __forwarding:傳入變量的地址

    3. __flags:標(biāo)志位

    4. __size:結(jié)構(gòu)體大小

    5. a:存放實(shí)變量 a實(shí)際的值对嚼,相當(dāng)于原局部變量的成員變量(和之前不加__block修飾符的時(shí)候一致)。

  • 再來看一下 main()函數(shù)中绳慎,__block int a纵竖、__block int b的賦值情況。

__Block_byref_a_0 a = {
    (void*)0,
    (__Block_byref_a_0 *)&a, 
    0, 
    sizeof(__Block_byref_a_0), 
    10
};

__Block_byref_b_1 b = {
    0,
    &b, 
    0, 
    sizeof(__Block_byref_b_1), 
    20
};
  • 還是拿第一個(gè)__Block_byref_a_0 a的賦值來說明杏愤。

  • 可以看到 __isa指針值傳空靡砌,__forwarding指向了局部變量 a本身的地址,__flags分配了 0珊楼,__size為結(jié)構(gòu)體的大小通殃,a賦值為 10。下圖用來說明 __forwarding指針的指向情況亥曹。

__forwarding指向
  • 這下邓了,我們知道 __forwarding 其實(shí)就是局部變量 a 本身的地址,那么我們就可以通過 __forwarding 指針來訪問局部變量媳瞪,同時(shí)也能對(duì)其進(jìn)行修改了骗炉。

  • 來看一下 Block 主體部分對(duì)應(yīng)的 __main_block_func_0 結(jié)構(gòu)體來驗(yàn)證一下。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_a_0 *a = __cself->a; // bound by ref
    __Block_byref_b_1 *b = __cself->b; // bound by ref

    (a->__forwarding->a) = 20;
    (b->__forwarding->b) = 30;

    printf("a = %d, b = %d\n",(a->__forwarding->a), (b->__forwarding->b));
}

可以看到 (a->__forwarding->a) = 20;(b->__forwarding->b) = 30;是通過指針取值的方式來改變了局部變量的值蛇受。這也就解釋了通過 __block來修飾的變量句葵,在 Block 的主體部分中改變值的原理其實(shí)是:通過『指針傳遞』的方式。

__block修飾對(duì)象
- (void)useBlockQualifierChangeLocalVariables {
    __block Person *person = [[Person alloc] init];

    void(^block)(void) = ^ {
      person = [[Person alloc] init];//修改person創(chuàng)建的地址
      NSLog(@"person is %@", person);
    };
    block();
}
  • 把上述代碼轉(zhuǎn)換成C++
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

struct __Block_byref_person_0 {
    void *__isa;
    __Block_byref_person_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    Person *person;
};

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_person_0 *person; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,         __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_person_0 *person = __cself->person; // bound by ref

    (person->__forwarding->person) = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_bp_sg6dyc5957s2j2v4l6z9k4dm0000gn_T_main_1f2ea2_mi_0, (person->__forwarding->person));
}

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
       __attribute__((__blocks__(byref))) 
       __Block_byref_person_0 person = {
           (void*)0,
           (__Block_byref_person_0 *)&person,
           33554432,
           sizeof(__Block_byref_person_0), 
             __Block_byref_id_object_copy_131,
           __Block_byref_id_object_dispose_131,
           ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"),
           sel_registerName("alloc")),
           sel_registerName("init"))
       };

       void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}
  • 我們先從 __main_block_impl_0 開始說起:
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_person_0 *person; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
  • 我們?cè)?__main_block_impl_0 結(jié)構(gòu)體中可以看到: 原 OC代碼中兢仰,被 __block 修飾的person變成了 __Block_byref_person_0類型結(jié)構(gòu)體指針
  • __Block_byref_person_0類型結(jié)構(gòu)體聲明和該結(jié)構(gòu)體初始化如下:
struct __Block_byref_person_0 {
    void *__isa;
    __Block_byref_person_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    Person *person;
};

__Block_byref_person_0 person = {
           (void*)0,
           (__Block_byref_person_0 *)&person,
           33554432,
           sizeof(__Block_byref_person_0), 
             __Block_byref_id_object_copy_131,
           __Block_byref_id_object_dispose_131,
           ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"),
           sel_registerName("alloc")),
           sel_registerName("init"))
       };


  • 我們發(fā)現(xiàn)乍丈,在_Block_byref_person_0中多了兩個(gè)函數(shù),通過其初始化可以知道這兩個(gè)函數(shù)分別是__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131這兩個(gè)函數(shù)
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
  • 這兩個(gè)函數(shù)其實(shí)和_main_block_copy_0_main_block_dispose_0一樣把将,最終都是調(diào)用_Block_object_assign_Block_object_dispose這兩個(gè)函數(shù)轻专。那么這里為什么都加上了40呢俏讹?我們分析一下_Block_byref_person_0的結(jié)構(gòu):
struct __Block_byref_person_0 {
    void *__isa;    //指針苏章,8字節(jié)
    __Block_byref_person_0 *__forwarding; //指針,8字節(jié)
    int __flags; //int型见咒,4字節(jié)
    int __size;  //int型洽议,4字節(jié)
    void (*__Block_byref_id_object_copy)(void*, void*);//指針型宗收,8字節(jié)
    void (*__Block_byref_id_object_dispose)(void*);//指針型,8字節(jié)
    Person *person;
};
  • 這樣一來亚兄,_Block_byref_person_0的地址和person指針的地址就相差40字節(jié)混稽,所以+40的目的就是找到person指針。

更改特殊區(qū)域變量值

  • C語言中有幾種特殊區(qū)域的變量,允許 Block 改寫值匈勋,具體如下:
    • 靜態(tài)局部變量
    • 靜態(tài)全局變量
    • 全局變量

下面我們通過代碼來看看這種情況

  • OC代碼
int global_val = 10; // 全局變量
static int static_global_val = 20; // 靜態(tài)全局變量

int main() {
    static int static_val = 30; // 靜態(tài)局部變量

    void (^myLocalBlock)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *= 3;

        printf("static_val = %d, static_global_val = %d, global_val = %d\n",static_val, static_global_val, static_val);
    };

    myLocalBlock();

    return 0;
}
  • C++代碼
int global_val = 10;
static int static_global_val = 20;

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

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int *static_val = __cself->static_val; // bound by copy
    global_val *= 1;
    static_global_val *= 2;
    (*static_val) *= 3;

    printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, (*static_val));
}

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)};

int main() {
    static int static_val = 30;

    void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
    ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);

    return 0;

}
  • 從中可以看到:
    • __main_block_impl_0 結(jié)構(gòu)體中礼旅,將靜態(tài)局部變量 static_val 以指針的形式添加為成員變量,而靜態(tài)全局變量 static_global_val颓影、全局變量 global_val 并沒有添加為成員變量各淀。
int global_val = 10;
static int static_global_val = 20;

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int *static_val;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
  • 再來看一下 Block 主體部分對(duì)應(yīng)的 __main_block_func_0 結(jié)構(gòu)體部分。靜態(tài)全局變量 static_global_val诡挂、全局變量 global_val 是直接訪問的碎浇,而靜態(tài)局部變量 static_val 則是通過『指針傳遞』的方式進(jìn)行訪問和賦值。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int *static_val = __cself->static_val; // bound by copy
    global_val *= 1;
    static_global_val *= 2;
    (*static_val) *= 3;

    printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, (*static_val));
}
  • 靜態(tài)變量的這種方法似乎也適用于自動(dòng)變量的訪問璃俗,但我們?yōu)槭裁礇]有這么做呢奴璃?

    實(shí)際上,在由 Block 語法生成的值 Block 上城豁,可以存有超過其變量域的被截獲對(duì)象的自動(dòng)變量苟穆。變量作用域結(jié)束的同時(shí),原來的自動(dòng)變量被廢棄唱星,因此 Block 中超過變量作用域而存在的變量同靜態(tài)變量一樣雳旅,將不能通過指針訪問原來的自動(dòng)變量。

  • 最后我們用一張圖來總結(jié)一下這一節(jié)所學(xué)

block捕獲變量類型

__block變量內(nèi)存管理

  • 我們先回顧一下之前的一些捕獲變量或?qū)ο笫侨绾喂芾韮?nèi)存的间聊。

  • 注:下面 “干預(yù)” 是指不用程序員手動(dòng)管理攒盈,其實(shí)本質(zhì)還是要系統(tǒng)管理內(nèi)存的分配與釋放。

    • auto 局部基本類型變量哎榴,因?yàn)槭侵祩鬟f型豁,內(nèi)存是跟隨 Block,不用干預(yù)尚蝌;
    • static 局部基本類型變量迎变,指針傳遞,由于分配在靜態(tài)區(qū)飘言,故不用干預(yù)衣形;
    • 全局變量,存儲(chǔ)在數(shù)據(jù)區(qū)姿鸿,不用多說谆吴,不用干預(yù);
    • 局部對(duì)象變量般妙,如果在棧上,不用干預(yù)相速。但 Block 在拷貝到堆的時(shí)候碟渺,對(duì)其 retain,在 Block 對(duì)象銷毀時(shí),對(duì)其 release苫拍;

在這里芜繁,__block 變量呢?

注意點(diǎn)就是:__block 變量在轉(zhuǎn)換后封裝成了一個(gè)新對(duì)象绒极,內(nèi)存管理會(huì)多出一層骏令。

基本類型的Desc

上述 age 是基本類型,其轉(zhuǎn)換后的結(jié)構(gòu)體為:

struct __Block_byref_age_0 {
    void *__isa;    
    __Block_byref_age_0 *__forwarding;
    int __flags;
    int __size;
    int age;
};

Block 中的 Desc 如下:

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

//下面兩個(gè)方法是Block內(nèi): 內(nèi)存管理相關(guān)函數(shù)
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_dispose((void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);
}

針對(duì)基本類型垄提,以 age 類型為例:

  • __Block_byref_age_0 對(duì)象同樣是在 Block對(duì)象從棧上拷貝到堆上榔袋,進(jìn)行 retain
  • 當(dāng) Block對(duì)象銷毀時(shí)铡俐,對(duì)__Block_byref_age_0 進(jìn)行 release凰兑;
  • __Block_byref_age_0 內(nèi) age,由于是基本類型审丘,是不用進(jìn)行內(nèi)存手動(dòng)干預(yù)的吏够。

對(duì)象類型的Desc

下面看__block 對(duì)象類型的轉(zhuǎn)換:

struct __Block_byref_person_1 {
    void *__isa;        //地址0---占用8字節(jié)
    __Block_byref_person_1 *__forwarding;   //地址8-16---占用8字節(jié)
    int __flags;        //地址16-20---占用4字節(jié)
    int __size;         //地址20-24---占用8字節(jié)
    void (*__Block_byref_id_object_copy)(void*, void*); //地址24-32---占用8字節(jié)
    void (*__Block_byref_id_object_dispose)(void*);     //地址32-40---占用8字節(jié)
    BFPerson *person;   //地址40-48---占用8字節(jié)
};
  • 因?yàn)椴东@的本身是一個(gè)對(duì)象類型,所以該對(duì)象類型還需要進(jìn)行內(nèi)存的干預(yù)滩报。

  • 這里有兩個(gè)熟悉的函數(shù)锅知,即用于管理對(duì)象 auto 變量時(shí),我們見過脓钾,用于管理對(duì)象 auto 的內(nèi)存:

void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
  • 那么這兩個(gè)函數(shù)對(duì)應(yīng)的實(shí)現(xiàn)售睹,我們也找出來:

初始化__block對(duì)象

下面針對(duì)轉(zhuǎn)換來轉(zhuǎn)換去的細(xì)節(jié)做了刪減,方便閱讀:

struct __Block_byref_person_1 person = {
    0,  //isa
    &person,    //__forwarding
    33554432,   //__flags
    sizeof(__Block_byref_person_1),     //__size
    __Block_byref_id_object_copy_131,  //(*__Block_byref_id_object_copy)
    __Block_byref_id_object_dispose_131,// (*__Block_byref_id_object_dispose)
    (objc_msgSend)((objc_msgSend)(objc_getClass("BFPerson"), sel_registerName("alloc")), sel_registerName("init"))
};

//注意此處+40字節(jié)
static void __Block_byref_id_object_copy_131(void *dst, void *src
{
        _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) 
{
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
  • 我們注意觀察惭笑,在__Block_byref_id_object_copy_131__Block_byref_id_object_dispose_131 函數(shù)中侣姆,都會(huì)偏移 40 字節(jié),我們?cè)倏?code>__block BFPerson 對(duì)象轉(zhuǎn)換后的__Block_byref_person_1 結(jié)構(gòu)體發(fā)現(xiàn)沉噩,其 40 字節(jié)偏移處就是原本的 BFPerson *person 對(duì)象捺宗。

對(duì)象類型的內(nèi)存管理

BFPerson *person,在__block 修飾后川蒙,轉(zhuǎn)換為:__Block_byref_person_1 對(duì)象:

  • __Block_byref_person_1 對(duì)象同樣是在 Block對(duì)象從棧上拷貝到堆上蚜厉,進(jìn)行 retain
    • 當(dāng)__Block_byref_person_1 進(jìn)行 retain 同時(shí)畜眨,會(huì)將 person 對(duì)象進(jìn)行 retain
  • 當(dāng) Block對(duì)象銷毀時(shí)昼牛,對(duì)__Block_byref_person_1 進(jìn)行 release
    • 當(dāng)__Block_byref_person_1 對(duì)象 release 時(shí),會(huì)將 person 對(duì)象 release

與auto對(duì)象變量的區(qū)別

auto對(duì)象變量與_block變量對(duì)比

從棧到堆

Block 從棧復(fù)制到堆時(shí)康聂,__block 變量產(chǎn)生的影響如下:

__block 變量的配置存儲(chǔ)域 Block 從棧復(fù)制到堆的影響
從棧復(fù)制到堆贰健,并被 Block 持有
被 Block 持有

Block從棧拷貝到堆

block從椞裰拷貝到堆

當(dāng)有多個(gè) Block 對(duì)象伶椿,持有同一個(gè)__block 變量。

  • 當(dāng)其中任何 Block 對(duì)象復(fù)制到堆上,__block 變量就會(huì)復(fù)制到堆上脊另。
  • 后續(xù)导狡,其他 Block 對(duì)象復(fù)制到堆上,__block 對(duì)象引用計(jì)數(shù)會(huì)增加偎痛。
  • Block 復(fù)制到堆上的對(duì)象旱捧,持有__block 對(duì)象。

Block銷毀

Block銷毀

總結(jié)

__block變量的內(nèi)存管理

更多細(xì)節(jié)

__block捕獲變量存放在哪踩麦?

__block int age = 20;
__block BFPerson *person = [[BFPerson alloc] init];

void(^block)(void) = ^ {
    age = 30;
    person = [[BFPerson alloc] init];
    NSLog(@"malloc address: %p %p", &age, person);
    NSLog(@"malloc age is %d", age);
    NSLog(@"person is %@", person);
};
block();
NSLog(@"stack address: %p %p", &age, person);
NSLog(@"stack age is %d", age);

//輸出結(jié)果
Block-test[12866:2303749] malloc address: 0x100610bf8 0x100612ff0
Block-test[12866:2303749] malloc age is 30
Block-test[12866:2303749] person is <Person: 0x100612ff0>
Block-test[12866:2303749] stack address: 0x100610bf8 0x100612ff0
Block-test[12866:2303749] stack age is 30

可以看到枚赡,不管是 age 還是 person,均在堆空間靖榕。

其實(shí)标锄,本質(zhì)上,將 Block 從椬录疲拷貝到堆料皇,也會(huì)將__block 對(duì)象一并拷貝到堆,如下圖:

block從棧到堆細(xì)節(jié)

對(duì)象與__block變量的區(qū)別

__block BFPerson *blockPerson = [[BFPerson alloc] init];
BFPerson *objectPerson = [[BFPerson alloc] init];
void(^block)(void) = ^ {
    NSLog(@"person is %@ %@", blockPerson, objectPerson);
};

轉(zhuǎn)換后:

//Block對(duì)象
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    BFPerson *objectPerson;         //objectPerson對(duì)象星压,捕獲
    __Block_byref_blockPerson_0 *blockPerson; // blockPerson封裝后的對(duì)象践剂,內(nèi)部捕獲blockPerson
};

//__block blockPerson封裝后的對(duì)象
struct __Block_byref_blockPerson_0 {
    void *__isa;
    __Block_byref_blockPerson_0 *__forwarding;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    BFPerson *blockPerson;
};

//兩種對(duì)象不同的處理方式
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->blockPerson, (void*)src->blockPerson, 8/*BLOCK_FIELD_IS_BYREF*/);
    _Block_object_assign((void*)&dst->objectPerson, (void*)src->objectPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

//__Block_byref_blockPerson_0內(nèi)部對(duì)__block blockPerson的處理
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

從上面可以得出

__block變量和對(duì)象的區(qū)別
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市娜膘,隨后出現(xiàn)的幾起案子逊脯,更是在濱河造成了極大的恐慌,老刑警劉巖竣贪,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件军洼,死亡現(xiàn)場離奇詭異,居然都是意外死亡演怎,警方通過查閱死者的電腦和手機(jī)匕争,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來爷耀,“玉大人甘桑,你說我怎么就攤上這事〈醵#” “怎么了跑杭?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長咆耿。 經(jīng)常有香客問我德谅,道長,這世上最難降的妖魔是什么萨螺? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任窄做,我火速辦了婚禮宅荤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘浸策。我一直安慰自己,他們只是感情好惹盼,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布庸汗。 她就那樣靜靜地躺著,像睡著了一般手报。 火紅的嫁衣襯著肌膚如雪蚯舱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天掩蛤,我揣著相機(jī)與錄音枉昏,去河邊找鬼。 笑死揍鸟,一個(gè)胖子當(dāng)著我的面吹牛兄裂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播阳藻,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼晰奖,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了腥泥?” 一聲冷哼從身側(cè)響起匾南,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛔外,沒想到半個(gè)月后蛆楞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡夹厌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年豹爹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尊流。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帅戒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出崖技,到底是詐尸還是另有隱情逻住,我是刑警寧澤,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布迎献,位于F島的核電站瞎访,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吁恍。R本人自食惡果不足惜扒秸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一播演、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伴奥,春花似錦写烤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至尼啡,卻和暖如春暂衡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背崖瞭。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來泰國打工狂巢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人书聚。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓唧领,卻偏偏與公主長得像,于是被迫代替她去往敵國和親雌续。 傳聞我的和親對(duì)象是個(gè)殘疾皇子疹吃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

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

  • Blocks Blocks Blocks 是帶有局部變量的匿名函數(shù) 截取自動(dòng)變量值 int main(){ ...
    南京小伙閱讀 929評(píng)論 1 3
  • Block作為Objective-C中閉包的實(shí)現(xiàn)在iOS開發(fā)中占有非常重要的地位,尤其是作為回調(diào)(callback...
    NotFunGuy閱讀 748評(píng)論 0 4
  • 1.Block的實(shí)現(xiàn) 我們?cè)诿钚邢螺斎隿lang -rewrite-objc 源代碼文件名就可以把含有block...
    雪山飛狐_91ae閱讀 374評(píng)論 0 2
  • 比起受傷西雀,更可怕的是一成不變的自己萨驶。
    塵零閱讀 133評(píng)論 0 0
  • 不是所有的癡情都有結(jié)果。 小陳 曾經(jīng)艇肴,我想和你分享我的所有秘密腔呜,但現(xiàn)在,你成了我心底的秘密再悼。 01 你瞞住所有人在...
    小陳物語閱讀 167評(píng)論 0 1