今天看啥  ›  专栏  ›  蒋斌文

OC-Block的本质(四)——__block的变量的内存管理分析

蒋斌文  · 简书  ·  · 2021-04-25 20:51
image-20210425144930404
image-20210425144952426
image-20210425145018734
image-20210425145043402
image-20210425145117904
image-20210425145138231

__block的使用

typedef void (^MJBlock)(void);

int main(int argc, const char * argv[]) {
        @autoreleasepool {
        int age = 10;
        MJBlock block1 = ^{
            age = 20;
        };
    }
    return 0;
}
image-20210425143321853

Variable is not assignable (missing __block type specifier)

在block内部是无法修改局部变量的.

怎么样才能在 block 内部修改外部变量呢?有三种方法:
1:使用 static 修饰 age
2:把 age 变成全局变量
3:使用 __block 修饰 age

如果想在 block 内部修改从外部捕获的 auto 变量的值,加上关键字 __block 。代码如下

typedef void (^MJBlock)(void);

int main(int argc, const char * argv[]) {
     @autoreleasepool {
        __block int age = 10;
        MJBlock block1 = ^{
            age = 20;
            NSLog(@"age is %d", age);
        };
        block1();
        NSLog(@"%@", [block1 class]);
        NSLog(@"MJBlock执行完之后,age = %d",age);
    }
    return 0;
}

RUN>

*********************** 运行结果 **************************
2021-04-25 14:45:23.221612+0800 Interview04-__block[2888:128162] age is 20
2021-04-25 14:45:23.222238+0800 Interview04-__block[2888:128162] __NSMallocBlock__
2021-04-25 14:45:23.222320+0800 Interview04-__block[2888:128162] MJBlock执行完之后,age = 20    

__block 只可以用来作用于 auto 变量,它的目的就是为了能够让 auto 变量能够在 block 内部内修改。全局变量和 static 变量本来就可以从 block 内部进行修改,因此 __block 对它们来说没有意义,所以 __block 被规定只能用于修饰 auto 变量,这一点应该不难理解。

__block的本质

我们把使用 __block 修饰的 age 转换为 C++ 代码

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main-arm64.cpp
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = 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};

为了对比,加入一个非 __block 修饰的变量来做对比参考

int main(int argc, const char * argv[]) {   
   @autoreleasepool {
        __block int age = 10;
        int noblock = 30;//普通auto对象
        MJBlock block1 = ^{
            age = 20;
            NSLog(@"age is %d", age);
            NSLog(@"age is %d", noblock);
        };
        block1();
        NSLog(@"%@", [block1 class]);
        NSLog(@"MJBlock执行完之后,age = %d",age);
    }
    return 0;
}

转化 C++ 后的代码

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

基本类型的 auto 变量被 block 捕获的时候,就是通过值拷贝的形式把值赋给 block 内部相对应的基本类型变量。而案例里面的 __block int age = 10 ,我们可以看到在底层,系统是把 int age 包装到了一个叫 __Block_byref_age_0 的对象里面。这个对象的结构如下

struct __Block_byref_age_0 {
  void *__isa;;//有isa指针 说明是一个ocobject对象
__Block_byref_age_0 *__forwarding;//指向自身类型对象的指针
 int __flags;
 int __size;//自己所占大小
 int age;//被封装的 基本数据类型变量
};

main函数中 __Block_byref_age_0 被赋了什么值

 __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        int noblock = 30;
image-20210425150700100

10被存储到了 block 内部 __Block_byref_age_0 对象的 成员变量int age 上。

__Block_byref_age_0 对象里面的成员变量 __forwarding 实际上指向了 __Block_byref_age_0 对象自身。

image-20210425151037568

在看看blcok内部函数操作

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref
  int noblock = __cself->noblock; // bound by copy

            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yh_qjzhl57s63j2m9l4frv27zjc0000gn_T_main_86e9d7_mi_0, (age->__forwarding->age));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yh_qjzhl57s63j2m9l4frv27zjc0000gn_T_main_86e9d7_mi_1, noblock);
        }
image-20210425151755842

block 内部修改 auto 变量,是先通过参数传递进来的 block 找到 age 结构体,然后通过 age 结构体找到 __forwarding 成员,通过之前的分析已经知道 __forwarding 存储的指针指向 age 结构体自己,所以本质上还是通过 age 结构体找到存储 auto 变量值得 age 成员,然后修改成 20.

为什么用 age->__forwarding->age ,而不是 age->age 直接拿到 int age ,通过 __forwarding 转一圈有什么用意?

为什么苹果要设计 forwarding 这种多此一举的方式呢?

