iOS底层原理之Block

前言

Block 是 C 语言的扩充功能, Apple 在 iOS4 引入了这个新功能. 一句话形容 Block, 那就是带有自动变量(局部变量)的匿名函数.

在 OC 中实现代码如下

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

结构图

在 Block_layout 中有 isa 指针, 所以 Block 在 OC 中是按照对象来处理的. 常见的 Block 有 3 种类型, 分别是 _NSConcreteStackBlock,_NSConcreteMallocBlock,_NSConcreteGlobalBlock.

研究工具: clang命令

为了研究编译器的实现原理, 需要使用 clang 命令. clang 命令可以将 OC 的源码转换为 C/C++ 语言的代码.

clang -rewrite-objc 文件名

一. Block 捕获外界变量的本质

C语言中变量分为以下几种:

  • 自动变量
  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量

先来一段测试代码

int global_i = 1;//全局变量

static int static_global_j = 2;//静态全局变量

int main(int argc, const char * argv[]) {
    
    static int static_k = 3;//静态变量
    int val = 4;//自动变量
    int val2 = 5;
    void(^myBlcok)(void) = ^ {
        global_i++;
        static_global_j++;
        static_k++;
        //val++;
        NSLog(@"中: global_i = %d, static_global_j = %d, static_k = %d, val = %d", global_i, static_global_j, static_k, val);
        NSLog(@"%p", &val2);
    };
    global_i++;
    static_global_j++;
    static_k++;
    val++;
    NSLog(@"前: global_i = %d, static_global_j = %d, static_k = %d, val = %d", global_i, static_global_j, static_k, val);
    NSLog(@"%@", myBlcok);
    NSLog(@"%p", &val2);
    myBlcok();
    NSLog(@"后: global_i = %d, static_global_j = %d, static_k = %d, val = %d", global_i, static_global_j, static_k, val);
    return 0;
}


这段代码运行的时候会报错,提示说自动变量没有加上__block修饰.

用 clang 转换的到.cpp文件,源码如下

int global_i = 1;

static int static_global_j = 2;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_k;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_k, int _val, int flags=0) : static_k(_static_k), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_k = __cself->static_k; // bound by copy
    int val = __cself->val; // bound by copy:此处Block仅仅捕获了val值,并没有捕获val的内存地址.所以在这个函数中即使重写这个val的值,依旧无法改变Block外面val的值.因此OC在编译层面就杜绝了这种错误,在Block中无法改变自动变量的值,编译器会报错.

        global_i++;
        static_global_j++;
        (*static_k)++;

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_b6_dgrlb0m175gd2m38vy8qgxzw0000gp_T_main_7d7194_mi_0, global_i, static_global_j, (*static_k), val);
    }

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[]) {

    static int static_k = 3;

   int val = 4;

    void(*myBlcok)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_k, val));
    global_i++;
    static_global_j++;
    static_k++;
    val++;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_b6_dgrlb0m175gd2m38vy8qgxzw0000gp_T_main_7d7194_mi_1, global_i, static_global_j, static_k, val);
    ((void (*)(__block_impl *))((__block_impl *)myBlcok)->FuncPtr)((__block_impl *)myBlcok);
    return 0;
}

首先全局变量 global_i 和静态变量 static_global_j 的值增加,是因为他们是全局的,作用域很广,所以 Block 捕获了它们之后,在 Block 里面进行 ++ 操作, Block 结束后,它们的值可以保存下来.

静态变量 static_k 和自动变量 i
__main_block_func_0 中可以看到 静态变量 static_k 和 自动变量 i 被 Block 从外部捕捉进来,成为__main_block_func_0 这个结构体的成员变量了.

如果 Block 外面有很多变量,但这些变量并不会在 Block 里面使用到,那么这些变量不会被 Block 捕获进来.

__main_block_func_0 中, 自动变量 val 虽然被捕获进来了, 但是是用 __cself->val 访问的. Block 只是获取了 val 的值,并没有获取到存放 val 的内存地址.所以在__main_block_func_0 这个函数中改变 val 的值,依旧无法改变 Block 外部自动变量 val 的值.

4种变量中只有 静态变量 , 静态全局变量 , 全局变量 这3种是可以在 Block 里面被改变值的.

  1. 静态全局变量,全局变量由于作用域的原因,可以在 Block 里面被改变,存储在全局区
存储图
  1. 静态变量传递给 Block 的是内存地址值,所以能在 Block 里面直接改变值.

  2. 对于自动变量而言,要想在 Block 里面改变自动变量的值,需要在自动变量前加上 __block 关键字(改变存储区).

小结:

在 Block 中改变变量值有2种方式, 一是传递内存地址指针到 Block 中, 二是改变存储区域.

二. Block 的 copy 和 dispose

在 OC 中, 一般 Block 分为3种, _NSConcreteStackBlock, _NSConcreteMallocBlock, _NSConcreteGlobalBlock.

先来比较一下区别,

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

推荐阅读更多精彩内容