iOS Principle:Block(上)

简介: iOS Principle:Block(上)

方便记忆:


  • Block 实现了对C的闭包实现,一个带有局部变量的匿名函数
  • 源码结构分析
  • 本体部分:block实际的结构体部分
  • 成员变量:impl和Desc
  • 结构体的构造函数:__main_block_impl_0
  • 捕获外部变量
  • 局部变量作为Block结构体的成员变量追加到了__main_block_impl_0
  • 根据传递给构造函数的参数对由局部变量追加的成员变量进行初始化
  • __main_block_func_0中进行了变量获取_cself->ch(变量的值-深拷贝)
  • 改变Block里的值
  • c方法:静态变量、静态全局变量、全局变量
  • __block修饰符:变成了结构体实例,初始化_main_block_impl_0的时候追加的是_block生成的结构体指针
  • 三种Block本体
  • NSConcreteStackBlock:在局部定义的(栈)
  • NSConcreteGlobalBlock:在全局中定义的(数据区域)-不存在对局部变量的捕获
  • NSConcreteMallocBlock:分配在堆中-Block从栈上复制到堆上的方法

Block其实就是C语言的扩充功能,实现了对C的闭包实现,一个带有局部变量的匿名函数。


1.声明一个Block:


使用场景一:

返回值 (^名称) (参数列表) = ^(参数列表) { };

int (^name)(int, int) = ^(int a, int b) {
    return (a+b);
};


使用场景二:

作为一个函数的参数:

- (void)testBlock:(NSString *(返回类型) (^)(int a))s (block名字) {
    NSString *a = s(1);
}
{
    [self testBlock:^NSString *(int a) {
        a = 5;
        return @"1";
    }];
}


2.Demo 中文件对照



image.png


  • mainTestBlock.cpp -> 基本的block转译
  • mainTestBlockValue.cpp -> block捕获外部变量
  • mainTestBlockValueC.cpp -> 通过C语言变量访问值
  • mainTestBlockValueValueBlock.cpp -> 通过__block变量访问值


3.从底层分析Block的实现


先从最简单的看起

void (^block)(void) = ^(void) {
};
block();

使用 Clang(LLVM)转化为 C++ 的实现

clang -rewrite-objc 文件名

注意:由于 ViewController 中使用 UIKit 库,编译时会出现找不到文件的情况。


image.png


转化后的代码如下:

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("aaa\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() {  
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));  
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);  
    return 0;  
}


源码结构分析

1.block实际的结构体部分(本体)

struct __main_block_impl_0 {  
    struct __block_impl impl; // 结构体 impl,见下 2
    struct __main_block_desc_0* Desc; // 结构体 Desc,见下 3
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {  // 构造函数,见下 4
        impl.isa = &_NSConcreteStackBlock;  
        impl.Flags = flags;  
        impl.FuncPtr = fp;  
        Desc = desc;  
    }  
};


2.再看看第一个成员变量impl

struct __block_impl {  
    void *isa; // struct 中,首地址是 *isa,证明 block 是 objc 中的对象
    int Flags; // 标记
    int Reserved; // 标记
    void *FuncPtr; // 函数指针,block所需要执行的代码段
};


他的第一个属性也是一个结构__block_impl,而第一个参数也是一个isa的指针。

在运行时,NSObject和block的isa指针都是指向对象的一个8字节。 NSObject及派生类对象的isa指向Class的prototype,而block的isa指向了_NSConcreteStackBlock这个指针。 就是说当一个block被声明的时候他都是一个_NSConcreteStackBlock类的对象。

3.第二个成员变量Desc


static struct __main_block_desc_0 {  
    size_t reserved; // 版本升级所需的区域
    size_t Block_size;  // Block的占用空间大小
}

4.第三个就是这个结构体的构造函数(可以理解为对象的初始化方法)


__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {  
    impl.isa = &_NSConcreteStackBlock; // block 的本体类型,后面专门讲解
    impl.Flags = flags;  
    impl.FuncPtr = fp; // void *fp的指针赋值给了FuncPtr指针
    Desc = desc; // desc 结构体的指针传入的初始化
}

以下是源码调用分析

5.现在来看看Main函数中调用的基本转换(初始化转换)

void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));


看起来这个函数好吓人啊,没事,咱们把类型都去了,而且分开两步来写

