史上最詳細的Block源碼剖析

前言

??之前寫過一篇block的文章,參考的源碼是libclosure-38的,跟libclosure-67有所區別,且由于之前理解不足文章有些細小錯誤,決定重新寫一篇。
??關于Block的文章各個博客各個論壇都已經數不勝數,每一篇都有自己獨特的特點和見解。但很多文章深度不夠,只是簡單的把一些源碼放上去,并未分析每一步操作的作用,比較不友好,只能看得一知半解。筆者剛看的時候也是一頭霧水,花費了很長時間查閱了各種資料才漸漸理解。鑒于此,決定寫一篇事無巨細的文章來剖析Block的每一個步驟,來幫助大家理解Block的奧秘。
??Block的使用這里就不再做過多解釋,下面會通過分析源碼來講解Block的實現、變量的捕獲以及__block的作用等。

Block

Block類型

??看一段代碼,以下在ARC下執行。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
       int num = 1;
       static int staticNum = 2;
       char str[] = "hello";
       void(^aBlock)(void) = ^(void) {
           printf("%d\n", num);
       };
       void(^bBlock)(void) = ^(void) {
          printf("%d\n", staticNum);
       };
       void(^cBlock)(char *) = ^(char *word) {
           printf("%s\n", word);
       };
       aBlock();
       bBlock();
       cBlock(str);
       NSLog(@"%@----%@----%@----%@", aBlock, bBlock, cBlock, ^{NSLog(@"%d", num);});
    }
    return 0;
}

打印結果如下:

<__NSMallocBlock__: 0x100607950>----<__NSGlobalBlock__: 0x100002118>--
--<__NSGlobalBlock__: 0x100002158>----<__NSStackBlock__: 0x7fff5fbff5c0>

??其中因為bBlock和cBlock只使用了靜態變量和入參,不需要捕獲外部變量,所以為全局Block __NSGlobalBlock__,存在于全局區,內存在程序結束后由系統回收。最后一個使用了外部變量num,為棧Block__NSStackBlock__,內存由編譯器控制,過了作用域就會被回收。而aBlock雖然也只使用了外部變量,但由于在ARC下會自動調用一次copy方法,將Block從棧區copy到堆區,所以aBlock為堆Block__NSMallocBlock__,內存由ARC控制,沒有強指針指向時釋放。而在MRC中,賦值不會執行copy操作,所以aBlock依然會存在于棧中,所以在MRC中一般都需要執行copy,否則很容易造成crash。
??在ARC中,當Block作為屬性被strong、copy修飾或被強指針應用或作為返回值時,都會默認執行copy方法。而MRC中,只有被copy修飾時,Block才會執行copy。所以MRC中Block都需要用copy來修飾,而在ARC中用copy修飾只是沿用了MRC的習慣,此時用copy和strong效果是相同的。下面會講到copy的具體實現。

Block數據結構定義

??Block的定義在Block_private.h中,點擊查看源碼

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime  
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime 
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime 
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler 
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime 
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler 
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler 
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler 
};

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

??用一張被用爛的圖,點擊查看原文。

結構圖

??事實上該圖適用于libclosure-38,在最新的源碼中結構已經有所改變,但整體并未改變,只是在基礎上新增了signaturelayout,依然可以用其來理解。

  • isa指針
    指向父類的結構體,就是_NSConcreteStackBlock,_NSConcreteMallocBlock_NSConcreteGlobalBlock這幾個,說明OC本身也是一個對象。
  • flags
    就是上面那幾個枚舉,用來保留block的一些信息,用到的如下:
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime  
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime 用來標識棧Block
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime  用來標識堆Block
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler compiler 含有copy_dispose助手
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler 是否為全局Block
};

BLOCK_REFCOUNT_MASK、BLOCK_IS_GLOBAL和BLOCK_NEEDS_FREE用來標識Block類型,BLOCK_HAS_COPY_DISPOSE判斷Block是否有copy_dispose助手,即description2中的copy和dispose函數,用來管理捕獲對象的內存,下面會細講。

  • reserved
    保留信息
  • invoke
    函數指針,指向block具體的執行函數
  • description
    block附加描述信息,主要保存了內存size以及copy和dispose函數的指針及簽名和layout等信息,通過源碼可發現,layout中只包含了Block_descriptor_1,并未包含Block_descriptor_2和Block_descriptor_3,這是因為在捕獲不同類型變量或者沒用到外部變量時,編譯器會改變結構體的結構,按需添加Block_descriptor_2和Block_descriptor_3,所以才需要BLOCK_HAS_COPY_DISPOSE和BLOCK_HAS_SIGNATURE等枚舉來判斷
  • variables capture的外部變量,如果Block中使用了外部變量,結構體中就會有相應的信息,下面會解釋。Block將使用的變量或者變量指針copy過來,內部才可以訪問。

