iOS面试相关iOS面试题

Block的本质

2018-03-16  本文已影响37人  雪山飞狐_91ae

1.Block的实现

我们在命令行下输入
clang -rewrite-objc 源代码文件名
就可以把含有block语法的源代码变换为C++的源代码。

int main(int argc, char * argv[]) {
    
    void (^blk)(void) = ^{
     printf("block");   
    };
    
    blk();
    
    return 0;
}

上面是最简单的block
我们把它转化为C++的源代码:

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");  
    }

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

    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));

简化一下:

struct _main_block_impl_0 tmp = _main_block_impl_0(_main_block_func_0, _main_block_desc_0_DATA);
struct _main_block_impl_0 *blk = &tmp;

这一段源码将_main_block_impl_0结构体实例的指针赋值给_main_block_impl_0结构体指针类型的变量。
看一下_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;
}
};

_main_block_impl_0结构体的构造函数有两个参数,第一个参数需要传入一个函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针,第三个参数有值flags = 0。

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

printf("block");
}

这一部分就是相当于block块中的^{};也即匿名函数作为简单的C语言函数来处理。

impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = _main_block_impl_func;
Desc = &_main_block_desc_0_DATA;

初始化里面最有价值的就是初始化impl.FuncPtr。

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

化简一下就是:

(*blk->impl.FuncPtr)(blk);

这就是用结构体指针来调用函数,并且将blk作为参数传入。上面的源码就等价于:

_main_block_func_0(blk);

由此我们也可以看出block是作为参数来传递的。

2.截获自动变量值

变量分为四种,即局部变量,全局变量,静态变量,全局静态变量。这里的局部变量即为自动变量。定义一个静态变量系统会自动初始化,但是定义有一个自动变量系统不会自动帮你初始化,

截获的意思即保存该自动变量的瞬时值,并且在block内不能对该自动变量进行修改。

看下面的代码:

int a = 1;
static int b = 2;

int main(int argc, const char * argv[]) {

    int c = 3;
    static int d = 4;
    NSMutableString *str = [[NSMutableString alloc]initWithString:@"hello"];
    void (^blk)(void) = ^{
        a++;
        b++;
        d++;
        [str appendString:@"world"];
        NSLog(@"1----------- a = %d,b = %d,c = %d,d = %d,str = %@",a,b,c,d,str);
    };
    
    a++;
    b++;
    c++;
    d++;
str = [[NSMutableString alloc]initWithString:@"haha"];
    NSLog(@"2----------- a = %d,b = %d,c = %d,d = %d,str = %@",a,b,c,d,str);
    blk();
    
    return 0;
}

执行结果:

2----------- a = 2,b = 3,c = 4,d = 5,str = haha
1----------- a = 3,b = 4,c = 3,d = 6,str = helloworld

如果在block中对c进行c++操作会报错。
把它转化为c++的源码:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

int a = 1;
static int b = 2;
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *d;
  NSMutableString *str;
  int c;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_d, NSMutableString *_str, int _c, int flags=0) : d(_d), str(_str), c(_c) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *d = __cself->d; // bound by copy
  NSMutableString *str = __cself->str; // bound by copy
  int c = __cself->c; // bound by copy

        a++;
        b++;
        (*d)++;
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_1);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_2,a,b,c,(*d),str);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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[]) {
    int c = 3;
    static int d = 4;
    NSMutableString *str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_0);
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &d, str, c, 570425344));

    a++;
    b++;
    c++;
    d++;
    str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_3);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_4,a,b,c,d,str);
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}

我们还是从block的实现中开始看,实现block的源码是:

void (blk)(void) = ((void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &d, str, c, 570425344));
化简一下,即:
struct _main_block_impl_0 tmp = _main_block_impl_0(_main_block_func_0, _main_block_desc_0_DATA, &d, str, c, 570425344);
struct _main_block_impl_0 *blk = &tmp;

我们看到_main_block_impl_0结构体截获了外部的三个变量,分别是&d,str,c,这里我们就可以知道,结构体截取的是静态局部变量d的地址,指针变量str,还截获了自动变量c的值。
然后看到__main_block_impl_0中多了三个成员变量,即

 int *d;
 NSMutableString*str;
 int c;

然后在_main_block_impl_0这个结构体的构造函数中用截获到的&d,str,c这三个值来分别初始化这三个成员变量。这样,静态局部变量d的地址,指针变量str和实值c就被_main_block_impl_0结构体的成员变量所保存。这样block的实现就全部完成了。

