Block详解

窥探block底层结构

我们写下一个最简单的block使用clang指令生成对应的C\C++代码

void (^block)(void) = ^{
    NSLog(@"Hello, World!");
};
block();

截取关键代码如下

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;
  // 构造函数(类似于OC的init方法),返回结构体对象
  __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;
  }
};
// 封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            //这一句就是打印"Hello, World!"
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2r__m13fp2x2n9dvlr8d68yry500000gn_T_main_c60393_mi_0);
}

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

// 定义block变量
void (*block)(void) = &__main_block_impl_0(
                                           __main_block_func_0,
                                           &__main_block_desc_0_DATA
                                           );

// 执行block内部的代码
block->FuncPtr(block);

从上面代码可以看出,block本质上也是一个OC对象,内部也有个isa指针,并且内部封装了函数调用。

block的变量捕获

写下一个访问外部变量的block

int age = 10;
void (^block)(void) = ^{
    NSLog(@"age is %d",age);
};
block();

生成C\C++代码,截取关键部分

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
};
//block内部的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int age = __cself->age; // bound by copy
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_5f0_0k152c5d825xmdf9ztmr0000gn_T_main_b9df52_mi_0,age);
}

int age = 10;
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));

((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

我们可以观察到block结构体多了个age变量,并且在初始化block时,将外部的age变量赋值给了结构体内部这个age变量,当函数执行时,直接打印的是结构体内部的age变量。
所以我们可以总结一下,block就是封装了函数调用以及函数调用环境的OC对象
为了保证block内部能正常访问外部的变量,block有个变量捕获机制

变量类型 是否捕获 访问方式
auto局部变量 值传递
static局部变量 指针传递
全局变量 直接访问

局部变量不写修饰默认就是auto变量,其实从内存上也很好理解block的捕获机制。auto局部变量在栈区,函数调用完后资源会被释放掉,而static局部变量是在程序运行过程中一直存在的(存在数据段),所以用指针随时可以找到,而全局变量本来就是在哪都可访问,根本没必要捕获。

block的类型

block有三种类型,可以通过class方法或者isa指针查看具体的类型,它们最终都继承自NSBlock

类型 判断依据 存储区域 调用copy结果
__NSGlobalBlock__ 没有访问auto变量 数据段 什么也不做
__NSStackBlock__ 访问了auto变量 栈区 从栈区复制到堆
__NSMallockBlock__ __NSStackBlock__调用了copy 堆区 引用计数增加

在ARC环境下会根据情况自动将栈上的block复制到堆上,比如以下情况

  • block作为函数返回值时
  • block赋值给__strong指针时(对象类型的默认修饰就是__strong)
  • block作为Cocoa API中方法名含有usingBlock参数时
  • block作为GCD API的方法参数时
    MRC下建议用copy修饰block属性,ARC可以用strong和copy修饰block属性
block访问对象类型的auto变量

写下如下代码,用clang指令生成C\C++

NSObject *obj = [[NSObject alloc] init];
void (^block)(void) = ^{
    NSLog(@"%@",obj);
};
block();

截取部分关键代码,可以看到
当block访问对象类型的auto变量时,内部多了copy函数和dispose函数

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 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};
//定义block代码
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
  • 当block在栈上时,不会对auto变量产生强引用
  • 当block从栈上被拷贝到堆上时
    会调用block内部的copy函数,copy函数会调用内部的_Block_object_assign函数,该函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用或弱引用
  • 当block从堆上移除时
    block会调用内部的dispose函数,dispose函数会调用内部的_Block_object_dispose函数,该函数会释放引用的auto变量(release)
__block修饰符

__block可以用于解决block内部无法修改auto变量问题,__block不能用来修饰全局变量,静态变量(static)

__block int age = 10;
^{
    age = 30;
}();
NSLog(@"age is %d",age);

生成C\C++代码

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
}
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
//__block int age = 10;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344))();
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2w_5f0_0k152c5d825xmdf9ztmr0000gn_T_main_ad80c7_mi_0,(age.__forwarding->age));

当我们使用__block修饰age变量时,会将age变量包转成age对象,age对象里的int age存储着最初的age值,__forwarding指针是指向age对象自己,block捕获的是age对象的地址值。从最后的一句可以看出,当我们使用__block修饰auto变量后,访问age都变成了访问age对象里的age成员变量。

  • __forwarding指针
    当block从栈上拷贝到堆上时,栈上对象的__forwarding会指向堆上的拷贝对象(block拷贝到堆上时,会将捕获的对象变量一并copy到堆上)
block循环引用问题

从上面我们可以看到,block访问对象类型的auto变量时有可能会产生强引用,当访问的auto变量又对block产生强引用时就会发生循环应用。举例如下

typedef void(^MyBlock)(void);
@interface Person : NSObject
@property (nonatomic, copy) MyBlock myBlock;
@end
//main函数里面
Person *person = [[Person alloc] init];
person.myBlock = ^{
    NSLog(@"%@",person);
};

在ARC环境下可以使用__weak、__unsafe_unretained解决(一般使用__weak,会自动置nil)

Person *person = [[Person alloc] init];
//或者 __unsafe_unretained typeof(Person *) weakPerson = person;
__weak typeof(Person *) weakPerson = person;
person.myBlock = ^{
    NSLog(@"%@",weakPerson);
};

在MRC环境下可以使用__unsafe_unretained解决

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

推荐阅读更多精彩内容

  • 第一部分:Block本质 Q:什么是Block,Block的本质是什么? block本质上也是一个OC对象,它内部...
    sheldon_龙阅读 555评论 0 0
  • Hash,一般翻译做”散列“,也有直接音译为”哈希“的,就是把任意长度的输入通过散列算法变换成固定长度的输出,该输...
    非洲小白猿阅读 1,410评论 0 4
  • 一、Block本质 Block是“带有自动变量值的匿名函数”。 所谓的匿名函数就是不带有名称的函数 但它究竟是什么...
    枫叶情结阅读 489评论 1 0
  • 开始之前,我想先提几个问题,看看大家是否对此有疑惑。唐巧已经写过一篇对block很有研究的文章,大家可以去看看(本...
    高思阳阅读 1,628评论 0 1
  • 转自李峰峰博客 一、概述 闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」...
    Joshua520阅读 985评论 0 0