Block_byref的結構定義

??上面介紹了Block也就是Block_layout的源碼,Block_private.h中還有定義了一個結構體Block_byref,變量在被__block修飾時由編譯器來生成,結構如下:

enum {
    // Byref refcount must use the same bits as Block_layout's refcount.
    // BLOCK_DEALLOCATING =      (0x0001),  // runtime
    // BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime

    BLOCK_BYREF_LAYOUT_MASK =       (0xf << 28), // compiler
    BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler
    BLOCK_BYREF_LAYOUT_NON_OBJECT = (  2 << 28), // compiler
    BLOCK_BYREF_LAYOUT_STRONG =     (  3 << 28), // compiler
    BLOCK_BYREF_LAYOUT_WEAK =       (  4 << 28), // compiler
    BLOCK_BYREF_LAYOUT_UNRETAINED = (  5 << 28), // compiler

    BLOCK_BYREF_IS_GC =             (  1 << 27), // runtime

    BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler
    BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime
};

struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
    void (*byref_destroy)(struct Block_byref *);
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

??當其結構與Block_layout結構類似

  • isa指針
    指向父類,一般直接指向0,后面會說。
  • forwarding指針
    在棧中指向自己,Block執行copy后指向堆中的byref。
  • flags
    runtime中用到的如下:
    BLOCK_BYREF_LAYOUT_EXTENDED =   (  1 << 28), // compiler
    BLOCK_BYREF_HAS_COPY_DISPOSE =  (  1 << 25), // compiler
    BLOCK_BYREF_NEEDS_FREE =        (  1 << 24), // runtime

BLOCK_BYREF_LAYOUT_EXTENDED表示含有layout,BLOCK_BYREF_HAS_COPY_DISPOSE表示byref中含有copy_dispose函數,在__block捕獲的變量為對象時就會生成copy_dispose函數用來管理對象內存,BLOCK_BYREF_NEEDS_FREE判斷是否要釋放。

  • size
    所占內存大小
  • Block_byref_2、Block_byref_3
    Block_byref_2和Block_byref_3用來保存附加信息

_NSConcreteGlobalBlock的實現

??使用Clang可以將OC轉成C++,就可以看到Block具體做了什么。clang -rewrite-objc filename,比如我需要轉換main.m中的代碼就可以寫clang -rewrite-objc main.m,注意路徑正確。轉換后的.cpp文件有10萬行,只需要看最后幾行。
??上面說到,在block內部不使用外部變量或者為靜態或者全局變量時,該block就是_NSConcreteGlobalBlock,看下編譯后的結果。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        ^{
            printf("hello block");
        }();
        
    }
    return 0;
}

Clang后

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
//下面為轉換后的代碼
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    printf("hello block");
        }

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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();

    }
    return 0;
}

??從上可以看到,__block_impl__main_block_impl_0以及__main_block_desc_0這三個結構體一起組成了整個Block,生成的結構體各個參數跟上面介紹的一致。編譯器將Block內的函數定義成一個獨立的函數__main_block_func_0,在最后調用。
??不過有兩點問題:

  • 1、該Block并未使用外部變量,應該是一個_NSConcreteGlobalBlock,而用clang顯示的卻是_NSConcreteStackBlock,唐巧的說法是:clang 改寫的具體實現方式和 LLVM 不太一樣。這點不是很清楚,如果有了解的可以釋疑一下。
  • 2、把編譯后的代碼copy到mm中后,((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();此行會crash,因為((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))指向的是impl的isa,也就是_NSConcreteStackBlock的指針。需要改寫成struct __main_block_impl_0 impl_0 = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA); ((void (*)())(impl_0.impl.FuncPtr))();才能執行成功。或許clang改寫的實現跟LLVM確實不一樣吧。

_NSConcreteStackBlock的實現

??如果block內部使用了普通的外部變量,就是_NSConcreteStackBlock。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...

        int a = 1;
        NSObject *obj = [[NSObject alloc] init];
        ^{
            obj, a;
        };
    }
    return 0;
}

Clang后

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

            obj, a;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 1;
        NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, a, 570425344));
    }
    return 0;
}

