MENU

Objective-C之Blocks(三)

February 3, 2017 • Read: 456 • iOS

前言

Objective-C之Blocks(二)中,说明了Block的实质,本文在此基础上解释Block特性的实现。

  • 截获自动变量值
  • __block说明符

截获自动变量

我们知道,Block可以截获自动变量,但是实现原理是什么呢?我们先把Block截获自动变量源代码通过clang转换一下。
截获自动变量源代码
转换后的代码:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  char *str;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_str, int flags=0) : str(_str) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  char *str = __cself->str; // bound by copy

        printf("%s",str);}

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[]) {
    int a = 1;
    int c = 2;
    char *str = "Hello,World!";
    void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

不难发现,截获的自动变量被追加到了__main_block_impl_0结构体中,而没有使用的变量则没有截获。看看初始化改结构体的构造函数:

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, char *_str, int flags=0) : str(_str)

在初始化结构体的时候,根据传入的参数对追加在结构体内的自动变量赋值。通过构造函数的调用来确认参数:

void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, str));

传入的参数即为str,所以block内部追加的变量的值即为外界自动变量str的值。
我们在来看调用Block的方法

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  char *str = __cself->str; // bound by copy
        printf("%s",str);}

在之前的文章中,我们说过__cself代表self,所以在该函数中,使用__cself的str值,即__main_block_impl_0结构体中的str值。
注意:Block不能使用C语言数组类型的自动变量。因为使用C语言数组类型的自动变量在Block中赋值相当于下面这种写法:

char a[10] = {2};
char t[10] = a;

C语言禁止这种写法,所以不能通过编译。

__block说明符

在Block中访问修改值

众所周知,Block截获的变量,不能在Block内修改,否则不能通过编译。
Block内不能修改截获的自动变量的值
当我们想要修改Block内截获的自动变量的值的时候,可以有三种方法:

  • 静态变量
  • 静态全局变量
  • 全局变量
  • 添加__block说明符

我们编写以下代码来看看:

#import <Foundation/Foundation.h>
int globalVal = 1;
static int globalStatic = 1;
int main(int argc, const char * argv[]) {
    static int staticVal = 1;
    __block int blockVal = 1;
    void (^blk) (void) = ^{
        globalVal = 2;
        globalStatic = 3;
        staticVal = 4;
        blockVal = 5;
        printf("globalVal is :%d\nglobalStatic is :%d\nstaticVal is :%d\nblockVal is %d\n",globalVal,globalStatic,staticVal,blockVal);
    };
    blk();
    return 0;
}

运行结果:
修改自动变量的值
代码转换后如下:

int globalVal = 1;
static int globalStatic = 1;
struct __Block_byref_blockVal_0 {
  void *__isa;
__Block_byref_blockVal_0 *__forwarding;
 int __flags;
 int __size;
 int blockVal;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *staticVal;
  __Block_byref_blockVal_0 *blockVal; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staticVal, __Block_byref_blockVal_0 *_blockVal, int flags=0) : staticVal(_staticVal), blockVal(_blockVal->__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_blockVal_0 *blockVal = __cself->blockVal; // bound by ref
  int *staticVal = __cself->staticVal; // bound by copy

        globalVal = 2;
        globalStatic = 3;
        (*staticVal) = 4;
        (blockVal->__forwarding->blockVal) = 5;
        printf("globalVal is :%d\nglobalStatic is :%d\nstaticVal is :%d\nblockVal is %d\n",globalVal,globalStatic,(*staticVal),(blockVal->__forwarding->blockVal));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->blockVal, (void*)src->blockVal, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->blockVal, 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[]) {
    static int staticVal = 1;
    __attribute__((__blocks__(byref))) __Block_byref_blockVal_0 blockVal = {(void*)0,(__Block_byref_blockVal_0 *)&blockVal, 0, sizeof(__Block_byref_blockVal_0), 1};
    void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticVal, (__Block_byref_blockVal_0 *)&blockVal, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

仔细查看这段代码可以知道,静态全局变量globalStatic和全局变量globalVal,转换前后完全相同,不需要多关注。对于静态变量staticVal在Block内部则是通过指针对其实现访问。通过将静态变量staticVal的指针传递给__main_block_impl_0结构体的构造函数并保存下来实现对其访问。

静态变量可以实现在Block内部访问变量,但是为什么还需要有__block说明符呢?因为,Block可以保存超出其变量作用域的的变量。如果在保存的变量本身超出了变量作用域,将不能通过指针对其进行访问。

__block说明符

观察转换后的源码可以知道,添加了__block说明符后,代码量增加了很多,提取出来大致增加了如下代码:

struct __Block_byref_blockVal_0 {
  void *__isa;
__Block_byref_blockVal_0 *__forwarding;
 int __flags;
 int __size;
 int blockVal;
};

__attribute__((__blocks__(byref))) __Block_byref_blockVal_0 blockVal = {(void*)0,(__Block_byref_blockVal_0 *)&blockVal, 0, sizeof(__Block_byref_blockVal_0), 1};

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->blockVal, (void*)src->blockVal, 8/*BLOCK_FIELD_IS_BYREF*/);}

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

