block底层原理学习

一、block的本质


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

使用clang转换OC为C++代码


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : 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) {
  int a = __cself->a; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_b4_xvb__z9916b4jgm4xkg60n_00000gn_T_main_0adf31_mi_0,a);
    }

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[]) {
    int a = 10;
    void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    return 0;
}
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

通过c++代码可见block内部有个isa指针,本质上也是一个oc对象。

  • block是封装了函数调用以及函数调用环境的OC对象

二、block的变量捕获

2.1、值传递

int main(int argc, const char * argv[]) {
    int a = 10;
    void (^Block)(void) = ^{
        NSLog(@"%d",a);
    };
    return 0;
}
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
int main(int argc, const char * argv[]) {
    int a = 10;
    void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    return 0;
}

block在创建时,传入了变量a的值,并将a的值赋给了main_block_impl_0的成员a,整个过程是值传递。

  • 当block内部访问了外部的局部变量(auto
    类型)时,block可以捕获到该局部变量,并以值传递的方式传递给block内部。

2.1、指针传递

int main(int argc, const char * argv[]) {
    static  int a = 10;
    void (^Block)(void) = ^{
        NSLog(@"%d",a);
    };
    return 0;
}
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

int main(int argc, const char * argv[]) {
    static int a = 10;
    void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
    return 0;
}

block在创建时,传入了变量a的地址,并将a的地址赋给了main_block_impl_0的成员*a,整个过程是指针传递。

  • 当block内部访问了外部的局部变量(static
    类型)时,block可以捕获到该局部变量,并以指针传递的方式传递给block内部。

2.3、全局变量

static  int a = 10;
int b = 21;
int main(int argc, const char * argv[]) {

    void (^Block)(void) = ^{
        NSLog(@"%d",a);
        NSLog(@"%d",b);
    };
    return 0;
}
static int a = 10;
int b = 21;

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

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_b4_xvb__z9916b4jgm4xkg60n_00000gn_T_main_263450_mi_0,a);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_b4_xvb__z9916b4jgm4xkg60n_00000gn_T_main_263450_mi_1,b);
    }

int main(int argc, const char * argv[]) {

    void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    return 0;
}

当a,b作为全局变量时,block就不会捕获到内部,直接访问就可以了。


截屏2020-05-26 上午11.36.58.png

2.3、测试题

static  int a = 10;
int b = 21;
int main(int argc, const char * argv[]) {
    static c = 22;
    int d = 20;
    void (^Block)(void) = ^{
        NSLog(@"%d",a);
        NSLog(@"%d",b);
        NSLog(@"%d",c);
        NSLog(@"%d",d);
  };
    a = 5;
    b = 5;
    c = 5;
    d = 5;
    Block();
    return 0;
}

只有c的值还是22,不会被修改,因为block内部捕获到的变量是值传递。

三、block的类型

block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型:
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )

截屏2020-05-26 下午2.00.47.png

3.1、MRC

nt main(int argc, const char * argv[]) {
    int a = 10;
    void (^block1)(void) = ^{
        
       NSLog(@"%d",a);
    };

    static  int b = 10;
   void (^block2)(void) = ^{
          
       NSLog(@"%d",b);
    };

   void (^block3)(void) = ^{
           
       
     };
    void (^block4)(void) = ^{
              
         NSLog(@"%d",c);
    };
    void (^block5)(void) = ^{
              
         NSLog(@"%d",d);
    };
    NSLog(@"%@",[block1 class]); __NSStackBlock__
    NSLog(@"%@",[block2 class]);__NSGlobalBlock__
    NSLog(@"%@",[block3 class]);__NSGlobalBlock__
    NSLog(@"%@",[block4 class]);__NSGlobalBlock__
    NSLog(@"%@",[block5 class]);__NSGlobalBlock__
    
    return 0;
}
  • 访问了auto变量,block的类型是_NSStackBlock
  • 没有访问auto变量,block的类型一般是NSGlobalBlock
    int a = 10;
    void (^block)(void) = ^{
        
       NSLog(@"%d",a);
    };
    NSLog(@"%@",[block class]); __NSStackBlock__
    NSLog(@"%@",[[block copy] class]); __NSStackBlock__
   
  • NSStackBlock调用了copy后的类型是NSMallocBlock

每一种类型的block调用copy后的结果如下所示:


