ios __block修饰词底层实现原理

 

注:此文章为自己学习笔记,部分来自欧阳大哥博客https://www.jianshu.com/p/595a1776ba3a

让我们看下代码:

//文件test.m
#import <Foundation/Foundation.h>
void test()
{
    //下面分别定义各种类型的变量
     int a = 10;                       //普通变量
    __block int b = 20;                //带__block修饰符的block普通变量
    NSString *str = @"123"; 
    __block NSString *blockStr = str;  //带__block修饰符的block OC变量
    NSString *strongStr = @"456";      //默认是__strong修饰的OC变量
    __weak NSString *weakStr = @"789"; //带__weak修饰的OC变量
  
  //定义一个block块并带一个参数
    void (^testBlock)(int) = ^(int c){
         int  d = a + b + c;
         NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr, weakStr);
     };

    a = 20;  //修改值不会影响testBlock内的计算结果
    b = 40;  //修改值会影响testBlock内的计算结果。
    testBlock(30);  //执行block代码。
}

大家都知道打印的结果,block里a的值为10,b为40。a在外部的更改并没有影响到block内部的值。为什么呢?让我们看一下底层的实现机制是什么。

我们打开终端控制台,并到test.m文件所在的路径下执行如下的命令: 

   clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations test.m

得到cpp文件,这个文件是OC代码的c++实现,我们可以通过查看c++来分析block的实现结构和方法,下面是c++部分代码。

struct __Block_byref_b_0 {
  void *__isa;
__Block_byref_b_0 *__forwarding;
 int __flags;
 int __size;
 int b;
};
struct __Block_byref_blockStr_1 {
  void *__isa;
__Block_byref_blockStr_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSString *blockStr;
};

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