在原本main函数中__block说明符修饰的变量被转换成了__Block_byref_blockVal_0结构体,变量本身成为了结构体的成员变量。对该变量赋值就是调用__Block_byref_blockVal_0结构体的构造函数向对应的成员变量赋值。

如以下源码所示,我们将blockVal初始化为1,转换后代码调用__Block_byref_blockVal_0结构体的构造函数向对应的成员变量赋值。

__block int blockVal = 1;
转换为:
__attribute__((__blocks__(byref))) __Block_byref_blockVal_0 blockVal = {(void*)0,(__Block_byref_blockVal_0 *)&blockVal, 0, sizeof(__Block_byref_blockVal_0), 1};

当我们向__block变量赋值代码:

blockVal = 5;
转换后:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_blockVal_0 *blockVal = __cself->blockVal; // bound by ref
        //...
        (blockVal->__forwarding->blockVal) = 5;
        //...
    }

__Block_byref_blockVal_0结构体实例的成员变量__forwarding持有指向该实例自身的指针。通过成员变量__forwarding访问成员变量blockVal实现对其的赋值。
__main_block_copy_0__main_block_dispose_0函数相当于retain和release实例方法的函数。分别让Block持有__block变量和释放__block变量。
这两个函数的调用时机:

函数调用时机
copy函数栈上的Block复制到堆时
dispose函数堆上的Block被废弃时

什么时候栈上的Block会复制到堆呢?

  • 调用Block的copy实例方法
  • Block作为函数返回值返回时
  • 将Block赋值给附有__strong修饰符id类型的或者Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或者GCD的API中传递Block时

正是由于这两个函数,使得__block变量可以超出其变量作用域被访问。因为它被Block持有。:satisfied:

结语

* 关于Block的内容结束了,三章均为在阅读参考资料时候的读书笔记,难免有错误。

[1] Kazuki Sakamoto,Tomohiko Furumoto.Objective-C高级编程〔M〕.北京:人民邮电出版社,2013

Tags: iOS开发
Archives QR Code
QR Code for this page
Tipping QR Code
Leave a Comment

已有 3 条评论
  1. Click here...

  2. As you will inevitably learn on your path to losing weight, effective weight loss is not only about watching what you eat, but much more about changing your lifestyle. This means changing your habits and how you approach your day-to-day life. Read this information to help you throughout the process.

  3. Some people, especially those running on busy daily schedules tend to use the pills to help maintain weight since they can not afford to follow all the diet programs. This is not advised. It is recommended that one seek advice from a professional in this field before using the pills. This can save one from many dangers associated with the misuse.

    The diet pills should always be taken whole. Some people tend to divide the pills to serve a longer period of time. This is not advised and can lead to ineffectiveness. If it is required that one takes a complete tablet, it means that a certain amount of the ingredients are required to achieve the desired goal. It is also recommended that one does not crush the pill and dissolve it in beverages. Chemicals found in beverages have the potential of neutralizing the desired nutrients in the pill thereby leading to ineffectiveness. The best way to take the tablets is swallowing them whole with a glass of water.

    The diet pills speed up the metabolic processes. This is the key factor that leads to the burning of all the fats in the body. This means that one passes out lots of urine, which subsequently leads to dehydration. It is imperative that the user take lots of water round the clock. This will help curb dehydration, which can lead to health problems. In addition to that, water offers the required medium for the function of the nutrients and elimination of the fats.

    When buying the review of diet pills, it is imperative that one gets the most recommended dose. People tend to compromise the quality and effectiveness of the tablets due to the variation in cost. The low priced pills depict poor quality, which means their effectiveness is not reliable. Some have also been found to cause health problems. The dose should also be taken as recommended. Over dose will not speed up the process but rather lead to complication. This will increase risk of side effects. If the taking of the pill is forgotten, do not take more to compensate for the lost time.

    The diet plan enclosed with the diet pills has also to be followed. According to the requirements, the termination of the diet must be done even with no results. This means your body is irresponsive.