image.png
image.png

3.2、ARC

  static  int b = 10;
     void (^block2)(void) = ^{
            
         NSLog(@"%d",b);
      };

     void (^block3)(void) = ^{
             
         
       };
      void (^block4)(void) = ^{
                
           NSLog(@"%d",c);
      };
      void (^block5)(void) = ^{
                
           NSLog(@"%d",d);
      };
      NSLog(@"%@",[block2 class]); __NSGlobalBlock__
      NSLog(@"%@",[block3 class]);__NSGlobalBlock__
      NSLog(@"%@",[block4 class]);__NSGlobalBlock__
      NSLog(@"%@",[block5 class]);__NSGlobalBlock__
  • 没有访问auto变量,block的类型是NSGlobalBlock
    int a = 10;
1、强指针
      void (^block1)(void) = ^{
          
         NSLog(@"%d",a);
      };
 2、弱指针
     __weak void (^block2)(void) = ^{
             
            NSLog(@"%d",a);
      };
     NSLog(@"%@",[block1 class]);__NSMallocBlock__
     NSLog(@"%@",[block2 class]); __NSStackBlock__

 3、没有指针
     NSLog(@"%@", [^{
                 
                NSLog(@"%d",a);
     }class]);__NSStackBlock__
    
  • 在ARC环境下,block被强指针指向时,编译器会根据情况自动将栈上的block复制到堆上。
  • 访问了auto变量,block的类型是_NSStackBlock
@property (copy, nonatomic) void (^testBlock)(void);
@property (weak, nonatomic) void (^test1Block)(void);
@property (strong, nonatomic) void (^test2Block)(void);

    int   a = 10;
    self.testBlock = ^(){
        NSLog(@"%d",a);

        
    };
    self.test1Block = ^(){
             NSLog(@"%d",a);
      };
    self.test2Block = ^(){
             NSLog(@"%d",a);
      };
    NSLog(@"%@",[self.testBlock class]);__NSMallocBlock__
    NSLog(@"%@",[self.test1Block class]);__NSStackBlock__
    NSLog(@"%@",[self.test2Block class]);__NSMallocBlock__
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

    self.test1Block();

}

self.test1Block的类型是_NSStackBlock,存储在栈区。过了作用域就会销毁,这样再次调用就会访问了坏内存,造成崩溃。这个例子也说明了MRC下block属性为什么一般用copy,就是 为了内存的访问安全考虑。

3.3、小结

  • 1、没有访问auto变量,block的类型是NSGlobalBlock

  • 2、 访问了auto变量,block的类型是_NSStackBlock

  • 3、 在ARC环境下,block被强指针指向时,编译器会根据情况自动将栈上的block复制到堆上:
    a、block作为函数返回值时
    b、将block赋值给__strong指针时
    c、block作为Cocoa API中方法名含有usingBlock的方法参数时
    d 、block作为GCD API的方法参数时

  • 4、
    MRC下block属性的建议写法
    @property (copy, nonatomic) void (^block)(void);

    ARC下block属性的建议写法
    @property (strong, nonatomic) void (^block)(void);
    @property (copy, nonatomic) void (^block)(void);

3.4、对象类型的auto变量

当block内部访问了对象类型的auto变量时,如果block是在栈上,将不会对auto变量产生强引用。如果block被拷贝到堆上:
会调用block内部的copy函数
copy函数内部会调用_Block_object_assign函数
_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用。
如果block从堆上移除,会调用block内部的dispose函数
dispose函数内部会调用_Block_object_dispose函数
_Block_object_dispose函数会自动释放引用的auto变量(release)

4、__block修饰符

__block可以用于解决block内部无法修改auto变量值的问题
_block不能修饰全局变量、静态变量(static)
编译器会将__block变量包装成一个对象

 __block int a = 10;
      void (^block1)(void) = ^{
          
          a = 0;
      };
struct __Block_byref_a_0 {
 void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};

struct __main_block_impl_0 {
 struct __block_impl impl;
 struct __main_block_desc_0* Desc;
 __Block_byref_a_0 *a; // by ref
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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


         (a->__forwarding->a) = 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_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
  __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
     void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
   return 0;
}
本质就是 __block变量被编译器 包装成 __Block_byref_a_0 对象,__main_block_func_0接收的是一个对象,再通过修改对象的指针修改age的值。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354