Block 的應(yīng)用漓穿、循環(huán)引用嗤军、底層

一、block的語(yǔ)法
 返回類型(^block名稱)(參數(shù)類型) = ^返回類型(變量類型 變量名稱){實(shí)現(xiàn)}

直接定義block時(shí)晃危,可以省略定義時(shí)的返回類型,即

返回類型(^block名稱)(參數(shù)類型) = ^(變量類型 變量名稱){實(shí)現(xiàn)}

若參數(shù)類型為void,可省略寫成

返回類型(^block名稱)(void) = ^{實(shí)現(xiàn)}

匿名block:block定義時(shí)孟抗,等號(hào)右邊的即為匿名block

1.2 typedef簡(jiǎn)化block的聲明

typedef 返回類型(^block名稱)(參數(shù)類型);

1.3 block是個(gè)對(duì)象

NSLog(@"%@",block);

輸出這個(gè)block疫诽,通過這個(gè)%@ 我們可以看出block其實(shí)是個(gè)對(duì)象,通過輸出結(jié)果__NSGlobalBlock__可以看出鳍鸵,該block存儲(chǔ)在全局區(qū)

二苇瓣、block的循環(huán)應(yīng)用

解決block循環(huán)引用的三種方法
1、__weak來解決

    self.name = @"hello";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",strongSelf.name);
        });
    };
    
    block();

ps : 其實(shí)很多時(shí)候偿乖,__strong會(huì)被省略掉击罪,這樣在一定情形下,會(huì)發(fā)生數(shù)據(jù)丟失贪薪。
比如外邓,我們進(jìn)入一個(gè)頁(yè)面,代碼執(zhí)行block古掏,接著在不過三秒的情況下损话,退出該頁(yè)面。如果不用__strong來修飾,二級(jí)頁(yè)面銷毀后丧枪,其成員變量name也跟著銷毀釋放了光涂,這樣在三秒后執(zhí)行的這個(gè)線程里,name這個(gè)成員變量拧烦,通過getter方法訪問忘闻,返回結(jié)果會(huì)是null

2、__block

    self.name = @"hello";
    __block ViewController *vc = self;

    self.block = ^{
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
            vc = nil; // self -> block -> vc(nil) ->(斷開循環(huán)引用) block
        });
    };
    
    block();

3恋博、通過block參數(shù)解決

    self.name = @"hello";
    self.block = ^(ViewController *vc){
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@",vc.name);
        });
    };
    
    self.block(self);
三齐佳、分類
  • 棧block
    _NSConcreteStackBlock :保存在棧中的 block,當(dāng)函數(shù)返回時(shí)會(huì)被銷毀债沮。

  • 堆block
    _NSConcreteMallocBlock
    保存在堆中的 block炼吴,當(dāng)引用計(jì)數(shù)為 0 時(shí)會(huì)被銷毀。

  • 全局的block
    _NSConcreteGlobalBlock:全局的靜態(tài) block疫衩,在block中不訪問外部局部變量硅蹦,可以訪問外部全局變量和靜態(tài)變量。此時(shí)為NSGlobalBlock闷煤。

三童芹、代碼+底層分析
  • 通過clang命令查看編譯器是如何實(shí)現(xiàn)Block的,在終端輸入clang -rewrite-objc main.m鲤拿,然后會(huì)在當(dāng)前目錄生成main.cpp的C++文件假褪,但是,這一步可能會(huì)報(bào)錯(cuò)近顷,如下:
In file included from /Users/apple/Desktop/Block_test/Block_test/ViewController.m:8:
/Users/apple/Desktop/Block_test/Block_test/ViewController.h:8:9: fatal error: 
      'UIKit/UIKit.h' file not found
#import <UIKit/UIKit.h>
        ^~~~~~~~~~~~~~~
1 error generated.
  • 沒關(guān)系嗜价,可以試試這個(gè)方法感覺還是挺管用的,cd到當(dāng)前的文件目錄下幕庐,然后將之前執(zhí)行的命令替換成為:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk  XXX.m文件