??該例在Block中同時使用了普通變量和實例變量,Block在捕獲外部變量的操作基本一致,都是在生成結構體的時候將所有Block里用到的外部變量作為屬性存起來。這就是為什么在self.block里調用self會造成循環引用,因為Block捕獲了self并把self當做一個值保存了起來。
??而且如果此時在Block里面改變a的值是會報錯,因為普通變量的值傳遞為復制,所有Block里的a只是copy過去的a的值,在Block里改變a的值也不會影響外面,編譯器避免這個錯誤就報錯。同樣的,捕獲對象時也是對指針的copy,生成一個指針指向obj對象,所以如果在Block中直接讓obj指針指向另外一個對象也會報錯。這點編譯器已經加了注釋// bound by copy。
??同時,該例與上例有所區別,多了__main_block_copy_0__main_block_dispose_0這兩個函數,并在描述__main_block_desc_0結構體中保存了這兩個函數指針。這就是上面所說的copy_dispose助手,C語言結構體中,編譯器很難處理對象的初始化和銷毀操作,所以使用runtime來管理相關內存。BLOCK_FIELD_IS_OBJECT是在捕獲對象時添加的特別標識,此時是3,下面會細講。
??此Block是為棧Block_NSConcreteStackBlock,不過在ARC中,因為賦值給aBlock,會執行一次copy,將其中棧中copy到堆中,所以在MRC中aBlock為_NSConcreteStackBlock,在ARC中就是_NSConcreteMallocBlock。

_NSConcreteMallocBlock && Block_copy()

??上面說到,ARC中生成的_NSConcreteStackBlock在賦值給強指針時會默認執行一次copy變成_NSConcreteMallocBlock,下面介紹下copy的實現。
??在Block.h中定義了Block_copy的宏

#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))

??其實現在runtime.h

void *_Block_copy(const void *arg) {
    //1、
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    //2、
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    //3、
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    //4、
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        //5、
        // Its a stack block.  Make a copy.
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return NULL;
        //6、
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        //7、
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        //8、
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
        //9、
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}

static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;

    (*desc->copy)(result, aBlock); // do fixup
}

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

??以上就是_NSConcreteStackBlock轉換成_NSConcreteMallocBlock的過程,下面按步解釋每一步的作用。

  • 1、先聲明一個Block_layout結構體類型的指針,如果傳入的Block為NULL就直接返回。
  • 2、如果Block有值就強轉成Block_layout的指針類型。
  • 3、如果Block的flags表明該Block為堆Block時,就對其引用計數遞增,然后返回原Block。latching_incr_int這個函數進行了一次死循環,如果flags含有BLOCK_REFCOUNT_MASK證明其引用計數達到最大,直接返回,需要三萬多個指針指向,正常情況下不會出現。隨后做一次原子性判斷其值當前是否被其他線程改動,如果被改動就進入下一次循環直到改動結束后賦值。OSAtomicCompareAndSwapInt的作用就是在where取值與old_value相同時,將old_value+2賦給where。 注:Block的引用計數以flags的后16位代表,以 2為單位,每次遞增2,1被BLOCK_DEALLOCATING正在釋放占用。
  • 4、如果Block為全局Block就不做其他處理直接返回。
  • 5、該else中就是棧Block了,按原Block的內存大小分配一塊相同大小的內存,如果失敗就返回NULL。
  • 6、memmove()用于復制位元,將aBlock的所有信息copy到result的位置上。
  • 7、將新Block的引用計數置零。BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING就是0xffff,~(0xffff)就是0x0000result->flags0x0000與等就將result->flags的后16位置零。然后將新Block標識為堆Block并將其引用計數置為2。
  • 8、如果有copy_dispose助手,就執行Block的保存的copy函數,就是上面的__main_block_copy_0。在_Block_descriptor_2函數中,用BLOCK_HAS_COPY_DISPOSE來判斷是否有Block_descriptor_2,且取Block的Block_descriptor_2時,因為有無是編譯器確定的,在Block結構體中并無保留,所以需要使用指針相加的方式來確定其指針位置。有就執行,沒有就return掉。
  • 9、將堆Block的isa指針置為_NSConcreteMallocBlock,返回新Block,end。
    ??因為在ARC中的賦值的自動copy,所以:
        NSObject *obj = [[NSObject alloc] init];
        void(^aBlock)() = ^{
            obj;
        };
        NSLog(@"%ld", CFGetRetainCount((__bridge void *)obj));

