iOS Principle:Block(中)

简介: iOS Principle:Block(中)

3.再来看看最终调用Block的时候和没有截获的区别


((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);  
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  
    const charchar *ch = __cself->ch; // bound by copy  
    int b = __cself->b; // bound by copy  
    printf(ch,b);  
}


可以看出block()这句代码转换的调用方式没有任何变化 还是转换为一下这句话,而且把本身结构体作为参数进行了传递

(*block-impl.FuncPtr)(block)


现在注意看__main_block_func_0这个C语言的函数,和之前相比这里对传递过来的block结构体参数进行了变量获取__cself->ch 和 __cself->b(这两个变量已经在Block表达式之前进行了声明定义),并最终打印出来


但是,Block中所捕获的变量就犹如“带有局部变量值的匿名函数”所说,仅仅截获局部变量的值而已,如果在截获的Block里面重写局部变量也不会改变原先所截获的局部变量

block 引用外部对象时候,不是简单的指针引用(浅复制),而是一种重建(深复制)方式(括号内外分别对于基本数据类型和对象分别描述)


所以如果在 block 中对外部对象进行修改,无论是值修改还是指针修改,自然是没有任何效果


例如:


int a = 0;
void (^block)(void) = ^{a = 1};

这样写直接就编译出错了!!!


可以直接看__block_main_block_impl_0的实现上,并不能改写其捕获变量的值,因此直接报错了


综上所述,所谓的“截获自动变量”,无非就是在执行Block语法的时候,Block语法所用到的局部变量值被保存到Block的结构体实例当中(即所谓的Block本体)


5.要在 Block 中改变里面的值


第一种方法:运用C语言中的变量

  • 静态变量
  • 静态全局变量
  • 全局变量
int global_val = 1; // 全局变量  
static int static_global_val = 2; // 静态全局  
int main(){  
    static int static_val = 3; // 静态局部  
    void (^block)(void) = ^{  
        global_val *= 1;  
        static_global_val *= 2;  
        static_val *= 3;  
    };  
    block(); 
}


这里全局和静态全局问题不大,出了作用于照样能访问,但是静态局部的话如果去掉Static按上面那种写法,就直接报错了,原因就是实现上不能改变捕获的局部变量的值。

没错,应该能想到了,能改变的情况就是直接让Block截获局部变量的指针,看Clang的源码


int global_val = 1;  
static int static_global_val = 2;  
struct __main_block_impl_0 {  
    struct __block_impl impl;  
    struct __main_block_desc_0* Desc;  
    intint *static_val;  
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, intint *_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) {  
    intint *static_val = __cself->static_val; // bound by copy  
    global_val *= 1;  
    static_global_val *= 2;  
    (*static_val) *= 3;  
}  
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(){  
    static int static_val = 3;  
    void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));  
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);  
    printf("%d\n",global_val);  
    printf("%d\n",static_global_val);  
    printf("%d\n",static_val);  
}

很简单就能看出区别了

  • 在__main_block_impl_0这个结构体中的追加的成员变量变成了int *_static_val指针了
  • 在结构体实例化函数中__main_block_impl_0参数也变为了&static_val地址了


static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  
    intint *static_val = __cself->static_val; // bound by copy  
    global_val *= 1;  
    static_global_val *= 2;  
    (*static_val) *= 3;  
}


根据Block语法转换而来的C静态函数,使用static_val的指针进行访问,上面参数可以看出,初始化的时候将静态变量的static_val指针传递给__main_block_impl_0结构体进行追加成员变量初始化并保存,这样做也是对超出作用于使用变量的最简单的方法


第二种方法:就是大家所熟知的__block修饰符的使用


__block int a = 0;
void (^block)(void) = ^{a = 1};

Clang 转化后代码


struct __Block_byref_a_0 {  
    void *__isa;  
    __Block_byref_a_0 *__forwarding;  
    int __flags;  
    int __size;  
    int a;  
};  
struct __main_block_impl_0 {  
    struct __block_impl impl;  
    struct __main_block_desc_0* Desc;  
    __Block_byref_a_0 *a; // by ref  
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref  
    (a->__forwarding->a) = 1;
}  
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}  
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
    _Block_object_dispose((void*)src->a, 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(){  
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 0};  
    void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));  
    return 0; 
}


代码多了不少,分解一下


__block int a = 0;


Clang 的代码如下


__Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a,0,sizeof(__Block_byref_a_0),0};


这货竟然加了_block变成了结构体实例,在栈上生成了__block_byref_a_0的结构体实例,a变量初始化为0,这个值也出现在了这个结构体成员变量变量中,意味着该结构体持有相当于原局部变量的成员变量


struct __Block_byref_a_0 {  
 *__isa;  
    __Block_byref_a_0 *__forwarding;  
    int __flags;  
    int __size;  
    int a;  
};


这个初始化的原结构,里面的最后一个int a就是原局部变量的成员变量


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


分解如下


void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));


还是调用__main_block_impl_0的初始化结构体,由于int a加了__block修饰符,那么这个初始化函数传递的不再是&a的地址,而是换成__Block_byref_a_0这个结构体实例的指针进行传递(该结构体其实也是个对象,最后的属性放着需要截获的局部变量)

这里的__main_block_func_0对应的block块函数转换为C


static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  
    __Block_byref_a_0 *a = __cself->a; // bound by ref  
    (a->__forwarding->a) = 1;
}


看了两个方案,第一个用C语言的静态变量来解决,那么直接操作静态变量的指针就完事了,但是__block要比这个真的复杂多了。Block的结构体实例持有指向__block变量的 __Block_byref_a_0结构体实例的指针。


__Block_byref_a_0这个结构体实例的成员变量__forwarding持有指向该结构体实例自身的指针。通过__forwarding访问成员变量a。


另外提一点,__block内部的__Block_byhef_val_0这个也是独立的结构体实例,不属于__main_blocl_impl_0结构体中,这样方便了在多个block中都能访问__block变量。

小结一下,不然太乱:


1.普通的局部变量被Block截获只是单纯的截获其值,并追加到__main_block_impl_0结构体中(无法修改重写)


2.要允许Block修改值有两个方法,第一个就是用C语言的变量,代表就是静态局部变量,第二个就是__block修饰符


3.__block修饰的变量,其实这就是变成了个对象结构体__Block_byref_val_0,初始化__main_block_impl_0的时候追加的是__block生成的结构体指针,例如__block int a = 0;这个int不在是普通的数据类型,而是在标识符的引用下变成了对象

struct __Block_byref_a_0 {  
    void *__isa;  
    __Block_byref_a_0 *__forwarding;  
    int __flags;  
    int __size;  
    int a;  
};



而int的值就存储在这个对象的一个字段 a中,而Block截获的就是这个结构体对象的指针,可以随意修改结构体内部a的值


4.最终在__main_block_func_0函数中调用__block结构体指针的forwarding指针(这一节指向自己,后面会变)的成员变量a来进行重新赋值


5.其实__block这方法生成的源码,能大致看出来这其实类似OC里面对象的retain和release一系列操作


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