成功:


截屏2021-04-26 下午5.25.49.png

1久锥、Demo1

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];    

    void(^block)(void) = ^{        

        NSLog(@"hello world!");        

    };
    block();    

    // Do any additional setup after loading the view.
}
@end
  • 底層代碼的實(shí)現(xiàn):
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  // 構(gòu)造函數(shù)
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_db_80twdv6s18s_zk2z35wwh1cw0000gn_T_ViewController_8cc697_mi_0);
}

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

    void(*block)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
   
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);


}
  • 為了方便我們閱讀代碼,我們先熟悉幾個(gè)規(guī)律:
    ①异剥、C++中 結(jié)構(gòu)體的名稱瑟由、方法體的名稱,一般可以看成是一層一層拼接的
    ②冤寿、xxx_0的意思歹苦,這個(gè)0 代表的是 第一個(gè),也就是首個(gè)
    ③督怜、C++的強(qiáng)轉(zhuǎn)換比較多殴瘦,可以對(duì)代碼進(jìn)行一定量的刪除操作。

  • 代碼分析
    通過底層我們可以看出号杠,block的C++實(shí)現(xiàn)蚪腋,是一個(gè)結(jié)構(gòu)體丰歌,有兩個(gè)成員變量(impl* Desc)和一個(gè)構(gòu)造函數(shù)(對(duì)結(jié)構(gòu)體進(jìn)行初始化的函數(shù))屉凯。

接著我們看看他的兩個(gè)成員變量:

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

__block_impl也是一個(gè)結(jié)構(gòu)體

*isa:isa指針立帖,指向一個(gè)類對(duì)象,有三種類型:_NSConcreteStackBlock悠砚、_NSConcreteGlobalBlock晓勇、_NSConcreteMallocBlock。
Flags:block 的負(fù)載信息(引用計(jì)數(shù)和類型信息)灌旧,按位存儲(chǔ)绑咱。
Reserved:保留變量。
*FuncPtr:一個(gè)指針枢泰,指向Block執(zhí)行時(shí)調(diào)用的函數(shù)描融,也就是Block需要執(zhí)行的代碼塊。在本例中是__ViewController__viewDidLoad_block_func_0函數(shù)宗苍。

__ViewController__viewDidLoad_block_desc_0
static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

__ViewController__viewDidLoad_block_desc_0是一個(gè)結(jié)構(gòu)體,包含兩個(gè)成員變量:

reserved:Block版本升級(jí)所需的預(yù)留區(qū)空間薄榛,在這里為0讳窟。
Block_size:Block大小(sizeof(struct __blockTest_block_impl_0))。
__ViewController__viewDidLoad_block_desc_0_DATA是一個(gè)__ViewController__viewDidLoad_block_desc_0的一個(gè)實(shí)例敞恋。

__ViewController__viewDidLoad_block_func_0

__ViewController__viewDidLoad_block_func_0就是Block的執(zhí)行時(shí)調(diào)用的函數(shù)丽啡,參數(shù)是一個(gè)__ViewController__viewDidLoad_block_impl_0類型的指針。

static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_db_80twdv6s18s_zk2z35wwh1cw0000gn_T_ViewController_8cc697_mi_0);
}
ViewDidLoad方法
static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {

    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));

    void(*block)(void) = ((void (*)())&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
   
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

代碼簡(jiǎn)化硬猫,并進(jìn)行一一對(duì)應(yīng)

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    
    // [super viewDidLoad];
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    // void(^block)(void) = ^{NSLog(@"hello world");};
    void(*block)(void) = &__ViewController__viewDidLoad_block_impl_0
                         (
                           __ViewController__viewDidLoad_block_func_0,
                           &__ViewController__viewDidLoad_block_desc_0_DATA
                         );
    // block();
    block->FuncPtr(block);
}
  • 第二行代碼:定義了Block补箍。

