一篇文章剖析block底层源码以及Block.private

关于我的仓库

  • 这篇文章是我为面试准备的iOS基础知识学习中的一篇
  • 我将准备面试中找到的所有学习资料,写的Demo,写的博客都放在了这个仓库里iOS-Engineer-Interview
  • 欢迎star??
  • 其中的博客在简书,CSDN都有发布
  • 博客中提到的相关的代码Demo可以在仓库里相应的文件夹里找到

前言

  • 本文主要是对于《高级编程》类似于总结的学习笔记
  • 其实这一部分本质上就是根据多个block的源代码实例,分析其背后真正的实现原理
  • 在这一块,我会以例子带入来讲,尽可能把每一个block的源码讲清楚
  • 这一块难就难在它的顺序很乱其实,这个知识点会涉及到好几个别的知识点,这些知识点我就统一写在后面,大家看到看不懂的地方,看目录翻后面的就行
  • 每一部分的源代码都是现场手动生成

准备工作

  • 阅读《Objective-C 高级编程》中的p.91 ~ 136

最简单block

//OC代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^blk)(void) = ^{
            
            printf("Block\n");
        };
        
        blk();
    }
    return 0;
}
//经过clang转换后的C++代码
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;
  __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;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself {

            printf("Block\n");
}

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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}
  • 我们把代码分成几块,一块一块分析

__block_impl结构体

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  • 名字中的impl即implementation的缩写,换句话说这一部分是block的实现部分结构体
  • void *isa:
    • C语言中void * 为 “不确定类型指针”,void *可以用来声明指针。
    • 看到isa就会联想到之前在objc_class结构体,因此我们的block本质上也是一个对象【而且是个类对象】
    • 我们知道实例对象->类对象->元类构成了isa链中的一条,而这个__block_impl结构体占据的是中间类对象的位置
    • 实例对象应该是生成的block变量,个人认为
    • 因此这里的isa指针会指向元类,这里的元类主要是为了说明这个块的存储区域【详见:Block存储域&&Block元类】
  • int Flags:
    • 标识符,在实现block的内部操作时会用到
  • int Reserved:
    • 注明今后版本升级所需区域大小Reserved
    • 一般就是填个0
  • void *FuncPtr:
    • 函数指针
    • 实际执行的函数,也就是block中花括号里面的代码内容,最后是转化成一个C语言函数执行的

struct __main_block_impl_0结构体

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __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;
  }
};
  • 该结构体才是块的完整结构
  • struct __block_impl impl:
    • 就是上面的结构体,作为块的实现部分
  • struct __main_block_desc_0* Desc:
    • 这里的desc即description,作为块的补充信息
    • 下面分析
  • __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0)
    • 这是该结构体的构造函数
    • 其中fp是实际执行的C语言函数指针
    • int flags = 0是C++中的缺省参数,表示默认是0
    • 具体内容就是对impl中相应的内容进行赋值,要说明的是impl.isa = &_NSConcreteStackBlock这个参看Block存储域&&Block元类

static void __main_block_func_0(struct __main_block_impl_0 *__cself)

static void __main_block_func_0(struct __main_block_impl_0 *__cself {

            printf("Block\n");
}
  • 这一块就是Blcok执行的实际代码块,就如我上面所说,它被转换为了一个C++函数
  • 它也是上面的fp函数指针指向的内容
  • 这里要注意的是传入的这个__cself参数,他其实就是C语言版的self,代表的就是block本身,毕竟其数据类型就是struct __main_block_impl_0
  • 当然从这段代码看不出来传入的cself有什么用,因为我们的代码就只输出一段话,没有用到捕获的变量,后面会讲到cself到底怎么用

static struct __main_block_desc_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)};
  • size_t reserved:
    • 今后版本升级所需区域大小
    • 一般就填0
  • size_t Block_size:
    • Block大小
  • __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    • 这就是和我们平时用结构体一样,在定义完最后写一个结构体实例变量,变量名就是__main_block_desc_0_DATA
    • 其中reserved为0,Block_size是sizeof(struct __main_block_impl_0)

主函数【blk实际调用】

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}
  • 正常人看到这一段都晕了?
  • 我们把所有强制类型转换去掉,看个正常人看的懂的版本:
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 这一段就是通过构造函数构造一个__main_block_impl_0结构体赋值给blk变量
// 翻译如下
struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 blk = &temp;

// 下面是调用block中函数的过程,我们可以看到我们要调用的其实就是FuncPtr这个函数指针指向的函数
// 查看__main_block_func_0的参数,发现就是我们上面研究的cself
// 所以,该调用翻译如下:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
blk->FuncPtr(blk);
  • 上面值得注意的是,在使用构造函数的时候,我们传入的参数一个是我们的block函数指针,一个是在定义结构体的时候定义的__main_block_desc_0_DATA
  • 好,这就是不涉及截获自动变量的最简单Block分析,下面我们来看捕获自动变量的情况

