Block的實(shí)現(xiàn)原理

摘要:

我們?cè)陂_發(fā)的過程中經(jīng)常使用到block,不僅如此,Apple的api里面也有很多使用到block,好比gcd里面也是大量使用了block。block在語法上來說比較簡(jiǎn)潔,不過還是需要注意不要引起了循環(huán)引用。

裸block:

我們先來看看最基本的block編譯之后長啥樣:

typedef void (^Block)();

int main(int argc, const char * argv[]) {
    
    @autoreleasepool
    {
        Block b = ^(){
            printf("Hello Gay");
        };
    }
    return 0;
}

我們使用 clang-rewrite-objc filename 指令編譯一下到底是什么鬼

struct __block_impl {
  void *isa;     //什么類型的block
  int Flags;     //block的一些附加信息,里面包含了何種類型的block
                 //以及引用計(jì)數(shù),下面講到copy的時(shí)候就能知道到底是干嘛的了
  int Reserved;  //保留位
  void *FuncPtr; //函數(shù)指針
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc; //block的描述
  __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 Gay");
}

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[]) {
    { 
        __AtAutoreleasePool __autoreleasepool; 
        Block b = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    }
    return 0;
}

我們可以看到聲明的block以結(jié)構(gòu)體的方式進(jìn)行了存儲(chǔ),從側(cè)面上來說,block是一個(gè)指向結(jié)構(gòu)體的指針。其中__block_impl結(jié)構(gòu)體記錄的是block的相關(guān)信息包括block的類型以及引用計(jì)數(shù)等。

block里面捕獲變量之后

我們假設(shè)在block里面捕獲了一個(gè)變量,看看內(nèi)部會(huì)變成什么樣?

typedef void (^Block)();

int main(int argc, const char * argv[]) {
    @autoreleasepool
    {
        int i = 0;
        Block b = ^(){
            printf("%d",i);
        };
    }
    return 0;
}

同樣clang編譯之后

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int i; //相對(duì)于未捕獲變量的block來說,多了一個(gè)變量來保存值
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) 
{
   int i = __cself->i; // bound by copy
   printf("%d",i);
 }

int main(int argc, const char * argv[]) 
{
    { 
        __AtAutoreleasePool __autoreleasepool; 
        int i = 0;
        Block b = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, i));
        //注意這里的i傳的是值,因此意味著在當(dāng)我們?cè)谕獠啃薷倪@個(gè)值的時(shí)候,你調(diào)用block打印出來的時(shí)候,這個(gè)值依舊是之前的值
    }
    return 0;
}

乍一看,好像跟剛才的大差不差,__main_block_impl_0里面產(chǎn)生了一個(gè)變量用來保存之前捕獲的值。

block捕獲可修改的變量之后

貼上low B代碼

typedef void (^Block)();

int main(int argc, const char * argv[]) {

    @autoreleasepool
    {
        __block int i = 0;
        Block b = ^(){
            printf("%d",i);
        };
    }
    return 0;
}

我們?cè)俅尉幾g一下??

struct __Block_byref_i_0 {
  void *__isa;  //什么類型的數(shù)據(jù)
__Block_byref_i_0 *__forwarding;  //這個(gè)到copy的時(shí)候就用得著了,主要是保證copy之后能夠找到在堆上的那個(gè)變量
 int __flags;//變量的引用計(jì)數(shù)
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref 這里跟之前不一樣了,這次是用指針來保存的
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref
      printf("%d",(i->__forwarding->i));
}

//輔助block copy的時(shí)候,對(duì)捕獲變量的存儲(chǔ)方案
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

//輔助block release的時(shí)候,對(duì)捕獲變量的釋放策略
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

//這里面添加copy和dispose兩個(gè)函數(shù)
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[]) {

    { __AtAutoreleasePool __autoreleasepool; 

       //這里將結(jié)構(gòu)體i的__forwarding指針指向了自身
        __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};

        Block b = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
//570425344干嘛的?后面識(shí)別block類型的時(shí)候就能用上了

    }
    return 0;
}