我們看到block變成了一個(gè)指針,指向一個(gè)通過__ViewController__viewDidLoad_block_impl_0構(gòu)造函數(shù)實(shí)例化的結(jié)構(gòu)體__ViewController__viewDidLoad_block_impl_0實(shí)例啸蜜,__ViewController__viewDidLoad_block_impl_0在初始化的時(shí)候需要兩個(gè)個(gè)參數(shù):

__ViewController__viewDidLoad_block_func_0:Block塊的函數(shù)指針坑雅。
__ViewController__viewDidLoad_block_desc_0_DATA:作為靜態(tài)全局變量初始化__ViewController__viewDidLoad_block_desc_0的結(jié)構(gòu)體實(shí)例指針

  • 第三行代碼:調(diào)用了Block
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)通過block->FuncPtr指針找到__blockTest_block_func_0函數(shù)并且轉(zhuǎn)成(void (*)(__block_impl *))類型。
    ((__block_impl *)block)然后將block作為參數(shù)傳給這個(gè)函數(shù)調(diào)用衬横。
    簡(jiǎn)單點(diǎn)說就是裹粤,block調(diào)用xxxx_func_0方法,并將自己傳了過去蜂林。
補(bǔ)充:Flags遥诉,__block_impl的參數(shù)的用途

在這里Block_private.h可以看到Flags的具體信息:

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

引用淺談 block(1) - clang 改寫后的 block 結(jié)構(gòu)的解釋:

也就是說,一般情況下噪叙,一個(gè) block 的 flags 成員默認(rèn)設(shè)置為 0矮锈。如果當(dāng) block 需要 Block_copy()Block_release 這類拷貝輔助函數(shù),則會(huì)設(shè)置成 1 << 25 睁蕾,也就是 BLOCK_HAS_COPY_DISPOSE 類型苞笨。可以搜索到大量講述 Block_copy 方法的博文,其中涉及到了 BLOCK_HAS_COPY_DISPOSE 猫缭。

總結(jié)一下枚舉類的用法葱弟,前 16 位即起到標(biāo)記作用,又可記錄引用計(jì)數(shù):

  • BLOCK_DEALLOCATING:釋放標(biāo)記猜丹。一般常用 BLOCK_NEEDS_FREE 做 位與 操作芝加,一同傳入 Flags ,告知該 block 可釋放射窒。
  • BLOCK_REFCOUNT_MASK:一般參與判斷引用計(jì)數(shù)藏杖,是一個(gè)可選用參數(shù)。
  • BLOCK_NEEDS_FREE:通過設(shè)置該枚舉位脉顿,來告知該 block 可釋放蝌麸。意在說明 block 是 heap block ,即我們常說的 _NSConcreteMallocBlock 艾疟。
  • BLOCK_HAS_COPY_DISPOSE:是否擁有拷貝輔助函數(shù)(a copy helper function)来吩。
  • BLOCK_HAS_CTOR:是否擁有 block 析構(gòu)函數(shù)(dispose function)。
  • BLOCK_IS_GC:是否啟用 GC 機(jī)制(Garbage Collection)蔽莱。
  • BLOCK_HAS_SIGNATURE:與 BLOCK_USE_STRET 相對(duì)弟疆,判斷是否當(dāng)前 block 擁有一個(gè)簽名。用于 runtime 時(shí)動(dòng)態(tài)調(diào)用盗冷。
Demo1總結(jié)

以上是一個(gè)最基本的block的底層實(shí)現(xiàn)怠苔。到這里其實(shí)我們才走了第一步。你想當(dāng)我們使用__block 仪糖、__weak柑司、block調(diào)取外部變量又是什么情況呢?底層又是怎樣實(shí)現(xiàn)的锅劝?變量是如何捕獲的攒驰?

Demo2 探究block截獲變量
截獲auto變量值
image.png

我們看到直接在block修改變量會(huì)提示錯(cuò)誤,為什么呢故爵?

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

int main(int argc, char * argv[]) {
    @autoreleasepool {
        blockTest();
    }
}

打印結(jié)果是10讼育,clang改寫后的代碼如下:

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  int num;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
  int num = __cself->num; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_3c2714_mi_0,num);
    }
    
    void blockTest()
{
    int num = 10;
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, num));
    num = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