??其打印結果為3,因為Block在生成棧Block捕獲外部變量的時候會生成一份指針指向obj,在將Block賦值給aBlock時從棧中copy到堆區中,又持有了一份。

Block_release()

??有Block_copy()就有對應的Block_release(),其在Block.h中的定義如下:

#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))

??其實現在runtime.h

void _Block_release(const void *arg) {
    //1、
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    //2、
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    //3、
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
    //4、
    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        //5、
        _Block_call_dispose_helper(aBlock);
        //6、
        _Block_destructInstance(aBlock);
        //7、
        free(aBlock);
    }
}

static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return false; // latched high
        }
        if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
            return false;   // underflow, latch low
        }
        int32_t new_value = old_value - 2;
        bool result = false;
        if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
            new_value = old_value - 1;
            result = true;
        }
        if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
            return result;
        }
    }
}

static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
    struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
    if (!desc) return;

    (*desc->dispose)(aBlock);
}

static void _Block_destructInstance_default(const void *aBlock __unused) {}
static void (*_Block_destructInstance) (const void *aBlock) = _Block_destructInstance_default;

  • 1、將入參arg強轉成(struct Block_layout *)類型,如果入參為NULL則直接返回。
  • 2、如果入參為全局Block則返回不做處理。
  • 3、如果入參不為堆Block則返回不做處理。
  • 4、判斷aBlock的引用計數是否需要釋放內存。與copy同樣的,latching_decr_int_should_deallocate也做了一次循環和原子性判斷保證原子性。如果該block的引用計數過高(0xfffe)或者過低(0)返回false不做處理。如果其引用計數為2,則將其引用計數-1即BLOCK_DEALLOCATING標明正在釋放,返回true,如果大于2則將其引用計數-2并返回false。
  • 5、如果步驟4標明了該block需要被釋放,就進入步驟5。如果aBlock含有copy_dispose助手就執行aBlock中的dispose函數,與copy中的對應不再多做解釋。
  • 6、_Block_destructInstance默認沒做其他操作,可見源碼。
  • 7、最后一步釋放掉aBlock,end。

__block的作用及實現

??前面說到,Block捕獲的變量不允許在內部改變指針指向,因為內部的對象是重新copy的一份指針或者普通變量值。如果需要改變普通變量的值或指針的指向,這個時候就需要用到__block。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        __block int a = 1;
        __block NSObject *obj = [[NSObject alloc] init];
        ^{
            a++;
            obj;
        }();
    }
    return 0;
}

Clang后

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};
struct __Block_byref_obj_1 {
  void *__isa;
__Block_byref_obj_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *obj;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __Block_byref_obj_1 *obj; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_obj_1 *_obj, int flags=0) : a(_a->__forwarding), obj(_obj->__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_a_0 *a = __cself->a; // bound by ref
  __Block_byref_obj_1 *obj = __cself->obj; // bound by ref

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->obj, 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
        __attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_obj_1 *)&obj, 570425344))();
    }
    return 0;
}

??可以看到加了__block的a和obj已經被編譯成了兩個結構體__Block_byref_a_0__Block_byref_obj_1,這兩個就是上面所說的Block_byref??梢钥吹狡鸾Y構大同小異,都有__isa指針指向0,__forwarding指針指向自己,__flags用來標記,__size用來保存內存大小,a和obj指向最初的變量。可以發現,對象的結構體多了兩個方法用來處理obj的內存。注釋也變成了// bound by ref,這時候就可以修改變量a和對指針obj重新賦值。對象結構體比普通變量多了兩個函數,用來處理NSOject的內存。
??上面說Block捕獲的對象會持有該對象,使其引用計數增加,然后看下__block修飾的對象會怎樣。

    __block NSObject *obj = [[NSObject alloc] init];
    NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
    ^{
        NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
    }();
    NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);

打印結果如下:

1---0x60c000007d50---0x7fff5e913c28
1---0x60c000007d50---0x7fff5e913c28
1---0x60c000007d50---0x7fff5e913c28

??可以看到,在棧Block中,自始只有強指針指向obj,就是__block生成的結構體。
??然后看下在copy到堆中的效果:

__block NSObject *obj = [[NSObject alloc] init];
    NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
    void(^aBlock)() = ^{
        NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
    };
    aBlock();
    NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);

結果如下:

1---0x604000012ca0---0x7fff50631c28
1---0x604000012ca0---0x600000058c28
1---0x604000012ca0---0x600000058c28

