iOS block __block 关键字详解 OC block 源码详解

http://www.jianshu.com/p/51d04b7639f1

行赋值是没有意义的,所以编译器给出了错误。我们可以通过地址传递来消除以上错误:

<code class="cpp">- (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>)test
{
    <span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> a = <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>;
    <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 利用指针p存储a的地址</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> *p = &a;

    ^{
        <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 通过a的地址设置a的值</span>
        *p = <span class="hljs-number" style="color: rgb(0, 102, 102);">10</span>;
    };
}</code>

但是变量a的生命周期是和方法test的栈相关联的,当test运行结束,栈随之销毁,那么变量a就会被销毁,p也就成为了野指针。如果block是作为参数或者返回值,这些类型都是跨栈的,也就是说再次调用会造成野指针错误。

OC Block 源码

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    struct Block_byref **destp = (struct Block_byref **)dest;
    struct Block_byref *src = (struct Block_byref *)arg;
        
    //printf("_Block_byref_assign_copy called, byref destp %p, src %p, flags %x\n", destp, src, flags);
    //printf("src dump: %s\n", _Block_byref_dump(src));
    if (src->forwarding->flags & BLOCK_IS_GC) {
        ;   // don't need to do any more work
    }
    else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        //printf("making copy\n");
        // src points to stack
        bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
        // if its weak ask for an object (only matters under GC)
        struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
        copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
        copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        if (isWeak) {
            copy->isa = &_NSConcreteWeakBlockVariable;  // mark isa field so it gets weak scanning
        }
        if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            copy->byref_keep = src->byref_keep;
            copy->byref_destroy = src->byref_destroy;
            (*src->byref_keep)(copy, src);
        }
        else {
            // just bits.  Blast 'em using _Block_memmove in case they're __strong
            _Block_memmove(
                (void *)&copy->byref_keep,
                (void *)&src->byref_keep,
                src->size - sizeof(struct Block_byref_header));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    // assign byref data block pointer into new Block
    _Block_assign(src->forwarding, (void **)destp);
}


<code class="cpp"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> __Person__test_block_func_0(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// bound by ref</span>
        <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 注意,这里的_forwarding用来保证操作的始终是堆中的拷贝a,而不是栈中的a</span>
        (a->__forwarding->a) = <span class="hljs-number" style="color: rgb(0, 102, 102);">10</span>;
    }
<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> __Person__test_block_copy_0(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*dst, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*src) {_Block_object_assign((<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)&dst->a, (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)src->a, <span class="hljs-number" style="color: rgb(0, 102, 102);">8</span><span class="hljs-comment" style="color: rgb(136, 0, 0);">/*BLOCK_FIELD_IS_BYREF*/</span>);}

<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> __Person__test_block_dispose_0(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*src) {_Block_object_dispose((<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)src->a, <span class="hljs-number" style="color: rgb(0, 102, 102);">8</span><span class="hljs-comment" style="color: rgb(136, 0, 0);">/*BLOCK_FIELD_IS_BYREF*/</span>);}

<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_desc_0 {
  <span class="hljs-keyword" style="color: rgb(0, 0, 136);">size_t</span> reserved;
  <span class="hljs-keyword" style="color: rgb(0, 0, 136);">size_t</span> Block_size;
  <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> (*copy)(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*);
  <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> (*dispose)(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">sizeof</span>(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};

<span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> _I_Person_test(Person * self, SEL _cmd) {
    <span class="hljs-comment" style="color: rgb(136, 0, 0);">// __block将a包装成了一个对象</span>
   __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span>*)<span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>,(__Block_byref_a_0 *)&a, <span class="hljs-number" style="color: rgb(0, 102, 102);">0</span>, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">sizeof</span>(__Block_byref_a_0)};
;
    (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> (*)())&__Person__test_block_impl_0((<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, <span class="hljs-number" style="color: rgb(0, 102, 102);">570425344</span>);
}</code>

可以看到,对比上面的结果,明显多了__Block_byref_a_0结构体,这个结构体中含有isa指针,所以也是一个对象,它是用来包装局部变量a的。当block被copy到堆中时,__Person__test_block_impl_0的拷贝辅助函数__Person__test_block_copy_0会将__Block_byref_a_0拷贝至堆中,所以即使局部变量所在堆被销毁,block依然能对堆中的局部变量进行操作。其中__Block_byref_a_0成员指针__forwarding用来指向它在堆中的拷贝,其依据源码如下:

<code class="objectivec"><span class="hljs-keyword" style="color: rgb(0, 0, 136);">static</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> _Block_byref_assign_copy(<span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *dest, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">void</span> *arg, <span class="hljs-keyword" style="color: rgb(0, 0, 136);">const</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136);">int</span> flags) {
    <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref **destp = (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref **)dest;
    <span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref *src = (<span class="hljs-keyword" style="color: rgb(0, 0, 136);">struct</span> Block_byref *)arg;

    ...
    <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 堆中拷贝的forwarding指向它自己</span>
    <span class="hljs-keyword" style="color: rgb(0, 0, 136);">copy</span>->forwarding = <span class="hljs-keyword" style="color: rgb(0, 0, 136);">copy</span>; <span class="hljs-comment" style="color: rgb(136, 0, 0);">// patch heap copy to point to itself (skip write-barrier)</span>
    <span class="hljs-comment" style="color: rgb(136, 0, 0);">// 栈中的forwarding指向堆中的拷贝</span>
    src->forwarding = <span class="hljs-keyword" style="color: rgb(0, 0, 136);">copy</span>;  <span class="hljs-comment" style="color: rgb(136, 0, 0);">// patch stack to point to heap copy</span>
    ...
}</code>
<code class="objectivec"></code><p class="p1" style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-size: 16px;">当block从栈上被copy到堆上时,会调用__main_block_copy_0将__block类型的成员变量i从栈上复制到堆上;而当block被释放时,相应地会调用__main_block_dispose_0来释放__block类型的成员变量i。</span></p><p class="p2" style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-size: 16px;">一会在栈上,一会在堆上,那如果栈上和堆上同时对该变量进行操作,怎么办?</span></p><p class="p3" style="color: rgb(51, 51, 51); font-family: Arial; font-size: 14px; line-height: 26px;"><span style="font-size: 16px;"><span class="s5">这时候,__forwarding的作用就体现出来了:</span>当一个__block变量从栈上被复制到堆上时,栈上的那个__Block_byref_i_0结构体中的__forwarding指针也会指向堆上的结构<span class="s5">。</span></span></p>

这样做是为了保证操作的值始终是堆中的拷贝,而不是栈中的值。(处理在局部变量所在栈还没销毁,就调用block来改变局部变量值的情况,如果没有__forwarding指针,则修改无效)


Ian_He: @tripleCC 也就是说, 编译器把用__block修饰的基本类型变量全部放到堆上包装成对象了, "栈里面的那个变量"在block内外都不会用到, 或者说是不存在.

以前觉得加了__block修饰后, 就能访问到外面了, 其实是加了__block之后, 外面全部都访问堆了.

学到很多, 感谢楼主的无私奉献(´ε`)

口可口可口达: @Ian_He 感觉这么说不严谨,应该是在block被copy(到堆上)后,__block修饰的变量才会被copy到堆上(__forwarding指向了堆上的__block修饰的变量)。


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值