objective-c高级编程(ios与osx多线程和内存管理)(Blocks-3)

前言:

以下文字都是摘自ios与osx多线程和内存管理一书,方便以后自己过来查阅的.
本文梗概:Block的实质:Block究竟是什么?本节将通过Block的实现进一步帮大家加深理解。。

1. Blocks的实现:


1.3 __block说明符:

我们再来回顾前面截获自动变量的例子。
^{printf(fmt, val);}
该源码转换结果如下:

static void __main_block_func_0 (struct __main_block_impl_0 *__cself)
{
    const char *fmt = __cself->fmt;
    int val = __cself->val;

    printf(fmt, val);
}

发现,Block中所使用的被截获自动变量就如“带有自动变量值的匿名函数”所说,仅截获自动变量的值。Block中使用自动变量后,在Block的结构体实例中重写该自动变量也不会改变原先截获的自动变量。
因为在实现上不能改写被截获自动变量的值,所以当编译器在编译过程中检出给被截获自动变量赋值的操作时,便产生编译错误。
不过这样就无法在Block中保存值了,极为不便。
解决这个问题有2种方法。第一种:c语言中有一个变量,允许Block改写值。具体如下:

  • 静态变量
  • 静态全局变量
  • 全局变量
    虽然Block语法的匿名函数部分简单地变换了c语言函数,但从这个变换的函数中访问静态全局变量/全局变量并没有任何改变,可直接使用。
    但是静态变量的情况下,转换后的函数原本就设置在含有BLock语法的函数外,所以无法从变量作用域访问。
    看看下面的代码。

    int global_val = 1;
    static int static_global_val = 2;
    
    int main()
    {
        static int static_val = 3;
    
        void (^blk)(void) = ^{
            global_val *= 1;
            static_global_val *= 2;
            static_val *= 3;
        };
        return 0;
    }
    

该代码使用了Block改写静态变量static_val,静态全局变量static_global_val和全局变量global_val.源码转换如下:

int global_val = 1;
static int static_global_val = 2;

struct __main_block_impl_0 {
    struct __block_impl impl;
    strict __main_block_desc_0* Desc;
    int *static_val;

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,
     int *_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){
    int *static_val = __cself->static_val;

    global_val *= 1;
    static_global_val *= 2;
    (*static_val) *= 3;
}

static struct __main_block_desc_0{
    unsigned long reserved;
    unsigned long Block_size;
} __main_block_desc_0_DATA = {
    0.
    sizeof(struct __main_block_impl_0)
};

int main()
{
    static int static_val = 3;
    blk = &__main_block_impl_0(
        __main_block_func_0, &__main_block_desc_0_DATA, &static_val);

    return 0;
}

这个结果大家很熟悉,对静态全局变量static_global_val和全局变量global_val的访问与转换前完全相同。
以下摘出Block使用该变量的部分。

static void __main_block_func_0(struct __main_block_impl_0 *__cself){
    int *static_val = __cself->static_val;

    (*static_val) *= 3;

}

使用静态变量static_val的指针对其进行访问。将静态变量static_val的指针传递给__main_block_impl_0结构体的构造函数并保存。这是超出作用域使用变量的最简单方法。
静态变量的这种方法似乎也适用于自动变量的访问。但我们为什么没有这么做?
实际上,在由Block语法生成的值Block上,可以存有超过其变量作用域的被截获对象的自动变量。变量作用域结束的同时,原来的自动变量被废弃。因此Block中超过变量作用域而存在的变量痛静态变量一样,将不能通过指针访问原来的自动变量。这些在下节详细说明。
解决Block中不能保存值这一问题的第二种方法是使用__block说明符。更准确的表述方式为__block存储域类说明符。c语言中有以下存储域类说明符:

  • typedef
  • extern
  • static
  • auto
  • register

__block说明符类似于static,autoregister说明符,它们用于指定将变量值设置到哪个存储域中。例如,auto表示作为自动变量存储在栈中,static 表示作为静态变量存储在数据区中。
下面我们来实际使用__block说明符,用它来指定Block中想要变更值的自动变量。

__block int val = 10;
void (^blk)(void) = ^{val = 1;};

转换后:

    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;

    __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 *vla = __cself->val;

        (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(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
        }

        static void __main_block_dispose_0(struct __main_block_impl_0*src)
        {
            _Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
        }
        static struct __main_block_desc_0{
            unsigned long reserved;
            unsigned long 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()
        {
            __Block_byref_val_0 val = {
            0,
            &val,
            0,
            sizeof(__Block_byref_val_0),
            10
        };

        blk = &__main_block_impl_0(
            __main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);

            return 0;
        }

只是在自动变量上附加了__block说明符,代码量就急剧增加。__block int val = 10;
这个__block变量val是怎样转换过来的?

__Block_byref_val_0 val = {
        0,
        &val,
        0,
        sizeof(__Block_byref_val_0),
        10
    };

我们发现,它竟然变为了结构体实例。__block变量也同Block一样变成__Block_byref_val_0结构体类型的自动变量,即栈上生成的__Block_byref_val_0结构体实例。该变量初始化为10,,且这个值夜出现在结构体实例的初始化中,这意味着该结构体持有相当于原自动变量的成员变量。
该结构体声明如下:

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

如同初始化时的源代码,该结构体中最后的成员变量val是相当于原自动变量的成员变量,我们从它的名称也能看出来这一点。
^{val = 1;}
该源码转换如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    __Block_byref_val_0 *vla = __cself->val;

    (val->__forwarding->val) = 1;
}

刚刚在Block中向静态变量赋值时,使用了指向该静态变量的指针。而向__block变量赋值要比这个更复杂。Block的__main_block_impl_0结构体实例持有指向__block变量__Block_byref_val_0结构体实例的指针。
__Block_byref_val_0结构体实例的成员变量__forwarding持有指向改实例自身的指针。通过成员变量__forwarding访问成员变量val.(成员变量val是该实例自身持有的变量,它相当于原自动变量。)如图3-1所示:
3-1
究竟为什么会有成员变量__forwarding呢?这个问题,我们留到下节来详细说明。
另外,__block变量__Block_byref_val_0结构体并不在Block用__main_block_impl_0结构体中,这样做是为了多个Block中使用__block变量。我们看一下下面的代码:

__block int val = 10;

void (^blk0)(void) = ^{val = 0;};

void (^blk1)(void) = ^{val = 1};

Block类型变量blk0盒blk1访问__block变量val。我们吧这2部分代码转换的结果摘录出来。

__Block_byref_val_0 val = {0, &val, 0, sizeof(__Block_byref_val_0), 10};

blk0 = &__main_block_impl_0(
    __main_block_func_0, &__main_block_desc_0_DATA, &val,0x22000000);

blk1 = &__main_block_impl_1(
    __main_block_func_1, &__main_block_desc_1_DATA, &val, 0x22000000);

两个Block都使用了__Block_byref_val_0结构体实例val的指针。这样一来就可以从多个Block中使用同一个__block变量。当然,反过来从一个Block中使用多个__block变量也是可以的。只要增加Block的结构体成员变量与构造函数的参数,便可对应使用多个__block变量
到此大概能能够理解__block变量了。下节主要说明之前跳过的部分的内容:

  • Block超出变量作用域可存在的理由
  • __block变量的结构体成员变量__forwarding存在的理由
Zerlz wechat
扫码关注一个很懒的程序员!
Winter is coming, give me a penny!