Blocks:Writable Variables

Writable Variables

Next we show how variables could be writable in Blocks. We see the two solutions to make variables writable, and start with reviewing the automatic variables that are used in Blocks. Again, see the example of capturing automatic variables (Listing 5–3).

^{printf(fmt, val);}

This source code is converted as follows.

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

    printf(fmt, val);
}

You might have noticed something already. Only the values of the automatic variables used in the Block are captured. As I’ve explained, because “anonymous functions together with automatic (local) variables,” after the Block use these values, they are never written back to the instance on the Block struct or to its original automatic variables.

Next the source code tries to modify the automatic variable “val” inside the Block.

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

This causes the following compilation error.

error: variable is not assignable (missing __block type specifier)
    void (^blk)(void) = ^{val = 1;};
                      ~~~ ^

As we’ve learned previously, the implementation of Blocks never writes back the modified value of the variables. So, when the compiler detects an assignment to the captured automatic variable, it causes a compile error. But this limitation would be too inconvenient if you could never change the values. To solve this problem, you have two choices: using another kind of variable or using __block specifier. Let’s discuss the other kind of variable first.

Static or Global Variables

In C Language, there are writable variables:

  • Static variables
  • Static global variables
  • Global variables

An anonymous function part in a Block literal is simply converted to a C function. In the converted function, static global variables and global variables can be accessed. They work without any problem. But static variables are different. Because the converted function is declared outside the original function, where the Block literal is, static variables cannot be accessed because of the variable scope. Let’s check it with the following source code.

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

In the Block, the static variable “static_val”, static global variable “static_global_val”, and global variable “global_val” are modified. How will the source code be converted?

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

The converted source code is familiar looking. The static global variable “static_global_val” and global variable “global_val” are accessed just as in the original source code. How about the static variable “static_val”? The following part shows how the variable is used inside the Block.

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

    (*static_val) *= 3;
}

The static variable “static_val” is accessed via its pointer. A pointer to the variable is passed to the constructor of _main_block_impl_0 struct, and then the constructor assigns it. This is the easiest way to use a variable beyond the variable’s scope.

You might think that accessing automatic variables could be implemented in the same way as static variables. Why not? Because a Block must be able to exist even after the scope of a captured automatic variable is left. When the scope is left, the automatic variable is destroyed. Which means the Block can’t access the automatic variable anymore. So, automatic variables can’t be implemented the same as static variables. We learn those details in the next section.

__block specifier

As mentioned before, the other choice to avoid this problem is to use a __block specifier. To be precise, it is called a __block storage-class-specifier. In C language, the following are the storage-class-specifiers.

  • typedef
  • extern
  • static
  • auto
  • register

The __block specifier is something like static, auto, and register. They specify where the variable is stored. With “auto”, the value is stored on the stack as an automatic variable. With “static”, the value is stored in the data section as a static variable, and so on.

Let’s see how __block specifier works.

The __block specifier is used when you want to modify automatic variables from a Block, Let’s use __block specifier for the previous source code, which causes a compiling error. What will happen when __block specifier is added to the automatic variable declaration?

__block int val = 10;

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

This source code is compiled without any problem. This is converted as shown in Listing 5–5.

Listing 5–5. converted sourcecode that uses __block specifier

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

When __block specifier is added for the automatic variable, the source code becomes quite large. Next, we discuss why such a large source code has to be needed only for __block specifier.

In the original source code, __block specifier was used as

__block int val = 10;

How was the __block variable “val” converted?

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

Surprisingly, it is converted to an instance of a struct. The __block variable is an automatic variable of __Block_byref_val_0 struct, as Block is, which means that the __block variable is an instance of the __Block_byref_val_0 struct on the stack. The _block variable is initialized with 10. You can see the instance of the struct is initialized with 10, meaning that the struct stores the value of the original automatic variable in its member variable.

Let’s check the declaration of the struct.

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

As we’ve seen in the initializing part, the last member variable in the struct “val”, stores the original automatic variable, as its name suggests.

How does the assignment to the __block variables work?

^{val = 1;}

This source code is converted as follows.

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

As we’ve learned, to assign to a static variable from a Block, a pointer to the static variable is used. Assigning to a __block variable, however, is more complicated. An instance of __main_block_impl_0 struct for the Block has a pointer to the instance of __Block_byref_val_0 struct for the __block variable.

The instance of __Block_byref_val_0 struct has a pointer to the instance of __Block_byref_val_0 struct in the member variable “__forwarding”. Through the member variable “__forwarding”, the member variable “val”, corresponding to the original automatic variable, is accessed (Figure 5–2).

images

Figure 5–2. Accessing __block variable

We get back to “forwarding” in a bit. It is explained further in the next two sections, “Memory Segments for Blocks” and “Memory Segments for __block Variables.” For now, let’s look at the member variables of the __Block_byref_val_0 struct, which are not included in the __main_block_impl_0 struct. The reason why __Block_byref_val_0 struct is separated is to make the __block variable usable from more than one Block. Let’s see the following example.

__block int val = 10;

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

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

The variables “blk0” and “blk1” of the Block type access the __block variable “val”. It is converted as

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

Two Blocks use pointers to the same instance “val” of the __Block_byref_val_0 struct, which means that multiple Blocks can share the same __block variable. On the other hand, one Block can share multiple __block variables as well. Multiple __block variables can be used by the member variables in the struct for Block and the arguments for the constructor are just added.

Now, we’ve learned almost all about a __block variable. In the next section, we learn what we’ve skipped:

  • Why can’t a Block exist beyond a variable scope?
  • For what does a member variable “__forwarding” of a struct for __block variables exist?

Also, in the section, “Capturing Objects,” I explain the member variables “copy” and “dispose”, which are added in __main_block_desc_0 struct.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值