Block探索

随着苹果越来越多的API开始提供block的写法,另外加上block在很多时候写起来确实很方便,因此block越来越受到iOS开发者的青睐。打算在这里记录下自己对block探索的一些成果。

Block的分类

在iOS中我们常用的block主要有三类:

  1. __NSStackBlock__  -> //存于栈区;   
  2.__NSGlobalBlock__ -> //存于程序数据区;
  3.__NSMollocBlock__ -> //存于堆区;  

这三个不同类型,存放位置不同的block是怎么管理内存的,这里不做解释。

Block捕获局部变量

在block中可以捕获始终类型的数据:全局变量,全局静态变量,局部静态变量,局部变量。这里只讲block对局部变量的捕获。首先我们在MRC环境下写如下测试代码,为了便于后面的说明我们给这段代码叫做A:

int main(intargc,constchar* argv[]) {
  @autoreleasepool {
    __block NSMutableString* mutableWXKey = [[NSMutableString alloc] initWithFormat:@"user's"];
    NSString* wxKey = [[NSMutableString alloc] initWithFormat:@"name"];
    int test =1;

    void(^myStackBlock)(void) = ^(){

        [mutableWXKeystringByAppendingString:@"name"];

        NSLog(@"我是栈block");

        NSLog(@"%d",test);

        NSLog(@"%@",wxKey);

    };

    NSLog(@"myStackBlock ->  %p",myStackBlock);

    void(^myGlobalBlock)(void) = ^(){

        NSLog(@"我是全局block");

        NSLog(@"%d",test);

        NSLog(@"%@",wxKey);

    };

    NSLog(@"myGlobalBlock ->  %p",myGlobalBlock);

    void(^myMollocBlock)(void) = [^(){

        [mutableWXKeystringByAppendingString:@"name"];

        NSLog(@"我是堆block");

        NSLog(@"%d",test);

        NSLog(@"%@",wxKey);

    }copy];

    NSLog(@"myMollocBlock ->  %p",myMollocBlock);

    myStackBlock();

    myGlobalBlock();

    myMollocBlock();

    NSLog(@"%@",mutableWXKey);

  }
  return 0;
}

然后我们需要利用clang将我们的这段OC代码转换成C++代码,方便我们去研究Block究竟是怎么去捕获局部变量、__block修饰符的原理。在终端中将路劲切换到我们的工程目录下并通过命令:clang -rewrite-objc main.m即可得到我们需要的C++代码。陈宫后如下所示:
WX20180104-111033.png

我们可以用自己喜欢的编辑器打开这个main.cpp文件。在文件中我们可以看到这样一个结构体:

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

那么这个结构体有什么意义呢?我们在A代码段的NSLog(@"%@",mutableWXKey);这段代码处打上一个断点,然后运行代码,在Xcode的控制台可以看到一些有趣的事情,结果如下图所示:

WX20180104-110853.png

我们会发现这个结构成员和我们在控制台上看到的block的成员变量很像,其实block在C++中就是用这样一个结构体来表示的。说了这么多,开始进入正题,赶紧搬好凳子,备好瓜子,准备听我唠叨了,哈哈哈。。。。

我们回到main.cpp这个文件中,从第99138行开始看,我们会发现几对看起来名称相似的结构体和函数,名称类似这一样

__main_block_impl_0;__main_block_func_0;__main_block_copy_0;__main_block_dispose_0;__main_block_desc_0

__main_block_impl_1;__main_block_func_1;__main_block_copy_1;__main_block_dispose_1;__main_block_desc_1

__main_block_impl_2;__main_block_func_2;__main_block_copy_2;__main_block_dispose_2;__main_block_desc_2;

在我们的代码段中总共定义了三个block变量,这里恰好出现三对这些东西,是不是有啥关联呢,朋友你很聪明,恭喜你才对了,赏你一个☺。为了方便说明,在这里我们用X代表数字:

__main_block_impl_X__main_block_desc_X是两个结构体,其他的是函数。在这里我们主要研究__main_block_impl_X__main_block_func_X

定义block

在我们的源代码main.m中我们定义了一个wxKey的变量并且没有用__block修饰我们在mian.cpp中可以看到我们定义的block被转换成了这样的:

  void(*myStackBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, test, wxKey, (__Block_byref_mutableWXKey_0 *)&mutableWXKey, 570425344));

一个函数指针。然后这里出现了这个东西:__main_block_impl_0。那么这是什么呢?

block捕获局部变量

我们回到__main_block_impl_0这个结构体中,我们会发现,结构体中定义了构造函数并且是以初始化列表的形式提供的。那么不难理解这里就是通过初始化列表的方式构造了一个函数指针,那么我们来看看这个构造函数的传入了哪些参数。总共6个参数其中第3、4、5个参数通过变量名,我们发现是我们的block捕获的三个局部变量的名字,也就可以理解block就是在这里对局部变量进行捕获的。但是这里是不是会有一个疑问?testwxKey这两个变量还可以理解,这个(__Block_byref_mutableWXKey_0 *)&mutableWXKey是什么鬼,这里就涉及到我们要说的__block这修饰符了

__block修饰符