__blockTest_block_impl_0多了一個(gè)成員變量int num;,再看看構(gòu)造函數(shù)__blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0)稠集,可以看到第三個(gè)參數(shù)只是變量的值奶段,這也就解釋了為什么打印的是10,因?yàn)閎lock截獲的是值剥纷。并未截獲指針痹籍。

使用static修飾變量
void blockTest()
{
    static int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

可以在block內(nèi)部修改變量了,同時(shí)打印結(jié)果是20晦鞋,30蹲缠。clang改寫后的代碼如下:

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  int *num;
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0) : num(_num) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
  int *num = __cself->num; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_0,(*num));
        (*num) = 30;
    }
    
    void blockTest()
{
    static int num = 10;
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, &num));
    num = 20;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_1,num);
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

__blockTest_block_impl_0多了一個(gè)成員變量int *num;棺克,和上面不同的是,這次block截獲的是指針线定,所以可以在內(nèi)部通過指針修改變量的值娜谊,同時(shí)在外部修改變量的值,block也能"感知到"斤讥。那么為什么之前不傳遞指針呢纱皆?因?yàn)樽兞渴菞I希饔糜蚴呛瘮?shù)blockTest內(nèi)芭商,那么有可能變量比block先銷毀派草,這時(shí)候block再通過指針去訪問變量就會(huì)有問題。而static修飾的變量不會(huì)被銷毀铛楣,也就不用擔(dān)心近迁。

全局變量
int num = 10;

void blockTest()
{
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

打印結(jié)果是20,30簸州。clang改寫后的代碼如下:

int num = 10;


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

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_1875c6_mi_0,num);
        num = 30;
    }

非常簡(jiǎn)單鉴竭,在初始化__blockTest_block_impl_0并沒有把num作為參數(shù),__blockTest_block_func_0中也是直接訪問全局變量岸浑。

總結(jié):
變量類型 是否捕獲到block內(nèi)部 訪問方式
局部auto變量 值傳遞
局部static變量 指針傳遞
全局變量 直接訪問
使用__block修飾變量
void blockTest()
{
    __block int num = 10;
    void (^block)(void) = ^{
        NSLog(@"%d",num);
        num = 30;
    };
    num = 20;
    block();
    NSLog(@"%d",num);
}

效果和使用static修飾變量一樣搏存,clang改寫后的代碼如下:

struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 int num;
};

struct __blockTest_block_impl_0 {
  struct __block_impl impl;
  struct __blockTest_block_desc_0* Desc;
  __Block_byref_num_0 *num; // by ref
  __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) {
  __Block_byref_num_0 *num = __cself->num; // bound by ref

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_0,(num->__forwarding->num));
        (num->__forwarding->num) = 30;
    }
    
static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __blockTest_block_dispose_0(struct __blockTest_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __blockTest_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __blockTest_block_impl_0*, struct __blockTest_block_impl_0*);
  void (*dispose)(struct __blockTest_block_impl_0*);
} __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0), __blockTest_block_copy_0, __blockTest_block_dispose_0};

void blockTest()
{
    __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 10};
    void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
    (num.__forwarding->num) = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_1,(num.__forwarding->num));
}

哇,難受啊兄dei助琐,怎么多出來這么多東西祭埂,沒關(guān)系面氓,慢慢分析兵钮。

__blockTest_block_impl_0多出來一個(gè)成員變量__Block_byref_num_0 *num;,我們看到經(jīng)過__block修飾的變量類型變成了結(jié)構(gòu)體__Block_byref_num_0舌界,__blockTest_block_impl_0多出來一個(gè)成員變量__Block_byref_num_0 *num;掘譬,block捕獲的是__Block_byref_num_0類型指針,

__Block_byref_num_0
我們看到__Block_byref_num_0是一個(gè)結(jié)構(gòu)體呻拌,并且有一個(gè)isa葱轩,因此我們可以知道它其實(shí)就是一個(gè)對(duì)象。同時(shí)還有一個(gè)__Block_byref_a_0 *類型的__forwardingnum藐握,num我們能猜到就是用來保存變量的值靴拱,__forwarding就有一點(diǎn)復(fù)雜了,后面慢慢講猾普。