通過編譯我們可以看到,block的描述里面多了兩個(gè)函數(shù)(關(guān)于copy和release)

Block_copy的實(shí)現(xiàn)

之前的熱身內(nèi)容是為了讓大家思路更加清(meng)晰(bi)。
我們可以在編譯器上使用Block_copy()或者在Block.h里面查看到關(guān)于Block_copy的定義

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

runtime.c中_Block_copy函數(shù)以這種方式實(shí)現(xiàn)了

void *_Block_copy(const void *arg) {
    return _Block_copy_internal(arg, WANTS_ONE);
}

該函數(shù)內(nèi)部調(diào)用了_Block_copy_internal,同樣我們?cè)?a target="_blank">runtime.c中能夠查看到該函數(shù)的實(shí)現(xiàn)方式

先貼上幾個(gè)block類型的枚舉,在這里能夠看到Block_private.h

enum{
    BLOCK_REFCOUNT_MASK =     (0xffff),
    BLOCK_NEEDS_FREE =        (1 << 24), //堆block
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), */* Helpers have C++ code. */*
    BLOCK_IS_GC =             (1 << 27),
    BLOCK_IS_GLOBAL =         (1 << 28),   //全局block
    BLOCK_HAS_DESCRIPTOR =    (1 << 29)
};

_Block_copy_internal實(shí)現(xiàn)如下

static void *_Block_copy_internal(const void *arg, const int flags) 
{
    struct Block_layout *aBlock;
    
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
 
    if (!arg) return NULL;
    
    aBlock = (struct Block_layout *)arg;
   //   還記得上面的那個(gè)初始化bloc時(shí)候賦值給flags標(biāo)志位的570425344嗎
   //   570425344 & (1 << 24) = 0 不滿足跳過
    if (aBlock->flags & BLOCK_NEEDS_FREE) { 
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
   //這邊也是不滿足條件的
    else if (aBlock->flags & BLOCK_IS_GC) {
       //此處省略若干行代碼。。。
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) 
    {
        return aBlock;
    }

    //最后來到這
    //isGC在runtime.c文件里面能夠找到,該變量被初始化為false
    // Its a stack block.  Make a copy.
    // 對(duì)棧block的copy
    if (!isGC) {
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed


        //這里是重點(diǎn)給flags標(biāo)識(shí)為加上BLOCK_NEEDS_FREE標(biāo)識(shí)位
        //同時(shí)增加了一個(gè)引用計(jì)數(shù)
        result->flags |= BLOCK_NEEDS_FREE | 1;

       //修改isa指針
        result->isa = _NSConcreteMallocBlock;                  
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) 
       {
            //如果存在copy的輔助函數(shù),會(huì)調(diào)用該輔助函數(shù),
            //當(dāng)捕獲了引用的時(shí)候,顯然是滿足條件的。
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
    else {
        //此處省略。。。
    }
}

至此,相信都知道了copy是怎么樣一個(gè)過程了。當(dāng)對(duì)一個(gè)堆上的block再次進(jìn)行調(diào)用copy的時(shí)候,因?yàn)槲覀冎敖oflags打入了BLOCK_NEEDS_FREE這個(gè)值,所以最后走的是這個(gè)判定條件

    if (aBlock->flags & BLOCK_NEEDS_FREE) { 
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }

結(jié)果就是增加引用計(jì)數(shù),然后返回該block。

輔助copy/dispose函數(shù)

1.普通變量的copy

在前面我們說到,如果有block復(fù)制到堆上的時(shí)候,有copy輔助函數(shù)的,該函數(shù)會(huì)被調(diào)用。以__block int i = 0為例子生成的輔助函數(shù)如下

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) 
{
    _Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);
}

在此之前,先賦上block中copy輔助函數(shù),flags標(biāo)識(shí)位能夠支持的類型