??可以看到,obj的內存地址一直在棧中,而執行了BlockCopy后,obj指針的地址從棧變到了堆中,而obj的引用計數一直是1。在copy操作之后,結構體obj也被復制到了堆中,并斷開棧中的obj結構體對obj對象的指向。那如果這個時候取棧中的obj不就有問題了?__forwarding就派上用場了,上面編譯的結果發現,結構體對象在使用obj的時候會使用obj->__forwarding->obj,如果所有__forwarding都指向自己,這一步還有什么意義?棧Block在執行copy操作后,棧obj結構體的__forwarding就會指向copy到堆中的obj結構體。此時再取值,操作的就是同一份指針了。證明如下:

        __block NSObject *obj = [[NSObject alloc] init];
        NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
        void(^aBlock)() = ^{
            NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
        };
        aBlock();
        void(^bBlock)() = [aBlock copy];
        bBlock();
        aBlock();
        NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);

注:該段在MRC下執行,否則棧Block賦值給aBlock就會默認執行copy操作變成堆block。
打印結果:

1---0x1006229d0---0x7fff5fbff6b8
1---0x1006229d0---0x7fff5fbff6b8
1---0x1006229d0---0x100508c98
1---0x1006229d0---0x100508c98
1---0x1006229d0---0x100508c98

??由上可知,在copy之前,aBlock的打印結果都是初始化生成的指針,而copy之后打印就和bBlock的打印結果相同了。總結一下就是,在棧中的obj結構體__forwarding指向的就是棧中的自己,執行copy之后,會在堆中生成一份obj結構體并斷開棧中對obj的引用,此時堆中的obj結構體__forwarding就指向自己,而棧中的__forwarding就指向堆中的obj結構體。下面也會通過分析源碼來具體解釋。

_Block_object_assign

??雖然上面已經講了很多,但細心的同學可以發現,在Clang后的代碼中還有兩個沒有講到,就是編譯器生成的copy_dispose代碼中調用的兩個用來管理對象內存的函數_Block_object_assign_Block_object_dispose
??然后先講_Block_object_assign。大家可以在Block.h中看到:

BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

??其實現在runtime.c中,其中用到很多枚舉,如果不懂其含義,很難理解其上面的代碼,所以在此之前先解釋下各種枚舉所代表的意思。
??其枚舉定義在Block_private.h中:

// Values for _Block_object_assign() and _Block_object_dispose() parameters
enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

enum {
    BLOCK_ALL_COPY_DISPOSE_FLAGS = 
        BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_BYREF |
        BLOCK_FIELD_IS_WEAK | BLOCK_BYREF_CALLER
};
  • BLOCK_FIELD_IS_OBJECTOC對象類型;
  • BLOCK_FIELD_IS_BLOCK為另一個block;
  • BLOCK_FIELD_IS_BYREF為一個被__block修飾后生成的結構體;
  • BLOCK_FIELD_IS_WEAK被__weak修飾過的弱引用,只在Block_byref管理內部對象內存時使用,也就是__block __weak id ;
  • BLOCK_BYREF_CALLER在處理Block_byref內部對象內存的時候會加的一個額外標記,配合上面的枚舉一起使用;
  • BLOCK_ALL_COPY_DISPOSE_FLAGS上述情況的整合,即以上都會包含copy_dispose助手。
    ??現在再看_Block_object_assign:
void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/
        //1.
        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/
        //2.
        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/
        //3.
        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/
        //4.
        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/
        //5.
        *dest = object;
        break;

      default:
        break;
    }
}
static void _Block_retain_object_default(const void *ptr __unused) { }
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;

static struct Block_byref *_Block_byref_copy(const void *arg) {
    //3.1
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        //3.2
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        //3.3
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        //3.4
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        //3.5
        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_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
            //3.6
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            //3.7
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                //3.8
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }
            //3.9
            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            //3.10
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        //3.11
        latching_incr_int(&src->forwarding->flags);
    }
    //3.12
    return src->forwarding;
}