因为当block从栈上拷贝到堆上后, __block变量 也会拷贝到堆上.这时就有两份 __block变量 ,一份栈上的,一份堆上的.如果 __block 修饰的变量是存放在栈上,这是 forwarding 指向的是它自己,这样没有问题.但是如果 __block 修饰的变量复制到堆上,它就会把栈上的 forwarding 指向堆上的变量,这样就能保证即使访问栈上的 __block变量 也能获取到堆上的变量值,如图:

img

上面,我们知道了通过 __block int age = 10 定义之后,这个 a 底层是一个 __Block_byref_age_0 对象,数值10存放在这个对象内部的成员变量 int age 上面。但是我们在写代码的时候,可以直接通过 __Block_byref_age_0 对象age来赋值,那么在 block 定义初始化结束,完成变量捕获之后,oc代码中再次通过 age 访问到的到底是什么呢?如下图所示

image-20210425152652641
#import <Foundation/Foundation.h>

typedef void (^MJBlock) (void);

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

struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(void);
    void (*dispose)(void);
};

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;
    struct __Block_byref_age_0 *age;
};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        
        MJBlock block = ^{
            age = 20;
            NSLog(@"age is %d", age);
        };
        
        struct __main_block_impl_0 *blockImpl = (__bridge struct __main_block_impl_0 *)block;
        
        NSLog(@"%@", [block class]);
        NSLog(@"MJBlock执行完之后,age = %d",age);
        NSLog(@"完成变量捕获之后,其内部的[__Block_byref_age_0 *] a = %p",blockImpl->age);
        NSLog(@"oc代码里直接通过a访问的内存空间是:%p",&age);
    }
    return 0;
}

RUN>

*********************** 运行结果 **************************
2021-04-25 15:32:06.386047+0800 Interview01-__block[3197:151003] __NSMallocBlock__
2021-04-25 15:32:06.386544+0800 Interview01-__block[3197:151003] MJBlock执行完之后,age = 10
2021-04-25 15:32:06.386602+0800 Interview01-__block[3197:151003] 完成变量捕获之后,其内部的[__Block_byref_age_0 *] a = 0x1004b9230
2021-04-25 15:32:06.386641+0800 Interview01-__block[3197:151003] oc代码里直接通过a访问的内存空间是:0x1004b9248
image-20210425153709926
image-20210425155044976

block 内部的 [__Block_byref_age_0 *] age 指向的地址是 0x1004b9230 ,之后我们在任意地方通过 age 访问的内存地址是 0x1004b9248 ,十六进制下它们地址相差了 0x18 ,也就是十进制下的24个字节。

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

8+8+4+4 = 24;

也就是说我们在oc代码里面完成了 block 的初始化以及 __block变量 的捕获之后,只能通过 age 访问到被封装在 __ Block_byref_age_0 * 内部的这个 int age 的内存空间。

我们思考一下苹果为什么这样设计? 因为苹果要隐藏它内部的实现,我们在修改 __block 修饰的 age 的值时,从表面看会以为真的是在直接修改 age 的值,如果不了解底层实现的话,根本就不知道被 __block 修饰的 age 已经被包装成了一个对象,而我们实际修改的是 age 结构体中的 age 成员的值.

__block变量的内存管理

block捕获 对象类型的auto变量 就会多出两个函数用于做内存管理操作( __main_block_copy_0 和__ main_block_dispose_0 ),如下:

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 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};

block也会使用这两个函数管理 __block 修饰的变量的内存,这也从侧面证明"编译器会将__block变量包装成一个对象",这句话是对的,因为只有对象才需要内存管理。

现在我们来研究一下,使用 __block 修饰的外部变量,在block内部是如何管理的.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        int noblock = 30;
        
        MJBlock myblock = ^{
            age = 20;
            NSLog(@"age is %d", age);
            NSLog(@"noblock is %d", noblock);
        };
        myblock();
       
        
        NSLog(@"%@", [myblock class]);


    }
    return 0;
}
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 结构体中同样有 copy,dispose 函数

为什么访问 __block int 时也会产生呢?

在上面我们说过, block 在访问 __block 修饰的变量时,其底层会被封装成 __Block_byref_age_0 类型,在这个类型存在一个 void *__isa 成员,所以本质上它就是一个对象类型.

__block修饰的变量的内存管理:

  1. 当block在栈上时,并不会对__block变量产生强引用
  2. 当block被copy到堆时(自己拷贝的或者ARC下系统自动拷贝的)
    会调用block内部的copy函数
    copy函数内部会调用_Block_object_assign函数
    _Block_object_assign函数会对__block变量形成强引用(retain)
img

关于内存管理问题这里,我们所讨论的问题需要考虑三个关键因素: __block __weak 对象变量 基本类型变量 ,他们合法的组合有如下几种:

  1. 基本类型变量
  2. 对象变量
  3. __weak + 对象变量
  4. __block + 基本类型变量
  5. __block + 对象变量
  6. __block + __weak + 对象变量