struct __test_block_impl_0 {
  struct __block_impl impl;
  struct __test_block_desc_0* Desc;
  int a;
  NSString *strongStr;
  NSString *weakStr;
  __Block_byref_b_0 *b; // by ref
  __Block_byref_blockStr_1 *blockStr; // by ref
  __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, int _a, NSString *_strongStr, NSString *_weakStr, __Block_byref_b_0 *_b, __Block_byref_blockStr_1 *_blockStr, int flags=0) : a(_a), strongStr(_strongStr), weakStr(_weakStr), b(_b->__forwarding), blockStr(_blockStr->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself, int c) {
  __Block_byref_b_0 *b = __cself->b; // bound by ref
  __Block_byref_blockStr_1 *blockStr = __cself->blockStr; // bound by ref
  int a = __cself->a; // bound by copy
  NSString *strongStr = __cself->strongStr; // bound by copy
  NSString *weakStr = __cself->weakStr; // bound by copy



        int d = a + (b->__forwarding->b) + c;

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_3, d, strongStr, (blockStr->__forwarding->blockStr), weakStr);

    }
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->strongStr, (void*)src->strongStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_assign((void*)&dst->blockStr, (void*)src->blockStr, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->weakStr, (void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __test_block_dispose_0(struct __test_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->strongStr, 3/*BLOCK_FIELD_IS_OBJECT*/);_Block_object_dispose((void*)src->blockStr, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->weakStr, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
  void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = { 0, sizeof(struct __test_block_impl_0), __test_block_copy_0, __test_block_dispose_0};
void test()
{


    int a = 10;
    __attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
    NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_0;
    __attribute__((__blocks__(byref))) __Block_byref_blockStr_1 blockStr = {(void*)0,(__Block_byref_blockStr_1 *)&blockStr, 33554432, sizeof(__Block_byref_blockStr_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, str};
    NSString *strongStr = (NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_1;
     NSString *weakStr = (NSString *)&__NSConstantStringImpl__var_folders_d6_rtk49g5s52g_m2sgrvm0b5q00000gn_T_main_5b81f9_mi_2;

    void (*testBlock)(int) = ((void (*)(int))&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, a, strongStr, weakStr, (__Block_byref_b_0 *)&b, (__Block_byref_blockStr_1 *)&blockStr, 570425344));

    a = 20;
    (b.__forwarding->b) = 40;
    ((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30);

}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

可以看到,__block int b 被转换为一个block字眼的结构体。它具备5个属性:

1.__isa指针

2.__forwarding我理解为传入变量的地址,这个最关键,也是为什么可以改变__block修饰的变量的原因

3.__flags我理解为标记位,编译器分配的

4.__size结构体大小

5. b,真正的值

这里插一句,__block NSString* blockString 会多出两个属性,这也完美解释了,MRC下为什么对象前面要加__block来防止内存循环引用,多出的两个属性为

1.__Block_byref_id_object_copy 

2.__Block_byref_id_object_dispose

当Block从栈复制到堆时,使用_Block_object_copy函数,持有Block截获的对象。当堆上的Block被废弃时,使用_Block_object_dispose函数,释放Block截获的对象

 回到正题,看一下__block int b对应的赋值情况

__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};

__isa指针传值为空(ps:但对于block的初始化来讲,isa指针指向的是该block的三种类型),__forwarding指向了变量b本身的地址,__flags分配了0,__size为结构体的大小,b等于20。用图说明赋值的情况:

所以,没有__block修饰的变量 和有__block修饰的变量在调用时的实现为:

    a = 20;
    (b.__forwarding->b) = 40;

通过打印地址,你会发现,对于自动释放变量(指出了作用域会被释放的变量),在block里只是用到了变量指向的值,而不是把变量本身传入了block;对于__block修饰的变量,block会创建block结构体,并且改变原变量的地址。成员变量和全局变量前后地址相同,见下图。

1.黄色和绿色是__block 修饰的int变量和string变量,会发现,当block执行后,原变量的地址被改了。

2.蓝色为成员变量,它的地址不变。这也是为什么成员变量不经过__block修饰也可以改变值的原因。

ps:关于__block修饰的变量,在block执行完后,地址被改的情况,如果有知道原因的大大们,小生在此虚心请教,多谢大大们~

现在说一下block的存储域和相互引用

ps:部分摘自https://www.jianshu.com/p/12c324c9dcc4

顾名思义,相互引用就是,A持有B,B又持有A,导致两个对象都无法释放。

1.先说一个不会存在相互引用的例子,依然用上面的代码,在block实现里小做修改:

  //定义一个block块并带一个参数
    void (^testBlock)(int) = ^(int c){
         int  d = a + b + c;
         NSLog(@"d=%d, strongStr=%@, blockStr=%@, weakStr=%@", d, strongStr, blockStr, weakStr);
         NSLog(@"self=%@, strongStr=%@, blockStr=%@, weakStr=%@", self);

     };

我们在block里打印了self,但并没有造成内存循环引用,原因就是self并没有持有block,这个block紧紧是在某个方法下定义的,存储栈上面,出了作用域后会自动销毁。

2.说一个会存在相互引用的例子,这里我们用__block,证明用__block修饰过也会造成相互引用。

typedeft void (^blk_t)(void);

@interface MyObject : NSObject
{
    blk_t blk_;
}
@end

@implementation MyObject
- (id)init
{
    self = [super init];
    
    __block id blockSelf = self;
    
    blk_ = ^{
          NSLog(@"self = %@", blockSelf);
        };
    
    return self;
}

为什么加了__block仍然会造成相互引用呢?

因为blk_t的存储域现在是在堆上,堆上的block会对__block修饰变量做持有,栈上的block只会对__block修饰的变量做使用,因此这个代码的引用情况为MyObject->blk_t->MyObject。

如何打破相互以后用呢?

只需在使用完后,将__block变量置空,打破循环。

    blk_ = ^{
          NSLog(@"self = %@", blockSelf);
          blockSelf = nil; //打破循环
        };

刚才我们说到堆会对__block变量做持有,为什么?这就是接下来要说的“存储域”

block的存储域有三种:

● _NSConcretStackBlock  栈

● _NSConcretGlobalBlock  全局

● _NSConcretMallocBlock  堆

一.全局_NSConcretGlobalBlock

1.定义在方法外部的,且没有捕获任何变量。

typedef void (^blk_t)(void);

- (void)test{

    blk_t blck = ^{

            NSLog(@"_global");
            
        };
blck();
}

2.定义在方法内部的,且没有捕获任何变量。

- (void)test{

        void(^block)(int) = ^(int c){
        
            } ;
        block(1);
}

二.栈_NSConcretStackBlock

arc下会在block赋值时自动加copy,将_NSConcretStackBlock变成_NSConcretMallocBlock,只有mrc下,能看到。目前我知道能保存在栈上的block只有在声明时才会看到,代码如下

   NSLog(@"block is %@", ^{
        
        NSLog(@"test Arr :%@", self.str);
        
    });

打印为:block is <__NSStackBlock__: 0x7ffee7376068>

三,堆_NSConcretMallocBlock

堆是我们最常见的block,对栈上的block进行copy后,就会被存储到堆上,什么时候会进行copy操作?ps:我们说的都是arc下的

1.当block作为函数返回值返回时

2.当 block 被赋值给 __strong id 类型的对象或 block 的成员变量时,ps:arc下默认都会加__strong

3.当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API时。

补充:当多个block引用同一个__block变量时,在堆上会使__block变量引用计数+1;

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值