enum {
    BLOCK_FIELD_IS_OBJECT   =  3,  /* id, NSObject, __attribute__((NSObject)), block, ... */
    BLOCK_FIELD_IS_BLOCK    =  7,  /* a block variable */
    BLOCK_FIELD_IS_BYREF    =  8,  // __block修飾的基本數(shù)據(jù)類型
    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. */
};

runtime.c里面_Block_object_assign函數(shù)的實(shí)現(xiàn)方式如下

void _Block_object_assign(void *destAddr, const void *object, const int flags) 
{
    ...
    此處省略部分代碼
    ...
    
    //之前的生成的輔助函數(shù),flags = 8 => BLOCK_FIELD_IS_BYREF
   else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  
    {
       //在這里調(diào)用了_Block_byref_assign_copy,看下面的那個(gè)函數(shù)
        _Block_byref_assign_copy(destAddr, object, flags);
    }
    ...
    此處省略部分代碼
    ...   
}

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;
        
    if (src->forwarding->flags & BLOCK_IS_GC) {
        ;   // don't need to do any more work
    } else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        
        //當(dāng)初次拷貝時(shí),flags為0,進(jìn)入此分支會(huì)進(jìn)行復(fù)制操作并改變flags值,置入BLOCK_NEEDS_FREE和初始的引用計(jì)數(shù)

        bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));

        struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);

        copy->flags = src->flags | _Byref_flag_initial_value; //  _Byref_flag_initial_value

        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
        }
        // 當(dāng)發(fā)現(xiàn)對(duì)象存在輔助的copy函數(shù)的時(shí)候,把copy函數(shù)的賦值給在堆上的對(duì)象
        if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
            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
    //當(dāng)再次拷貝對(duì)象時(shí),則僅僅增加其引用計(jì)數(shù)
    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    // 將原來對(duì)象的forwarding 指向現(xiàn)在在堆上的對(duì)象
    _Block_assign(src->forwarding, (void **)destp);
}

上面就是一個(gè)被__block修飾的基本數(shù)據(jù)類型拷貝到堆上的時(shí)候,copy函數(shù)的實(shí)際調(diào)用過程。

普通oc對(duì)象的復(fù)制

以捕獲NSObject對(duì)象為栗子
輔助函數(shù)如下:

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*/);}

同樣是調(diào)用_Block_object_assign這個(gè)函數(shù),不過最終走的是這個(gè)

if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) 
{
        _Block_retain_object(object);
        _Block_assign((void *)object, destAddr);
}

把該對(duì)象retain之后,然后再賦值,這也就是說為什么block里面的對(duì)象需要使用weak的原因。

__block修飾的oc對(duì)象的復(fù)制

還是以NSObject為栗子,輔助函數(shù)如下:

//131即為BLOCK_FIELD_IS_OBJECT|BLOCK_BYREF_CALLER 

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

同樣是調(diào)用_Block_object_assign這個(gè)函數(shù),最終走的是這個(gè)

    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // 最終走的是這個(gè)
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }

門面上看起來似乎沒有retain住被__block修飾的對(duì)象,實(shí)際上在這里:

struct __Block_byref_test_0 {
  void *__isa;
__Block_byref_test_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *object; (在ARC下,這里retain住了對(duì)象,所以說__block在ARC下并不能解決循環(huán)引用問題,但是在MRC下,此處相當(dāng)于__unsafe_unretained, 在MRC下可以解決問題)
};

小結(jié)

通過上面的分析,相信大家對(duì)block有了更加清晰的理解。??
如果你看完以上內(nèi)容覺得so easy,那么你可能是大神。
如果你看完以上內(nèi)容覺得啥玩意,那么可能是我寫的太渣了。
如果文章中,有錯(cuò)誤的地方歡迎大家提出指正。

附錄

本文參考了以下兩篇帖子,特此奉上:
沒事蹦蹦的Block實(shí)現(xiàn)原理
BobooO的iOS中block介紹(四)揭開神秘面紗(下)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容