block研究

为什么外部变量加上__blcok之后就可以在block内部进行修改。

通过clang把OC重写成C++来看一下__block究竟做了什么。
clang -rewrite-objc main.m或者
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk xxxxx.m

1.先看一下没有使用__block的非对象变量:

int main () {
    int num = 1;
    void (^aBlock) (void) = ^{
        printf(" 输出 num == %d", num);
    };
    aBlock();
    return 0;
}

重写完后的代码很多,挑主要的部分展示一下:

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

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int num = __cself->num; // bound by copy
  printf(" 输出 num == %d", num);
}

int main () {
    int num = 1;
    void (*aBlock) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
    ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
    return 0;
}

先看main函数里

void (*aBlock) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));

aBlock在这列是一个指针变量,指向了一个__main_block_impl_0类型的结构体,这一段拆开来可以看作是这样的:

struct __main_block_impl_0 tmp = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
struct __main_block_impl_0 *aBlock = &tmp;

来看一下结构体__main_block_impl_0的定义:

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

在block里捕获到的外部变量num会作为结构体的成员变量,并且会在构造方法里对成员变量num赋值,构造方法里可以看作是这样的:

    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    num = 0

我们之前在block写的代码在这里是一个静态函数:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int num = __cself->num; // bound by copy
        printf(" 输出 num == %d", num);
 }

构造器的第一个参数就是这个函数的地址:

struct __main_block_impl_0 tmp = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));

这个函数的地址会存储到结构体成员变量里:

 impl.FuncPtr = fp;

之后调用aBlock()对应的是:

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

这个可以看做是:

(*aBlock->impl.Funcptr)(aBlock);

调用blcok也就是通过函数指针Funcptr调用了函数,并且把aBlock指向的结构体作为函数参数。

刚刚那个函数里:

  int num = __cself->num; // bound by copy

block里面使用的num是一个新定义的num,它是值拷贝,我们无法通过它修改外部的变量。在OC里面如果修改的话编译器也会直接报错。

接下来看一下加上__block之后的效果:

int main () {
    __block int num = 1;
    printf("输出前 num == %p", &num);
    void (^aBlock) (void) = ^{
        num ++;
        printf("输出中 num == %p", &num);
    };
    aBlock();
    printf("输出后 num == %p", &num);
    return 0;
}

先看重写之后的main函数

int main () {
    
    printf("输出前 num == %p", &(num.__forwarding->num));
    void (*aBlock) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
    printf("输出后 num == %p", &(num.__forwarding->num));
    return 0;
}

__block int num 被重写成了

__attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 1};

num在这里是一个结构体。

block结构体是这样的:

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

blcok结构体会持有__block结构体的指针,函数里面修改num是这样的:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_num_0 *num = __cself->num; // bound by ref

        (num->__forwarding->num) ++;
        printf("输出中 num == %p", &(num->__forwarding->num));
    }

函数里是先获取到了__block结构体的指针,然后修改了__block的成员变量num。

首先可以看到,加上__block之后的外部变量,重写之后main函数里第一行不再是int num = 1,而是定义了一个 __Block_byref_num_0 num = ...结构体,并且初始化时这个结构体的forwad是指向它自己的,我们定义的int 值存在了结构体的成员变量num里面。

再看一下这里的__main_block_func_0这个函数里,它里面不再像没有加__block那样新定义一个int num,而是新定义了一个指针__Block_byref_num_0 *num,在OC里面的a++在这里被重写成了(num->__forwarding->num) ++;也就是说它是通过指针实现了修改外部变量。

接下来在MRC和ARC环境下分别运行下面的代码:

    __block int num = 1;
    printf("输出前 num == %p\n", &num);
    void (^aBlock) (void) = ^{
        num ++;
        printf("输出中 num == %p\n", &num);
    };
    aBlock();
    printf("输出后 num == %p\n", &num);

MRC下打印出来的三个地址是一样的:

输出前 num == 0x7fff547606c8
输出中 num == 0x7fff547606c8
输出后 num == 0x7fff547606c8

而ARC下打印的地址后两个是一样的:

输出前 num == 0x7fff547606c8
输出中 num == 0x604000431018
输出后 num == 0x604000431018

MRC下上面的aBlock和num都是在栈上的,结构体的__forwarding指针始终指向它自己,所以三个地址打印出来的是一样的。


[图片上传中...(1194012-5f5f486bab68191f.jpg-795226-1523414501068-0)]

在ARC下,上面的aBlock是在堆上的,而num也会从栈上copy到对上,而原来在栈上的结构体__forwarding指针会指向在堆上的结构体,这时打印的地址是在堆上的num的地址。

1194012-5f5f486bab68191f.jpg

2.再来看一下外部对象变量:

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

重写之后的代码block里面的实现:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    NSObject *obj = __cself->obj; // bound by copy
    
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_main_4c9416_mi_1, obj, &obj);
}

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

    NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_block3_e8baad_mi_0, obj, &obj);
    void (*aBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_block3_e8baad_mi_2, obj, &obj);
    return 0;

}

可以看出来,block里的obj对象是新定义的一个指针,因此不能够通过修改它来修改外部的obj,所以在block里不能够修改obj的值。

再看一下加上__block之后的代码:

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

重写之后的代码:

struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 __Block_byref_obj_0 *obj = __cself->obj; // bound by ref    (obj->__forwarding->obj) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_block4_6564ec_mi_1, (obj->__forwarding->obj), &(obj->__forwarding->obj));
}


int main(int argc, char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {(void*)0,(__Block_byref_obj_0 *)&obj, 33554432, sizeof(__Block_byref_obj_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_block4_6564ec_mi_0, (obj.__forwarding->obj), &(obj.__forwarding->obj));
    void (*aBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_obj_0 *)&obj, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)aBlock)->FuncPtr)((__block_impl *)aBlock);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_mn_jp2_90d16qb5m5_bbpyfj_8h0000gn_T_block4_6564ec_mi_2, (obj.__forwarding->obj), &(obj.__forwarding->obj));
    return 0;
}

从上面可以看出,加上__block之后,对象obj会被编译成一个结构体__Block_byref_obj_0 obj,并且在block的实现里面__Block_byref_obj_0 *obj = __cself->obj使用一个新的结构体指针引,通过这个结构体指针就可以访问到结构体里存储的obj对象并进行修改。

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

推荐阅读更多精彩内容

  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,760评论 0 23
  • 1 Block机制 (Very Good) Block技巧与底层解析 http://www.jianshu.com...
    Kevin_Junbaozi阅读 4,040评论 3 48
  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 919评论 1 3
  • 摘要 Blocks是C语言的扩充功能, iOS 4中引入了这个新功能“Blocks”,那么block到底是什么东西...
    CholMay阅读 1,160评论 2 10
  • 生活中有很多失落感,大人有,孩子有。面对失落,我们更多时候是抱怨,可有些时候,我们是哀伤,也有落寞和凄凉,最要紧的...
    明心zyl阅读 328评论 2 3