iOS 进阶知识点整理--分析block

本文将围绕以下几个问题分析block:

  1. block是不是函数指针?如果不是它们有什么区别?

  2. block修改局部变量为什么必须要加__block修饰?

  3. block为什么会产生循环引用?应该怎么避免?

要完全搞清楚这几个问题,首先我们需要搞清block的本质是什么?

结论:在iOS中block本质上就是一个oc对象,内部同其他OC对象一样有一个isa指针。block是一个将函数定义、实现、调用封装在一起的OC对象;

第一步:先写一个简单的测试block

- (void)test {
    void(^block)(NSInteger, NSInteger) = ^(NSInteger a, NSInteger b){
        NSLog(@"block a = %ld, b = %ld",a, b);
    };
    block(10, 20);
}

用命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m 将OC文件转换成c++代码查看OC内部实现结构

static void _I_ViewController_test(ViewController * self, SEL _cmd) {
    void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger,                 NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA));
    ((void (*)(__block_impl *, NSInteger, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 20);
}

从上述代码可以看到,block的定义被转换成了

void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA));

实际上是把函数__ViewController__test_block_impl_0的函数地址赋值给了block,\color{red}{注意:block与函数指针有一点相似,但block不是函数指针}

应该在这段代码之上我们可以看到__ViewController__test_block_impl_0其实是一个结构体,结构如下:

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

该结构体内部包含了两个变量implDesc和一个同名构造函数__ViewController__test_block_impl_0,全局搜索__block_impl找到其结构如下:

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

  • isa:类型指针,这里代表block的类型

  • Flags:这里从构造函数的调用看到它有一个默认值0,并未对其赋值,也许在更深层有其它的用处

  • Reserved:应该同Flags差不多在底层有使用吧

  • FuncPtr:函数指针,这里指向__ViewController__test_block_func_0函数

    到这里足以证明block就是一个OC对象,同样有这一个指向它类型的isa指针;结构体__ViewController__test_block_impl_0的下方应该就能看到另一个变量Desc其结构如下:

    static struct __ViewController__test_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __ViewController__test_block_desc_0_DATA = { 0, sizeof(struct __ViewController__test_block_impl_0)};
    
    

    __ViewController__test_block_desc_0包含了两个变量,并在定义时创建了一个__ViewController__test_block_desc_0_DATA结构体

  • reserved:字面意思保留,应也是一个内部使用字段,这里能看到为其赋值0

  • Block_size:sizeof(),这里代表结构体__ViewController__test_block_impl_0的内存占用大小

void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA));

回看我们的block定义,__ViewController__test_block_impl_0调用时还传入了一个__ViewController__test_block_func_0函数

static void __ViewController__test_block_func_0(struct __ViewController__test_block_impl_0 *__cself, NSInteger a, NSInteger b) {
  // NSLog(@"block a = %ld, b = %ld",a, b); OC block 内部代码
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_dm_tq28fyls0cq42nl6fdhyqzd40000gn_T_ViewController_939d4d_mi_0,a, b);
        }

从函数结构就能看出该函数其实就是block{}里面的实现,因此我们得出block就是OC对象,内部封装了c函数定义、实现、和函数调用;

1.那么block是不是函数指针?如果不是它们有什么区别?

综上所述,block不是函数指针。函数指针是指向一段预先定义好的可执行代码,且该函数地址是在编译时就已经确定好的,函数内只能访问全局变量。block是OC对象,可以在运行时自由创建,不同作用域内创建的block对象的生命周期也不一样,通常动态创建的block都存储在栈上,可以调用[block copy]将其拷贝到堆上延长生命周期,另外block对局部变量拥有读的权限,对使用__block修饰的局部变量拥有读写权限

2.block修改局部变量为什么必须要加__block修饰?
- (void)test {
 int j = 10;
 int g = 10;
 static int s = 10;
 __block int i = 10;
 void(^block)(NSInteger, NSInteger) = ^(NSInteger a, NSInteger b){
 i++;
 s++;
 self;
// j++; 报错:Variable is not assignable (missing __block type specifier)
 NSLog(@"block a = %ld, b = %ld",a, b);
 NSLog(@"block i = %d, j = %d",i, j);
 };
 block(10, 20);
}