/* 调用结构体函数初始化   
struct __main_block_impl_0 impBlock = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);  
/* 赋值给该结构体类型指针变量  
struct __main_block_impl_0 *block = &impBlock;

把栈上生成的__main_block_impl_0的结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量block


__main_block_func_0就是转换成的函数指针,这样void (*block)(void) = ^{}这句简单的代码最终就是上面的结构体初始化函数的内部实现逻辑

6.再来细看下初始化函数内部的实现__block_main_impl_0

__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);


第一个参数是由Block块语法转换的正真内部函数指针,第二个参数就是作为静态全局变量初始化__main_block_desc_0的结构体实例指针,初始化如下


__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};


最终,初始化内部进行赋值

isa = &_NSConcreteStackBlock
Flags = 0
Reversed = 0
FuncPtr = __main_blcok_func_0 // 就是Block块代码转换成的C语言函数指针
Desc = &__ main_block_desc_0_DATA


7.最终 block() 调用的内部实现

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

老规矩 ,去掉类型就是


(*block->imp.FuncPtr)(block);

打印换行

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  
printf("aaa\n");}

没错,最后就是直接调用函数指针进行最终的调用,由上面所描述的,FuncPtr就是由__main_block_func_0的函数指针所赋值的指针,而且可以看出,这个函数的参数正是指向block本身结构体实例,block作为参数进行了传递


综上所述,block的实质,就是一个对象,包含了一个指向函数首地址的指针,和一些与自己相关的成员变量。


4.Block 访问外部变量是怎么回事


int main(){  
    int a = 100;  
    int b = 200;  
    const char *ch = "b = %d\n";  
    void (^block)(void) = ^{   
    };  
    b = 300;  
    ch = "value had changed.b = %d\n";  
    block();  
    return 0;  
}

Clang 转化后代码


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 *ch;
    int b;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_ch, int _b, int flags=0) : ch(_ch), b(_b) {
        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 *ch = __cself->ch; // bound by copy
    int b = __cself->b; // bound by copy
    printf(ch,b);
}
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 a = 100;
    int b = 200;
    const char *ch = "b = %d\n";
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, ch, b));
    b = 300;
    ch = "value had changed.b = %d\n";
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}


1.首先看看Block的内部结构本尊和没有截获的区别


struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    const char *ch;
    int b;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_ch, int _b, int flags=0) : ch(_ch), b(_b) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};


根据main函数里面的Block语法,这里把Block块表达式里面的局部变量作为了这个Block结构体的成员变量追加到了__main_block_impl_0的结构体中

值得注意的是:

  • 结构体内声明的成员变量类型与局部变量类型完全相同
  • 语法中没有使用的局部变量(例如咱们这里的a变量)不会被追加
  • 细节需要注意,这里截获的ch是不可修改的,而且捕捉的b只是截获了该局部变量的值而已(下面在将如何截获指针)

2.再来看看该结构体实例化的构造函数以及调用和没有截获的区别


__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const charchar *_ch, int _b, int flags=0) : ch(_ch), b(_b) {  
    impl.isa = &_NSConcreteStackBlock;  
    impl.Flags = flags;  
    impl.FuncPtr = fp;  
    Desc = desc;  
}  
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, ch, b));

根据传递给构造函数的参数对由局部变量追加的成员变量进行初始化。(这里传递的参数仅仅只是ch和b的值而已)


理解为把Block语法块里面截获的局部变量对__main_block_impl_0的成员追加并且传递赋值,如:


impl.isa = &_NSConcreteStackBlock;
impl.Flags = 0;
impl.FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
ch = "b=%d\n";
b = 200;

由此可以看出,__main_block_impl_0的结构体(即Block)对自动变量进行了截获


目录
相关文章
|
iOS开发
iOS block修饰符用copy还是strong
iOS block修饰符用copy还是strong
144 0
|
iOS开发 Python
iOS小技能:lldb打印block参数签名
iOS逆向时经常会遇到参数为block类型,本文介绍一个lldb script,可快速打印出Objective-C方法中block参数的类型。
199 0
iOS小技能:lldb打印block参数签名
|
iOS开发 开发者
iOS开发 - 如何写出漂亮的block
iOS开发 - 如何写出漂亮的block
104 0
|
iOS开发
iOS开发- 关于Block的几种应用
iOS开发- 关于Block的几种应用
117 0
|
自然语言处理 iOS开发
IOS——Block
IOS——Block
79 0
|
iOS开发
iOS代理 通知 block传值的规范写法
iOS代理 通知 block传值的规范写法
145 0
|
iOS开发
iOS Principle:CGAffineTransform
iOS Principle:CGAffineTransform
182 0
iOS Principle:CGAffineTransform
|
安全 Unix API
iOS Principle:CALayer(下)
iOS Principle:CALayer(下)
169 0
iOS Principle:CALayer(下)
|
iOS开发
iOS Principle:CALayer(中)
iOS Principle:CALayer(中)
148 0
iOS Principle:CALayer(中)
|
API C语言 iOS开发
iOS Principle:CALayer(上)
iOS Principle:CALayer(上)
177 0
iOS Principle:CALayer(上)