上面疑问为什么在捕获mutableWXKey这个变量是会出现不一样的情况,回到main.m文件,我们发现mutableWXKey这个变量用__block这是进行了修饰,而test和wxKey没有,我们知道在使用block时,如果要在block内对从外部捕获到的变量进行修改,需要对外部的局部变量用__block 修饰,在这有两种不同情况,block外的局部变量分为:基础数据类型和引用类型,基础数据类型如果不用__block修饰,在block内对变量进行修改,编译器会报错,但是引用类型不用__block修饰不一定会报错,但是存在的问题的是,当引用类型的变量在block内被修改后,出了block的作用域之后,会发现局部变量的值未改变。

我们回到(__Block_byref_mutableWXKey_0 *)&mutableWXKey这里。首先我们去看看__Block_byref_mutableWXKey_0这是什么。在main.cpp文件找到这个,发现竟然是一个结构体。惊喜不惊喜,我明明定义的是一个NSMutableString类型,咋就莫名奇妙成了一个结构体。别急,我们来看看这个结构体到底是个啥面目:

  struct __Block_byref_mutableWXKey_0 {
    void *__isa;
    __Block_byref_mutableWXKey_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    NSMutableString *mutableWXKey;
};

我们发现这个结构体竟然包含一个名叫mutableWXKey的成员变量,而且恰好就是我们要的NSMutableString类型,那么问题来了,为啥要多此一举先弄个结构出来在把这个变量放到结构体中呢?而且这里在传递参数的时候只把这个结构体的地址给传进去,我们知道__block的作用就是让我们在调用block时,能在block内部对捕获到的局部变量进行修改。那么我们先看下block怎么调用的,我给这段代码取个名字叫B,方便后面说明:

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

看到这个,这时候就有句当讲不当讲了,我感觉clang你是在为难我,这一大串是啥,木办法,只能耐着性子,慢慢看了。

我们知道__block_impl这是一个结构体,FuncPtr是这个结构体的成员变量,而且还是传说中的万能指针。此时似乎是雾里看花,雾突然渐渐消散起来了,啦啦啦,继续。
拿到这个指针后我们把这个指针转换成(void (*)(__block_impl *))类型,这是一个函数指针,返回值为void,入参是一个__block_impl类型的结构体指针。貌似看到希望了,激动啊。继续看FuncPtr这个成员变量是啥呢。回到我们的block的定义处,具体看
__main_block_impl_0这个构造函数,调用这个构造函数的时候我们传入的第一个参数是(void *)__main_block_func_0这是一个函数指针,而在构造函数里面我们将这个指针赋值给了FuncPtr这个成员变量,那么这里的代码段B实际就是调用__main_block_func_0这个函数了,那我们就赶紧去看看这个函数干了啥呢:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_mutableWXKey_0 *mutableWXKey = __cself->mutableWXKey; // bound by ref
int test = __cself->test; // bound by copy
NSString *wxKey = __cself->wxKey; // bound by copy

        ((NSString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)(mutableWXKey->__forwarding->mutableWXKey), sel_registerName("stringByAppendingString:"), (NSString *)&__NSConstantStringImpl__var_folders_fm_xcy0l9917d5gvhx85v295_k80000gn_T_main_e8ea30_mi_2);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_fm_xcy0l9917d5gvhx85v295_k80000gn_T_main_e8ea30_mi_3);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_fm_xcy0l9917d5gvhx85v295_k80000gn_T_main_e8ea30_mi_4,test);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_fm_xcy0l9917d5gvhx85v295_k80000gn_T_main_e8ea30_mi_5,wxKey);
}

这个函数的入参是__main_block_impl_0类型的结构体指针,该结构体的定义如下:

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int test;
    NSString *wxKey;
    __Block_byref_mutableWXKey_0 *mutableWXKey; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _test,         
    NSString *_wxKey, __Block_byref_mutableWXKey_0 *_mutableWXKey, int flags=0) : test(_test), wxKey(_wxKey), mutableWXKey(_mutableWXKey->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

这个结构体的成员变量mutableWXKey是一个指针类型,既然是拿到了指针,当然就可以修改这个变量的值了,

关于__block这个修饰符,总而言之,其实早在看block定义的代码时就应该发现区别的:

void(myStackBlock)(void) = ((void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, test, wxKey, (__Block_byref_mutableWXKey_0 *)&mutableWXKey, 570425344));

同样都是局部变量,同样都是引用类型,在调用构造函数的时候,未用__block修饰的wxKey直接传入的是值,而用__block修饰的mutableWXKey传入的是地址。

总结

由于自身C++知识不足,在查看main.cpp的代码时比较吃力,并且关于结构体的构造函数,初始化列表的理解可能有很多不足,欢迎讨论。

参考资料

https://www.cnblogs.com/graphics/archive/2010/07/04/1770900.html

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

推荐阅读更多精彩内容

  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,727评论 0 23
  • Block基础回顾 1.什么是Block? 带有局部变量的匿名函数(名字不重要,知道怎么用就行),差不多就与C语言...
    YeeChain阅读 6,682评论 5 61
  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 882评论 1 3
  • block.png iOS代码块Block 概述 代码块Block是苹果在iOS4开始引入的对C语言的扩展,用来实...
    全栈农民工阅读 566评论 0 1
  • Block使用场景,可以在两个界面的传值,也可以对代码封装作为参数的传递等。用过GCD就知道Block的精妙之处。...
    Coder_JMicheal阅读 696评论 2 1