日常开发中block的用处可以说是无处不在,也许你已经能够很熟练的运用block.也许你已经很有了足够多使用block的经验可以轻而易举的避免block背后的风险(比如在调用block的时候 判断block是否为nil 在调用block的时候是否会循环引用....) .但是你知道block的本质是什么吗?执行block() 为啥会回调前面的^{ // }; 代码快中呢? 如果不清楚那么我们带着这些疑问一步一步来操作,一行一行代码来解释.(清楚的请忽略此文章)
我大概会分这么几部去分析
1先一个简单的工程
在main.m的main函数中实现一个很简单的block
// main.m
// block1
//
// Created by lcc on 2019/1/23.
// Copyright © 2019年 lcc. All rights reserved.
//
#include
int main(int argc, char * argv[]) {
void(^lccBlock)(void) = ^{
printf("--");
};
lccBlock();
}
2用Clang吧main.m转化成main.cpp代码
如图3用分析上图生成的main.cpp代码
短短的一个几行代码当我们转成.cpp代码的时候 你会发现变成了很多,我们挑出有用的代码复制到main.m中因为包含c++代码所以.m后缀我们改成.mm后缀. 并逐行分析没行代码
//
// main.m
// block1
//
// Created by lcc on 2019/1/23.
// Copyright © 2019年 lcc. All rights reserved.
//
#include
#define __OBJC_RW_DLLIMPORT extern
#ifdef __OBJC_EXPORT_BLOCKS
extern "C" __declspec(dllexport) void _Block_object_assign(void *, const void *, const int);
extern "C" __declspec(dllexport) void _Block_object_dispose(const void *, const int);
extern "C" __declspec(dllexport) void *_NSConcreteGlobalBlock[32];
extern "C" __declspec(dllexport) void *_NSConcreteStackBlock[32];
#else
__OBJC_RW_DLLIMPORT void _Block_object_assign(void *, const void *, const int);
__OBJC_RW_DLLIMPORT void _Block_object_dispose(const void *, const int);
__OBJC_RW_DLLIMPORT void *_NSConcreteGlobalBlock[32];
__OBJC_RW_DLLIMPORT void *_NSConcreteStackBlock[32];
#endif
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("--");
}
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, char * argv[]) {
//1 下面是源码 但是这里是报错的.没关系我们先注释掉 我们先分析下 这行代码
// void(*lccBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
/*
等号左边 void(*lccBlock)(void) lccBlock就是一个指针 这个指针是什么类型的呢 他的类型可以void(*)(void)这样表示 就是一个没有返回值 没有参数的函数指针
等号右边 ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)) 比较复杂我们先一点一点分析
首先我们先看一下 __main_block_impl_0这个结构体 这个是c++的结构体 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0){} 就是这个结构体的构造函数 (void (*)()) 就是类型强转 等号左边是一个指针 右边肯定也得是指针 等号右边就是一个结构体的实例的地址 被强转成了(void (*)())类型 我们可以拆分一下
*/
//生成一个结构体实例 我们看__main_block_impl_0()这个构造函数接受的参数 第一个是void *fp 任何类型的指针 我们传了一个__main_block_func_0函数的指针 第二个参数 __main_block_desc_0结构体指针 __main_block_desc_0_DATA就是 __main_block_desc_0类型的一个实例 我们穿的第二个参数就是 __main_block_desc_0_DATA这个实例的地址没问题 第三个参数是默认参数外界不用传.
struct __main_block_impl_0 __main_block_impl_0_instance = __main_block_impl_0((void *)__main_block_func_0,&__main_block_desc_0_DATA);
//这里 lccBlock 指针指向 NULL
void(*lccBlock)(void) = NULL;
//__main_block_impl_0 类型的指针
struct __main_block_impl_0 *p = &__main_block_impl_0_instance;
//强制类型转换后赋值给 lccBlock lccBlock说白了 就只一个结构体指针而已
lccBlock = (void (*)())p;
//简化结果就是下面
// void(*lccBlock)(void) = (void (*)())&__main_block_impl_0_instance;
//我们打开下面注释 分析一下
// ((void (*)(__block_impl *))((__block_impl *)lccBlock)->FuncPtr)((__block_impl *)lccBlock);
((void (*)(__block_impl *))((__block_impl *)lccBlock)->FuncPtr)((__block_impl *)lccBlock);
//((void (*)(__block_impl *))((__block_impl *)lccBlock)->FuncPtr)就是函数名 ((__block_impl *)lccBlock)这个括号里面(__block_impl *)lccBlock就是 函数的参数
//这个函数 其实就是 FuncPtr 函数 我们分析上面 __main_block_impl_0结构体 也就是是 __main_block_impl_0_instance实例里面impl结构体实例的一个 FuncPtr
// 步骤1((__block_impl *)lccBlock) 这里是吧 lccBlock指针类型 强转成了 __block_impl类型的指针
// 步骤2((__block_impl *)lccBlock)->FuncPtr) 这里其实就是 p->impl.FuncPtr 这里我就有个疑问了 就算指针lccBlock的类型可以强转换 但是真正指向的地址是不变的啊 lccBlock 一直都是指向的 __main_block_impl_0_instance这个结构体的地址啊
// 步骤3(void (*)(__block_impl *)) 强转的类型 说面吧 ((__block_impl *)lccBlock)->FuncPtr 这个指针强转成了 (void (*)(__block_impl *))的函数指针 这个函数的参数是__block_impl *指针 所以步骤1才会把 lccBlock指针类型 强转成了 __block_impl类型的指针
//我们这样也可以达到和上面 相同的效果
((void(*)(__main_block_impl_0 *))(p->impl.FuncPtr))(p);
}
//int main(int argc, char * argv[]) {
// void(^lccBlock)(void) = ^{
// printf("--");
// };
//
// lccBlock();
//}
以后会分析 带参数的block 待返回值的block block如何捕获变量 __block的原理