天天看點

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之後, 外面全部都通路堆了.

學到很多, 感謝樓主的無私奉獻(´ε`)

回複 2015.07.23 15:23

口可口可口達: @Ian_He 感覺這麼說不嚴謹,應該是在block被copy(到堆上)後,__block修飾的變量才會被copy到堆上(__forwarding指向了堆上的__block修飾的變量)。

繼續閱讀