OC-Block的本质(三)-对象类型的auto变量 这一篇里面详细分析了一个对象类型的 auto 变量被 block 捕获时的内存管理过程,上面的1、2、3这三种场景已经得到了说明。下面我们来分析一下4、5、6这三种场景。

__block + 基本类型变量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int age = 10;
        int noblock = 30;
        
        MJBlock myblock = ^{
            age = 20;
            NSLog(@"age is %d", age);
            NSLog(@"noblock is %d", noblock);
        };
        myblock();
       
        
        NSLog(@"%@", [myblock class]);


    }
    return 0;
}
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main-arm64.cpp

转化后的c++源码

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int noblock;
  __Block_byref_age_0 *age; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _noblock, __Block_byref_age_0 *_age, int flags=0) : noblock(_noblock), age(_age->__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_age_0 *age = __cself->age; // bound by ref
  int noblock = __cself->noblock; // bound by copy

            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yh_qjzhl57s63j2m9l4frv27zjc0000gn_T_main_f68719_mi_0, (age->__forwarding->age));
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_yh_qjzhl57s63j2m9l4frv27zjc0000gn_T_main_f68719_mi_1, noblock);
        }
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*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 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};

我们知道 __block int age = 10; 这句代码的作用,是将 int age 包装在 struct __Block_byref_age_0 内部,这样 block 实际上捕获的是这个 struct __Block_byref_age_0 ,它可以被当作一个对象来看待,所以内存管理上面,最终仍然是通过 _Block_object_assign _Block_object_dispose 这两个函数来处理,但是可以看到这两个函数的最后一个参数是 8 (对于对象类型的捕获,传递的参数是 3 ),这个参数表明了即将要处理的是一个 struct __Block_byref_age_0 ,因为它是没有 __weak __strong 标记的,所以处理方式很简单,就是copy到堆上的时候,同时需要进行 retain ,dispose的时候同时需要进行 release

img
img

而当block从堆中移除时:
1:调用block内部的dispose函数
2:dispose函数会调用 _Block_object_dispose 函数
3: _Block_object_dipose 函数会自动释放引用的 __block 变量(release)

img
img

既然 block 访问对象类型的变量和访问使用 __block 修饰的变量都会增加 copy,dispose 函数,那么他们之间有没有区别呢?

他们之间的区别就是:
  • 如果使用__block修饰的变量,block内部直接对其强引用
  • 如果是对象类型的变量,会根据变量的修饰符 __weak , __strong 来决定是否强引用

__block + 对象变量

#import <Foundation/Foundation.h>

@interface MJPerson : NSObject

@end
    
@implementation MJPerson
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end
typedef void (^MJBlock) (void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        __block MJPerson *blockPerson = person;
        
        MJBlock block = ^{
            NSLog(@"%p", blockPerson);
        } ;
        
       
        NSLog(@"%@", [block class]);
        block();
        
       
    }
    return 0;
}

RUN>

*********************** 运行结果 **************************
2021-04-25 17:49:33.467580+0800 Interview01-__block[4123:185220] __NSMallocBlock__
2021-04-25 17:49:33.468013+0800 Interview01-__block[4123:185220] 0x10061e4d0
2021-04-25 17:49:33.468078+0800 Interview01-__block[4123:185220] -[MJPerson dealloc]

编译

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main-arm64.cpp

编译之后, __block MJPerson *blockPerson 的底层结构如下

struct __Block_byref_blockPerson_0 {
  void *__isa;
__Block_byref_blockPerson_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 MJPerson *__strong blockPerson;
};

可以看到使用 __block 修饰的对象底层被封装成了 __Block_byref_blockPerson_0 类型的对象, __Block_byref_blockPerson_0 这个对象类型的结构体中有一个 MJPerson *__strong blockPerson 成员,强引用着我们在 main 函数中创建的 MJPerson 对象,而 __main_block_impl_0 中的 blockPerson 又强引用着 __Block_byref_blockPerson_0 对象,他们的关系如下图:

__block修饰对象类型
image-20210425175504533

从这个结构可以看出两点变化:

  • 相比较基本类型变量,对象类型的变量被 __block 修饰后,底层所生成的 __Block_byref_xxx_x 结构体里面多了两个函数指针, __Block_byref_id_object_copy __Block_byref_id_object_dispose
  • 对象类型的变量被封装到 __Block_byref_xxx_x 内部以后,默认是被 __strong 修饰的。

上面发现的两个新函数指针 __Block_byref_id_object_copy __Block_byref_id_object_dispose 就是当 __Block_byref_xxx_x 被拷贝到堆空间的时候,以及将要被系统释放的时候调用的。

image-20210425175825419