__blockTest_block_copy_0__blockTest_block_dispose_0

__blockTest_block_copy_0中調(diào)用的是_Block_object_assign袜炕,__blockTest_block_dispose_0中調(diào)用的是_Block_object_dispose

函數(shù) 調(diào)用時(shí)機(jī)
__blockTest_block_copy_0 __block變量結(jié)構(gòu)體實(shí)例從棾跫遥拷貝到堆時(shí)
__blockTest_block_dispose_0 __block變量結(jié)構(gòu)體實(shí)例引用計(jì)數(shù)為0時(shí)

關(guān)于_Block_object_assign_Block_object_dispose更詳細(xì)代碼可以在runtime.c 中查看偎窘。

// Runtime support functions used by compiler when generating copy/dispose helpers

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};
  • BLOCK_FIELD_IS_OBJECT:OC對(duì)象類型
  • BLOCK_FIELD_IS_BLOCK:是一個(gè)block
  • BLOCK_FIELD_IS_BYREF:在棧上被__block修飾的變量
  • BLOCK_FIELD_IS_WEAK:被__weak修飾的變量乌助,只在Block_byref管理內(nèi)部對(duì)象內(nèi)存時(shí)使用
  • BLOCK_BYREF_CALLER:處理Block_byref內(nèi)部對(duì)象內(nèi)存的時(shí)候會(huì)加的一個(gè)額外標(biāo)記(告訴內(nèi)部實(shí)現(xiàn)不要進(jìn)行retain或者copy)

__blockTest_block_desc_0
我們可以看到它多了兩個(gè)回調(diào)函數(shù)指針*copy*dispose,這兩個(gè)指針會(huì)被賦值為__main_block_copy_0__main_block_dispose_0

最后我們看到訪問num是這樣的:

__Block_byref_num_0 *num = __cself->num; // bound by ref   

(num->__forwarding->num) = 30;

下面就講一講為什么要這樣陌知。

Block的內(nèi)存管理

在前面我們講到__block_impl指向的_NSConcreteStackBlock類型的類對(duì)象他托,其實(shí)總共有三種類型:

類型 存儲(chǔ)區(qū)域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 數(shù)據(jù)區(qū)
_NSConcreteMallocBlock

前面也講到copydispose,在ARC環(huán)境下仆葡,有哪些情況編譯器會(huì)自動(dòng)將棧上的把Block從棧上復(fù)制到堆上呢赏参?

Block從棧中復(fù)制到堆
調(diào)用Block的copy實(shí)例方法時(shí)
Block作為函數(shù)返回值返回時(shí)
在帶有usingBlock的Cocoa方法或者GCD的API中傳遞Block時(shí)候
將block賦給帶有__strong修飾符的id類型或者Block類型時(shí)

當(dāng)Bock從棧中復(fù)制到堆,__block也跟著變化:

image.png

當(dāng)Block在棧上時(shí)浙芙,__block的存儲(chǔ)域是棧登刺,__block變量被棧上的Block持有。
當(dāng)Block被復(fù)制到堆上時(shí)嗡呼,會(huì)通過調(diào)用Block內(nèi)部的copy函數(shù)纸俭,copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)。此時(shí)__block變量的存儲(chǔ)域是堆南窗,__block變量被堆上的Block持有揍很。
當(dāng)堆上的Block被釋放,會(huì)調(diào)用Block內(nèi)部的dispose万伤,dispose函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose窒悔,堆上的__block被釋放。