然后看到block的调用,调用block的源码是:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
简化一下:
(*blk->impl.FuncPtr)(blk);
再通俗一点就是:
_main_block_func_0(blk);
我们看一下_main_block_func_0中的实现:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *d = __cself->d; // bound by copy
  NSMutableString *str = __cself->str; // bound by copy
  int c = __cself->c; // bound by copy

        a++;
        b++;
        (*d)++;
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_1);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_2,a,b,c,(*d),str);
    }

刚刚我们在block的实现中知道,_main_block_impl_0结构体截取了局部变量,并且赋值给自己的成员变量保存下来。现在_main_block_func_0函数中的三个临时变量指针d,指针变量str,常量c的值就是由_main_block_impl_0结构体的成员变量的值传递过来的。

3.__block修饰符

因为不能改写被截获的自动变量的值,所以当编译器在变异的过程中检查出给被截获的自动变量的赋值操作时变产生编译错误,不过这样一来就无法在block中保存值了,很不方便。因此就产生了__block修饰符。

int main(int argc, char * argv[]) {
        
        __block int val = 10;
        
        void (^blk)(void) = ^{
            
            val = 1;
        };
    
    return 0;
}

转化为c++的源码:

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;
        }
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, char * argv[]) {

        __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));

    return 0;
}

可以看到增加了__block修饰符后代码量增加了很多。
还是从main函数开始看。可以看到带有__block修饰符的自动变量val被转化成了__Block_byref_val_0结构体类型的实例变量。看一下__Block_byref_val_0结构体:

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

其中的_forwarding是一个指向_Block_byref_val_0结构体实例的指针。最后一个成员变量val相当于原自动变量,这意味着该结构体持有相当于原自动变量的成员变量。然后利用_main_block_impl_0的构造函数来初始化自己的成员变量,尤其注意新增的成员变量:_Block_byref_val_0结构体指针。
下面这段给__block变量复制的代码又如何呢?
^{val = 1;}
该源代码转化如下:

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;
        }

该源代码首先将_main_block_impl_0结构体的成员变量即_Block_byref_impl_0结构体指针传递进来。_Block_byref_val_0结构体实例的成员变量_forwarding持有指向该实例自身的指针。通过成员变量访问成员变量val(成员变量val是该实例自身持有的变量,它相当于原自动变量)。

通过增加__block修饰符是怎么完成修改自动变量的值的呢?

4.Block存储域和__block变量存储域

Block存储域

Block也是oc对象,把Block当做oc的对象来看时,Block所属的类有三种,即_NSConcreteStackBlock,_NSConvreteGlobalBlock,_NSConcreteMallocBlock.

1. _NSConvreteGlobalBlock

在以下两种情况下的任意一种情况都是将Block分配在数据区,即属于_NSConvreteGlobalBlock类。

void (^blk)(void) = ^{
    
    printf("test");
};
int main(int argc, char * argv[]) {
    
    return 0;
}

此源代码通过声明全局变量blk来使用Block语法,我们转化为c++的源代码后会发现其isa指针指向_NSConvreteGlobalBlock,即该Block被分配在数据区。
第二种情况:

int a = 1;
static int b = 2;
int main(int argc, char * argv[]) {
    
    int c = 3;
    
    static int d = 4;
    
    void (^blk)(void) = ^{
        
        NSLog(@"测试数据:a = %d, b = %d, d = %d", a, b, d);
    };
    
    return 0;
}

Block内使用了全局变量,静态全局变量,静态局部变量,唯独没有使用自动变量,因此Block类型就是_NSConvreteGlobalBlock,该Block被分配在数据区。

2. _NSConcreteStackBlock

除了以上两种情况会将Block分配在数据区,其他情况都是讲Block分配在栈区,即Block属于_NSConcreteStackBlock类。

分配在数据区的Block,从变量作用域外也可以通过指针安全地使用。但是分配在栈区的Block,当Block超出变量的作用域范围时,该Block就会被销毁。为了解决这一问题,Block提供了将Block和__block变量从栈区复制到堆区的操作,这样即使Block超过变量的作用域范围,还可以继续访问堆上的Block。

3. _NSConcreteMallocBlock

上面说过_NSConcreteMallocBlock的出现是为了解决当Block分配在栈区时,当Block超出变量的作用域范围时该Block就会被销毁的问题。
在栈区的Block何时复制到堆区多数时候是由编译器决定的。少数时候编译器无法判断的情况是:

向方法或函数的参数中传递Block时。

但是如果在方法或函数中适当的复制了传递过来的参数,那就不必在调用该方法或函数前手动复制了。以下方法或函数不用手动复制:

  • Cocoa框架的方法且方法名中含有usingBlock时。
  • GCD的API。

__block变量存储域

上一篇 下一篇

猜你喜欢

热点阅读