在原有test方法内新增成员变量j、g,static变量s,__block变量i,再次执行命令转换为c++,结果如下:

static void _I_ViewController_test(ViewController * self, SEL _cmd) {
    int j = 10;
    int g = 10;
    static int s = 10;
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 10};
    void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA, &s, self, j, g, (__Block_byref_i_0 *)&i, 570425344));
    ((void (*)(__block_impl *, NSInteger, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 20);
}

可以看到前三个变量并没有什么变化,只有__block 变量i被转换成了__Block_byref_i_0结构体:

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

__isa=(void*)0,__forwarding=(__Block_byref_i_0 *)&i指向i自身地址的指针,__flags=0,__size=sizeof(__Block_byref_i_0),i=10为原来i的值,修改后block的实现变成如下:

struct __ViewController__test_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__test_block_desc_0* Desc;
  int *s;
  ViewController *self;
  int j;
  int g;
  __Block_byref_i_0 *i; // by ref
  __ViewController__test_block_impl_0(void *fp, struct __ViewController__test_block_desc_0 *desc, int *_s, ViewController *_self, int _j, int _g, __Block_byref_i_0 *_i, int flags=0) : s(_s), self(_self), j(_j), g(_g), i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

仔细看会发现在函数__ViewController__test_block_impl_0()后面还有这么一截: s(_s), self(_self), j(_j), g(_g), i(_i->__forwarding)这一截都是对结构体__ViewController__test_block_impl_0内的自定义变量进行复制s(_s)等价于s = _s其他同理,结合改变后的block定义:

    void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA, &s, self, j, g, (__Block_byref_i_0 *)&i, 570425344));

看调用__ViewController__test_block_impl_0函数时传入的参数((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA, &s, self, j, g, (__Block_byref_i_0 *)&i, 570425344)前两个同修改前一样前面有介绍,从第三个开始分别为:静态变量s的地址、当前类对象self、j和g传入的都是int类型数值、(__block变量转换后)结构体i的地址,最后那个数字对应的是flags

那么,block修改局部变量为什么必须要加__block修饰?

因为,使用__block修饰后的变量在block内部会创建一个相应的结构体,该结构体内保留了变量的值和地址,block内部还为结构体定义了一个指针变量,内部对原来变量的读写都是通过这个结构体指针来实现的,所以就完成了对__block变量的修改;更多细节

3.block为什么会产生循环引用?应该怎么避免?

通过以上内容分析,我们知道block内会对外部变量、对象产生一个引用关系,OC对象在没有指定强弱引用的情况下,默认是强引用,那么一个对象如果间接或直接拥有block,block内部又拥有该对象那么就会产生循环引用

如图1中object----(strong)---->object1----(strong)---->block----(strong)---->object就是一个间接性引用产生的循环引用

图1

解决图1中问题其实只需将三条箭头中任意一条改用弱引用就能打破循环保留问题,但在实际开发过程中我们调用block一般都不是及时性的,所以如果object----(weak)---->object1或者object1----(weak)---->block当我们在调用block时可能对象已经被释放,代码运行会违背我们预期效果,即解决block循环引用问题,我们推崇将block-->object外部改成弱引用,block内部在将弱引用对象转强引用避免block内部访问还没结束object就被释放问题,如图2:

图2

参考文章:
iOS Block原理探究以及循环引用的问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容

  • 1. Block的底层结构 以下是一个没有参数和返回值的最简单的Block: 为了探索Block的底层结构,需要将...
    再好一点点阅读 461评论 0 4
  • block.png iOS代码块Block 概述 代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实...
    全栈农民工阅读 588评论 0 1
  • iOS代码块Block 概述 代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实现匿名函数的特性,B...
    smile刺客阅读 2,338评论 2 26
  • 人与人初识,都只会觉得对方眉眼可爱志趣相投,大抵是因为大家深知第一印象的重要性,于是总能轻易化解心中不悦,给旁...
    最近爱放空阅读 178评论 0 0
  • dasfsad
    adamasp阅读 72评论 0 1