它们的定义如下:

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
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_object_assign _Block_object_assign 这两个函数,二从参数可以看出,它们所要处理的对象就是 __Block_byref_id_object_dispose 内部所封装的对象类型变量,也就是我们代码中的 MJPerson *blockPerson ,因为默认 blockPerson 是被 __strong 修饰的,所以接下来对于 blockPerson 的内存管理方式就和我们之前所分析过的是一样的。

__block + __weak + 对象变量

#import <Foundation/Foundation.h>
#import "MJPerson.h"

typedef void (^MJBlock) (void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        MJPerson *person = [[MJPerson alloc] init];
        __block __weak MJPerson *blockPerson = person;
        
        MJBlock block = ^{
            NSLog(@"%p", blockPerson);
        } ;
        
       
        NSLog(@"%@", [block class]);
        block();
        
       
    }
    return 0;
}

RUN>

2021-04-25 20:05:06.334104+0800 Interview01-__block[4591:204724] __NSMallocBlock__
2021-04-25 20:05:06.334480+0800 Interview01-__block[4591:204724] 0x10051edc0
2021-04-25 20:05:06.334534+0800 Interview01-__block[4591:204724] -[MJPerson dealloc]

查看底层代码:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m -o main-arm64.cpp
typedef void (*MJBlock) (void);

struct __Block_byref_blockPerson_0 {// __block 底层对象
  void *__isa;//8
__Block_byref_blockPerson_0 *__forwarding;//8
 int __flags;//4
 int __size;//4
 void (*__Block_byref_id_object_copy)(void*, void*);//8
 void (*__Block_byref_id_object_dispose)(void*);//8
 MJPerson *__weak blockPerson;//这里变成了弱引用,说明这里的引用情况取决于外部变量的修饰符
};
// block 底层对象
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
    //这里还是强引用,说明 __weak 关键字并不会影响这里
  __Block_byref_blockPerson_0 *blockPerson; // by ref 
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_blockPerson_0 *_blockPerson, int flags=0) : blockPerson(_blockPerson->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = 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};

查看底层代码我们发现, block 内部对于 __Block_byref_blockPerson_0 的引用没有变化,强指针指向__Block_byref_weakPerson_0结构体,但是 __Block_byref_blockPerson_0 中的 blockPerson 已经从强引用变成了弱引用.如图:

img

总结:

对于block:

  1. 如果block是在栈上,将不会对__Block_byref_weakPerson_0产生强引用
  2. 如果栈上的block被拷贝到堆上
    _Block_object_assign函数会对__Block_byref_weakPerson_0产生强引用
  3. 如果堆上的block被移除
    _Block_object_dispose函数会对__Block_byref_weakPerson_0产生弱引用或者移除

对于__block修饰的对象类型:

  1. __block 变量在栈上时,不会对指向的对象产生强引用
  2. __block 变量被copy到堆时(自己拷贝的或者ARC下系统自动拷贝的)
    会调用 __block 变量内部的copy函数
    copy函数内部会调用 _Block_object_assign 函数
    _Block_object_assign 函数会根据所指向对象的修饰符( __strong __weak __unsafe_unretained )做出相应的操作,形成强引用(retain)或者弱引用( 注意:这里仅限于ARC时会retain,MRC时不会retain,一直是弱的。这个特例只会在MRC并且是 __block 修饰对象类型才有
  3. 如果 __block 变量从堆上移除
    会调用 __block 变量内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放指向的对象(release)

将项目切换成 MRC环境

image-20210425202427509

使用__block修饰对象类型,如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block MJPerson *person = [[MJPerson alloc] init];
        
        MJBlock block = [^{
            NSLog(@"%p", person);
        } copy];

        [person release];

        block();  //在此处打断点

        [block release];
    }
    return 0;
}

RUN>

*********************** 运行结果 **************************
2021-04-25 20:25:01.684799+0800 Interview01-__block[4780:216050] -[MJPerson dealloc]
2021-04-25 20:25:01.685196+0800 Interview01-__block[4780:216050] 0x1004708c0
image-20210425202848845

当block被拷贝到堆上的时候,__block修饰的person也会被拷贝到堆上,这时候会调用__block修饰的变量内部的copy函数,MRC环境下,copy函数内部只会对person对象产生弱引用。如下图:

__block修饰的对象

如果把 __block 去掉,会怎么样?

image-20210425203407928

如果把 __block 去掉,blcok 就会对 person 产生强引用,在 block 释放之前,person 是不会释放的,因为去掉 __block 后,就没有 __Block_byref_blockPerson_0 这个中间层,blcok 会直接强引用 person 对象

直接引用
特别备注

本系列文章总结自MJ老师在腾讯课堂 iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化 ,相关图片素材均取自课程中的课件。如有侵权,请联系我删除,谢谢!




原文地址:访问原文地址
快照地址: 访问文章快照