??destArg為執行Block_copy()后的block中的對象、block、或者BYREF指針的指針,obj為copy之前的變量指針。

  • 1、當block捕獲的變量為OC對象時執行此步,ARC中引用計數由強指針來確定,所以_Block_retain_object默認是不做任何操作,只進行簡單的指針賦值。
  • 2、當block捕獲的變量為另外一個block時執行此步,copy一個新的block并賦值。
  • 3、當block捕獲的變量為__block修飾的變量時會執行此步,執行byref_copy操作。
    3.1、強轉入參為(struct Block_byref *)類型。
    3.2、當入參為棧byref時執行此步。分配一份與當前byref相同的內存,并將isa指針置為NULL。
    3.3、將新byref的引用計數置為4并標記為堆,一份為調用方、一份為棧持有,所以引用計數為4。
    3.4、然后將當前byref和malloc的byref的forwading都指向堆byref,然后操作堆棧都是同一份東西。
    3.5、最后將size賦值
    3.6、如果byref含有需要內存管理的變量即有copy_dispose助手,執行此步。分別取出src的Block_byref_2和copy的Block_byref_2
    3.7、將src的管理內存函數指針賦值給copy。
    3.8、如果src含有Block_byref_3,則將src的Block_byref_3賦值給copy。
    3.9、執行byref的byref_keep函數(即_Block_object_assign,不過會加上BLOCK_BYREF_CALLER標記),管理捕獲的對象內存。
    3.10、如果捕獲的是普通變量,就沒有Block_byref_2,copy+1和src+1指向的就是Block_byref_3,執行字節拷貝。
    3.11、如果該byref是存在于堆,則只需要增加其引用計數。
    3.12、返回forwarding。
  • 4、如果管理的是__block修飾的對象或者block的內存會執行此步,直接進行指針賦值。
  • 5、同時被__weak和__block修飾的對象或者block執行此步,也是直接進行指針賦值。

_Block_object_dispose

??與_Block_object_assign對應的。

void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        //1.
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        //2.
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        //3.
        _Block_release_object(object);
        break;
      //4.
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}

static void _Block_byref_release(const void *arg) {
    //1.1
    struct Block_byref *byref = (struct Block_byref *)arg;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    byref = byref->forwarding;
    
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        //1.2
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);

        if (latching_decr_int_should_deallocate(&byref->flags)) {
            //1.3
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                //1.4
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            //1.5
            free(byref);
        }
    }
}

static void _Block_release_object_default(const void *ptr __unused) { }
static void (*_Block_release_object)(const void *ptr) = _Block_release_object_default;
  • 1、如果需要管理的變量為byref,則執行該步。
    1.1、將入參強轉成(struct Block_byref *),并將入參替換成forwarding。
    1.2、如果該byref在堆上執行此步,如果該byref還被標記為棧則執行斷言。
    1.3、此函數上面有講就不多提,判斷是否需要釋放內存。
    1.4、如果有copy_dispose助手就執行byref_destroy管理捕獲的變量內存。
    1.5、釋放byref。
  • 2、如果block則調用_Block_release釋放block,上面有講。
  • 3、如果是OC對象就進行release,默認沒有做操作,由ARC管理。
  • 4、如果是其他就不做處理,__block修飾的變量只有一個強指針引用。

測試

??如果這么長都看完了,想必你已經對block有了深刻的認識,來波測試吧(本文只有此測試是抄的)。下面的選項均為:A、ARC可以正常執行。B、ARC和MRC均可以正常執行。C、均不可正常執行。

Example A
void exampleA() {
  char a = 'A';
  ^{
    printf("%c\n", a);
  }();
}
Example B
void exampleB_addBlockToArray(NSMutableArray *array) {
  char b = 'B';
  [array addObject:^{
    printf("%c\n", b);
  }];
}

void exampleB() {
  NSMutableArray*array = [NSMutableArray array];
  exampleB_addBlockToArray(array);
  void(^block)() = [array objectAtIndex:0];
  block();
}
Example C
void exampleC_addBlockToArray(NSMutableArray *array) {
  [array addObject:^{
    printf("C\n");
  }];
}

void exampleC() {
  NSMutableArray *array = [NSMutableArray array];
  exampleC_addBlockToArray(array);
  void(^block)() = [array objectAtIndex:0];
  block();
}
Example D
typedef void(^dBlock)();

dBlock exampleD_getBlock() {
  char d = 'D';
  return^{
    printf("%c\n", d);
  };
}

void exampleD() {
  exampleD_getBlock()();
}
Example E
typedef void(^eBlock)();

eBlock exampleE_getBlock() {
  char e = 'E';
  void(^block)() = ^{
    printf("%c\n", e);
  };
  return block;
}

void exampleE() {
  eBlock block = exampleE_getBlock();
  block();
}

??答案?不存在的!看在辛苦碼這么字的份上,先star一發我就發咯。

參考文獻

apple源碼地址
參考文章

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,428評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,024評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,285評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,548評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,328評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,878評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,971評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,098評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,616評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,554評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,725評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,243評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,971評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,361評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,613評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,339評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,695評論 2 370

推薦閱讀更多精彩內容