image.png
  • 當(dāng)多個(gè)棧上的Block使用棧上的__block變量敌买,__block變量被棧上的多個(gè)Block持有简珠。
  • 當(dāng)Block0被復(fù)制到堆上時(shí),__block也會(huì)被復(fù)制到堆上虹钮,被堆上Block0持有聋庵。Block1仍然持有棧上的__block,原棧上__block變量的__forwarding指- 向拷貝到堆上之后的__block變量芙粱。
  • 當(dāng)Block1也被復(fù)制到堆上時(shí)祭玉,堆上的__block被堆上的Block0Block1只有,并且__block的引用計(jì)數(shù)+1春畔。
  • 當(dāng)堆上的Block都被釋放脱货,__block變量結(jié)構(gòu)體實(shí)例引用計(jì)數(shù)為0,調(diào)用_Block_object_dispose律姨,堆上的__block被釋放振峻。

下圖是描述__forwarding變化。這也就能解釋__forwarding存在的意義:

__forwarding 保證在棧上或者堆上都能正確訪問對(duì)應(yīng)變量

image.png
int main(int argc, char * argv[]) {

    int num = 10;

    NSLog(@"%@",[^{
        NSLog(@"%d",num);
    } class]);

    void (^block)(void) = ^{
        NSLog(@"%d",num);
    };

    NSLog(@"%@",[block class]);
}

打印結(jié)果:

2019-05-04 18:40:48.470228+0800 BlockTest[35824:16939613] __NSStackBlock__
2019-05-04 18:40:48.470912+0800 BlockTest[35824:16939613] __NSMallocBlock__
  • 我們可以看到第一個(gè)Block沒有賦值給__strong指針择份,而第二個(gè)Block賦值給__strong指針扣孟,所以第一個(gè)在棧上,而第二個(gè)在堆上缓淹。
Block截獲對(duì)象(對(duì)象類型的auto變量)

1哈打、定義一個(gè)簡(jiǎn)單的block:

  MJBlock block;
  {
    MJPerson *person = [[MJPerson alloc] init];
    person.age = 10;
    block = ^{
      NSLog(@"---------%d", person.age);
    };
  }
  NSLog(@"------");
  • 第二個(gè)nslog打印完成之后塔逃,person不會(huì)銷毀,因?yàn)閎lock有個(gè)指針指向了外面的person對(duì)象料仗,block在堆上湾盗,是malloc類型的。block不銷毀立轧,person也不會(huì)銷毀格粪。但是如果改成mrc環(huán)境,棧上的block不會(huì)強(qiáng)引用auto對(duì)象氛改。但是如果person用__weak修飾的話帐萎,person就會(huì)先銷毀。
    上面的block改成下面這樣:
        MJBlock block;
        {
            MJPerson *person = [[MJPerson alloc] init];
            person.age = 10;
            __weak MJPerson *weakPerson = person;
            block = ^{
                NSLog(@"---------%d", weakPerson.age);
            };
        }
        NSLog(@"------");
  • 這種情況下用之前的clang xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m轉(zhuǎn)變成底層c++代碼會(huì)報(bào)錯(cuò) cannot create __weak reference in file using manual reference 因?yàn)閣eak是弱引用是在runtime下進(jìn)行的胜卤,所以用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m轉(zhuǎn)換之后:
typedef void (*MJBlock)(void);
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  MJPerson *__weak person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MJPerson *__strong _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) {
  MJPerson *__strong person = __cself->person; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c41e64_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};

之前的MJPerson * person;也轉(zhuǎn)換成了MJPerson *__weak person;

總結(jié)如下:

  • 當(dāng)block內(nèi)部訪問了對(duì)象類型的auto變量時(shí)
  • 1疆导、如果block是在棧上,不論是強(qiáng)指針還是弱指針都不會(huì)去對(duì)auto對(duì)象進(jìn)行強(qiáng)引用葛躏。
  • 2澈段、如果block被拷貝到堆上。在arc環(huán)境下舰攒,當(dāng)一個(gè)block被強(qiáng)引用引用著败富,就會(huì)進(jìn)行copy操作,如果block進(jìn)行copy操作的時(shí)候會(huì)調(diào)用內(nèi)部的_Block_object_assign函數(shù) 摩窃,調(diào)用這個(gè)函數(shù)會(huì)根據(jù)外部的auto對(duì)象的修飾關(guān)鍵字MJPerson *__weak person或者M(jìn)JPerson *__strong person對(duì)auto進(jìn)行強(qiáng)引用或者弱引用
  • 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)釋放引用的auto變量,類似于release操作匪蟀。


    image.png