截获自动变量的block

  • 这里我们又需要看一波C++源码,这里面会有很多一样的代码,我就不做分析了

  • GOGOGO

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int dmy = 256;
        int val = 10;
        const char  *fmt = "val = %d\n";
        void (^blk)(void) = ^{
            
            printf(fmt, val);
        };
        val = 2;
        fmt = "THESE VALUES WERE CHANGED. val = %d\n";
        blk();
    }
    return 0;
}
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;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), 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 {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
  
	printf(fmt, 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d\n";
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
        val = 2;
        fmt = "THESE VALUES WERE CHANGED. val = %d\n";
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}

新的__main_block_impl_0

  • 我们会看到__block_impl结构体没有任何变化,而__main_block_impl_0多了点东西
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 我们的fmt,val这两个被block截获的自动变量被放入到该结构体当中,同时构造函数也发生了变化,构造时要给fmt,val赋值
  • 这里我们就能大概猜出截获自动变量的原理了,自动变量会被存入block结构体
  • 在这里也要注意我们等于是使用了一个长得一模一样,保存在结构体里的数来进行的赋值操作,所以我们不能对它进行赋值操作,因为我们操作的只能是我们自己建的数据,而不会是我们真正的变量

新的__main_block_func_0函数

  • 这次就会用到上面说的cself了
static void __main_block_func_0(struct __main_block_impl_0 *__cself {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy
  
	printf(fmt, val);
}
  • 这里在实际调用时,我们还是只需要传入一个cself,我们就会看到在函数内部,我们进行操作的拿来printf不是原来的fmt和val,而是通过块结构体保存的这两个值

没有截获自动变量,而是使用静态变量,全局变量情况

  • 在前一篇文章,我们了解到,对于截获的自动变量,不能直接修改它的值,而对于静态变量,全局变量时OK的,我们来看下对于这些变量block是怎么处理的
int global_val = 10; // 全局变量
static int static_global_val = 20; // 静态全局变量

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int static_val = 30; // 静态局部变量
        
        void (^myLocalBlock)(void) = ^{
            global_val *= 1;
            static_global_val *= 2;
            static_val *= 3;
            
            printf("static_val = %d, static_global_val = %d, global_val = %d\n",static_val, static_global_val, global_val);
        };
        
        myLocalBlock();
    }
    return 0;
}
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

int global_val = 10;
static int static_global_val = 20;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_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_val = __cself->static_val; // bound by copy

            global_val *= 1;
            static_global_val *= 2;
            (*static_val) *= 3;

            printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, global_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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        static int static_val = 30;

        void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));

        ((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
    }
    return 0;
}

新的__main_block_impl_0

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 我们发现全局变量,静态全局变量,我们的Block都没有用结构体去特地保存它
  • 只有对于我们的静态局部变量会来保存,但这里要注意,我们使用的不是int static_val,而是int *static_val
  • 也就是说我们使用一个指针来保存的静态局部变量
  • 它会直接保存该变量的地址,之后的操作也是直接对该值本身进行操作,而不是向之前截获的那些变量,等于是重新开辟空间进行保存

新的__main_block_func_0函数

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *static_val = __cself->static_val; // bound by copy

            global_val *= 1;
            static_global_val *= 2;
            (*static_val) *= 3;

            printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, global_val);
}
  • 这里我们使用的global_val以及static_global_val都是直接调用,只有static_val是通过指针获取值,进行修改
  • 那么这种做法看起来很不错,为什么在截获自动变量的时候我们不用指针传值而是要用值传值呢?
    • 原因在于,我们的静态变量是存在数据区的,在程序结束前它其实一直都会存在,之所以会被称为局部,只是说出了作用域无法调用到它了,并不是说这块数据不存在了。因此我们只要自己准备好一个指针,保证出了作用域依然能调用到他就行;而对于自动变量,它们真正的问题在于一但出了作用域,直接被释放了,所以要在结构体里开辟空间重新存放,进行值传递

使用__block修饰符的情况

  • 在前一篇文章,我们了解到,对于截获的自动变量,不能直接修改它的值,而对于静态变量,全局变量时OK的,我们来看下对于这些变量block是怎么处理的
  • __block 修饰符类似于 staticautoregister 说明符,它们用于指定将变量值设置到哪个存储域中。例如auto 表示作为自动变量存储在中, static表示作为静态变量存储在数据区中。
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int val = 10;
        void (^blk)(void) = ^{
            val = 1;
            printf("val = %d\n", val);
        };
        blk();
    }
    return 0;
}

struct __Block_byref_val_0 {
 void *__isa;
 __Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

            (val->__forwarding->val) = 1;
            printf("val = %d\n", (val->__forwarding->val));
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}
  • 当你觉得block的代码已经够多的时候,__block源代码反手给你个超级加倍告诉你什么才是多?

__Block_byref_val_0结构体

struct __Block_byref_val_0 {
 void *__isa;
 __Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};
  • 对于使用__block修饰的变量,不管在块里有没有使用,都会相应的给他生成一个结构体
  • 这里的isa指针默认都是传的空,但实际上是对于C语言基础数据类型会是0,因为他们不是对象没有所属类,而对于对象其实isa指针指向的就是所属类
  • 但为什么看源码会是全部都赋值为0呢,因为OC是一门动态语言,运行的时候才会确定下来,不放心的话可以通过class方法查看下
  • 关于__forwarding参看__block的拷贝部分
  • flags标志符位
  • size大小
  • val变量本身

__main_block_impl_0结构体

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
  • 这部分值得注意的是,对于我们的__Block_byref_val_0结构体,我们同样是用一个指针去保存,这么做的原因是通过__block修饰的变量可能会被不止一个block使用,使用指针可以保证其可以被多个block调用

__main_block_func_0函数

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

            (val->__forwarding->val) = 1;
            printf("val = %d\n", (val->__forwarding->val));
}
  • 这里看到我们用val截获下来的就是一个__Block_byref_val_0结构体了,对它进行赋值的时候需要通过forwarding指针进行
  • 下面我们先看下主函数

主函数

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    return 0;
}
  • 主要关注下__Block_byref_val_0结构体的赋值
__Block_byref_val_0 val = {
    (void*)0,
    (__Block_byref_val_0 *)&val, 
    0, 
    sizeof(__Block_byref_val_0), 
    10
};
  • isa为0上面解释过了,forwarding为自身的地址,flags为0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值