面試題:

    MJPerson *p = [[MJPerson alloc] init];
    __weak MJPerson *weakP = p;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"1-------%@", p);
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2-------%@", weakP);
        });
    });
    NSLog(@"-----------");

經(jīng)過nslog1跟nslog2位置的變換椎麦,得出一個(gè)結(jié)論宰僧,person的釋放時(shí)間是看person強(qiáng)引用什么時(shí)候釋放材彪,不用管弱引用釋放

block在ARC和MRC中的聲明引用有些區(qū)別.
  • block可以存儲(chǔ)在棧中,也可以在堆中
  • 默認(rèn)存儲(chǔ)在棧中,不需要管理內(nèi)存
  • 存儲(chǔ)在堆中的block會(huì)對(duì)block進(jìn)行retain操作
  • (MRC)block在堆中時(shí),不想對(duì)block進(jìn)行retain操作,前面加__block
  • (ARC)前面加__weak或__unsafe_unretained
  • __weak和__unsafe_unretained的區(qū)別:__weak則在釋放時(shí)會(huì)對(duì)對(duì)象賦值nil,后者不會(huì)
  • Block_copy使棧中的block轉(zhuǎn)移到堆中,并對(duì)block會(huì)引用的對(duì)象進(jìn)行retain操作
  • 避免block引用的對(duì)象進(jìn)行retain操作,在引用對(duì)象聲明時(shí)前面加__block
ARC下獲取引用計(jì)數(shù)(retain count)

注意:以下方法只可用于debug,而且在多線程等情況下返回值不是100%可信琴儿。

1.使用KVC

[obj valueForKey:@"retainCount"]

2.使用私有API

OBJC_EXTERN int _objc_rootRetainCount(id);
_objc_rootRetainCount(obj)

3.使用CFGetRetainCount

CFGetRetainCount((__bridge CFTypeRef)(obj))

出處
鏈接:http://www.reibang.com/p/221d0778dcaa
鏈接:http://www.reibang.com/p/60c0bc161201
鏈接:https://blog.csdn.net/weixin_37547351/article/details/105106559
鏈接:https://blog.csdn.net/zhangwenhai001/article/details/46702271
鏈接:https://blog.csdn.net/iuyo89007/article/details/51761720/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末段化,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子造成,更是在濱河造成了極大的恐慌显熏,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晒屎,死亡現(xiàn)場(chǎng)離奇詭異喘蟆,居然都是意外死亡缓升,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門蕴轨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來港谊,“玉大人,你說我怎么就攤上這事橙弱∑缢拢” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵棘脐,是天一觀的道長(zhǎng)斜筐。 經(jīng)常有香客問我,道長(zhǎng)蛀缝,這世上最難降的妖魔是什么顷链? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮屈梁,結(jié)果婚禮上蕴潦,老公的妹妹穿的比我還像新娘。我一直安慰自己俘闯,他們只是感情好潭苞,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著真朗,像睡著了一般此疹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上遮婶,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天蝗碎,我揣著相機(jī)與錄音,去河邊找鬼旗扑。 笑死蹦骑,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的臀防。 我是一名探鬼主播眠菇,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼袱衷!你這毒婦竟也來了捎废?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤致燥,失蹤者是張志新(化名)和其女友劉穎登疗,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辐益,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年断傲,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片智政。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡艳悔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出女仰,到底是詐尸還是另有隱情猜年,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布疾忍,位于F島的核電站乔外,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏一罩。R本人自食惡果不足惜杨幼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望聂渊。 院中可真熱鬧差购,春花似錦、人聲如沸汉嗽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)饼暑。三九已至稳析,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間弓叛,已是汗流浹背彰居。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留撰筷,地道東北人陈惰。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像毕籽,于是被迫代替她去往敵國(guó)和親抬闯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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