Block

Block 是 iOS4 之后添加的一種語法結(jié)構(gòu),也成為閉包,或者匿名函數(shù)。在 iOS 中被廣泛的使用,著名的第三方庫也大量用到此特性,如 AFNetworking,SDWebimage 等。

下文將介紹幾個方面

  1. Block 的基礎(chǔ)用法
  2. Block 底層表示
  3. Block 的變量捕獲
  4. __block 變量底層描述
  5. OC 中的 Block 3 種類型
  6. Block 的 copy 相關(guān)的理解
  7. __block 的 __forwarding

Block 語法

當(dāng)做局部變量

returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};

當(dāng)做屬性

@property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);

當(dāng)做方法參數(shù)

- (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;

調(diào)用方法

[someObject someMethodThatTakesABlock:^returnType (parameters) {...}];

typedef

typedef returnType (^TypeName)(parameterTypes);
TypeName blockName = ^returnType(parameters) {...};

Block 底層

我們可以使用 clang 的 rewrite 指令來生成 C/C++ 描述來供我們研究 Block 底層實現(xiàn)原理,首先先寫一個最簡單的 Block

int main(int argc, const char * argv[]) {
    void(^block)() = ^()
    {
        NSLog(@"hello");
    };
    block();
    return 0;
}

轉(zhuǎn)換后的代碼




static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_3d0d97_mi_0);
    }

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

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


int main(int argc, const char * argv[]) {
    void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

整理下,里面有幾個結(jié)構(gòu)體,第一個 __main_block_func_0

這個比較簡單,從代碼可以看出是我們 block 里面執(zhí)行語句,一個執(zhí)行函數(shù),參數(shù)是 __cself 類型是 __main_block_impl_0 也就是我們 block 本身,下文會提到。

第二個是__main_block_desc_0 一個 block 的描述結(jié)構(gòu)體,其中有2個字段:

  • reserved:保留字段。
  • Block_size block 大小。

第三個是__block_impl,這個是 block 對象。其中喲幾個字段

  • isa:類似于類的 isa 的指針,模擬對象。在 ARC 下有3種類型分別是_NSConcreteStackBlock_NSConcreteMallocBlock_NSConcreteGlobalBlock
  • Flags:標(biāo)志位,有以下幾個
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
};
  • Reserved:保留位
  • FuncPtr:block 執(zhí)行的函數(shù)指針地址。

看完以上3個結(jié)構(gòu)在來看這個__main_block_impl_0就比較簡單了。此結(jié)構(gòu)體包含了一個__block_impl__main_block_desc_0結(jié)構(gòu)體,
還有一個初始化方法,代碼比較簡單,只是賦值相應(yīng)的字段。
終于進入到我們的 main函數(shù)了

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

首先聲明了一個函數(shù)指針 block 也就是我們 OC 代碼中的

void(^block)()

然而右邊轉(zhuǎn)換成了

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

這句話的意思是,讓 block 這個變量的指針指向新建生成的__main_block_impl_0結(jié)構(gòu)體變量。它調(diào)用的構(gòu)造器并且傳入 __main_block_func_0(函數(shù)地址),__main_block_desc_0_DATA(描述字段)

下一句也就是我們執(zhí)行的 OC 代碼

    block();

被轉(zhuǎn)換成

    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

這里比較難理解,我們忽略多余的類型轉(zhuǎn)換這里取出了 block 指針的第一個成員(也就是__main_block_impl_0中的impl,因為指針類型是__block_impl,也就是在這個地址連續(xù)取__block_impl的大小,又因為impl是第一個結(jié)構(gòu)體成員,所以取出impl),之后調(diào)用了impl.FuncPtrblock 實現(xiàn)函數(shù)指針,傳入?yún)?shù) block,也就是自己。

這里我們上文提到

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_3d0d97_mi_0);
    }

這里有個參數(shù)是__cself,類型也是__main_block_impl_0,也就是傳入 block 自己本身的結(jié)構(gòu)體,這不難理解,聯(lián)想面向?qū)ο笳Z言中的方法第一個參數(shù)都會是self,這也是 block 模擬對象的原理之一,當(dāng)然還有更大用處,這里后文提到。

有一個點需要注意一下, block 結(jié)構(gòu)體有一定的命名規(guī)律(__xxx_block_impl_y:這里的 xxx 是 block 名稱,y 是該函數(shù)出現(xiàn)的順序值,如果 block 是匿名的則會是當(dāng)前作用域的函數(shù)名)。

變量捕獲

我們一直都知道,在 Block 中基礎(chǔ)類型的變量會被拷貝值,而指針變量則會捕獲指針變量,并且強引用,那么這到底是怎么回事呢?我們一起來研究一下
寫一個捕獲外部變量的 block

int main(int argc, const char * argv[]) {
    int i = 0;
    NSMutableArray *n = [NSMutableArray array];
    void(^block)() = ^()
    {
        int a = i;
        [n addObject:@"1"];
        NSLog(@"hello");
    };
    block();
    return 0;
}

改寫之后變成如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int i;
  NSMutableArray *n;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, NSMutableArray *_n, int flags=0) : i(_i), n(_n) {
    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
  NSMutableArray *n = __cself->n; // bound by copy

        int a = i;
        ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)n, sel_registerName("addObject:"), (id)(NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_16958d_mi_0);
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_16958d_mi_1);
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->n, (void*)src->n, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->n, 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[]) {


    int i = 0;
    NSMutableArray *n = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
    void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i, n, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

我們看到__main_block_impl_0多了2個變量,一個是 i 一個是 指針 n,這和我們捕獲的變量是一致的,構(gòu)造函數(shù)中初始化這 2 個值,也就是在底層,對于基礎(chǔ)類型,內(nèi)部會維護一個相應(yīng)的基礎(chǔ)類型變量,對于對象,則內(nèi)部也會有指向這個對象的指針(默認(rèn)為強引用)。
對于代碼中出現(xiàn)的 copy 和 dispose 下文我們會提到,可以先忽略它,

__block 底層描述

如果想在 block 中改變一個外界變量的值,那么這個變量必須聲明為 __block,這在底層是怎么實現(xiàn)的呢,我們一起來從代碼說話。
寫一個__block的例子

int main(int argc, const char * argv[]) {
    __block int i = 0;
   __block NSMutableArray *n = [NSMutableArray array];
    void(^block)() = ^()
    {
        i = 1;
        n = [NSArray array];
        NSLog(@"hello");
    };
    block();
    return 0;
}

改寫后的代碼

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};
struct __Block_byref_n_1 {
  void *__isa;
__Block_byref_n_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSMutableArray *n;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __Block_byref_n_1 *n; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, __Block_byref_n_1 *_n, int flags=0) : i(_i->__forwarding), n(_n->__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
  __Block_byref_n_1 *n = __cself->n; // bound by ref

        (i->__forwarding->i) = 1;
        (n->__forwarding->n) = ((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_ea2303_mi_0);
    }
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_object_assign((void*)&dst->n, (void*)src->n, 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_object_dispose((void*)src->n, 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[]) {

    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
   __attribute__((__blocks__(byref))) __Block_byref_n_1 n = {(void*)0,(__Block_byref_n_1 *)&n, 33554432, sizeof(__Block_byref_n_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))};
    void(*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, (__Block_byref_n_1 *)&n, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

我們注意到多了 2 個結(jié)構(gòu)體分別是__Block_byref_i_0__Block_byref_n_1。i 為基礎(chǔ)類型,n 為對象類型,
在底層__block修飾的變量會被轉(zhuǎn)換為__Block_byref結(jié)構(gòu)體,
其中分別有

  • __isa:isa 指針,模擬對象特性
  • __forwarding:轉(zhuǎn)發(fā)對象,后文會提到。
  • __flags:標(biāo)志位
  • __size:大小
  • 如果是對象類型,會多出__Block_byref_id_object_copy__Block_byref_id_object_dispose 這 2 都是和 copy 相關(guān)的操作,
  • 和變量捕獲一樣,會維護一個內(nèi)部變量。

于捕獲不同__main_block_impl_0這時候維護的變量會變成__Block_byref結(jié)構(gòu)體變量(__Block_byref 內(nèi)部也維護了一個變量)。構(gòu)造函數(shù)在構(gòu)造的時候會把 __forwarding 會指向外界的 __block對象
在來看執(zhí)行函數(shù)的部分代碼

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref
  __Block_byref_n_1 *n = __cself->n; // bound by ref

        (i->__forwarding->i) = 1;
        (n->__forwarding->n) = ((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("array"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_v4_8fn73k9s2p5bxbsdm0cvqyd40000gn_T_main_ea2303_mi_0);
    }

可以看出,執(zhí)行部分也變了,直接將__forwarding里面所維護的變量拿出來設(shè)置值,從而達(dá)到在 block 體內(nèi)修改變量的作用。

為什么要有__forwarding,這是個很重要的概念,下文我們在 blcok 的拷貝部分會同意處理

Block 的 3 種類型

我們可以寫一段代碼來測試一下 block 的類型

int gi = 0;
int main(int argc, const char * argv[]) {
    int i = 0;
    static int si= 0;
   void (^block)(void) = ^void()
    {
    };
    NSLog(@"沒有引用任何變量= %@", block);
    
    block = ^void()
    {
        int inside = i;
    };
    NSLog(@"使用局部變量= %@", block);

    
   block = ^void()
    {
        int inside = si;
    };
    NSLog(@"使用局部靜態(tài)變量 =%@", block);

    
   block = ^void()
    {
        int inside = gi;
    };
    NSLog(@"使用全局變量 = %@", block);
    
    
    NSLog(@"ARC 不參與賦值操作(未引用任何變量) %@", ^{});
    NSLog(@"ARC 不參與賦值操作(使用全局變量) %@", ^{int n = gi;});
    NSLog(@"ARC 不參與賦值操作(使用局部變量) %@", ^{int n = i;});
    NSLog(@"ARC 不參與賦值操作(使用局部靜態(tài)變量) %@", ^{int n = si;});

    return 0;
}

輸出的 log 為

2016-09-25 00:50:40.664318 Block[5731:231257] 沒有引用任何變量= <__NSGlobalBlock__: 0x100001060>
2016-09-25 00:50:40.664561 Block[5731:231257] 使用局部變量= <__NSMallocBlock__: 0x100202780>
2016-09-25 00:50:40.664586 Block[5731:231257] 使用局部靜態(tài)變量 =<__NSGlobalBlock__: 0x1000010c0>
2016-09-25 00:50:40.664625 Block[5731:231257] 使用全局變量 = <__NSGlobalBlock__: 0x100001100>
2016-09-25 00:50:40.664639 Block[5731:231257] ARC 不參與賦值操作(未引用任何變量) <__NSGlobalBlock__: 0x100001140>
2016-09-25 00:50:40.664678 Block[5731:231257] ARC 不參與賦值操作(使用全局變量) <__NSGlobalBlock__: 0x100001180>
2016-09-25 00:50:40.664710 Block[5731:231257] ARC 不參與賦值操作(使用局部變量) <__NSStackBlock__: 0x7fff5fbff770>
2016-09-25 00:50:40.664725 Block[5731:231257] ARC 不參與賦值操作(使用局部靜態(tài)變量) <__NSGlobalBlock__: 0x1000011e0>
Program ended with exit code: 0

由此我們可以得出結(jié)論

  • _NSConcreteStackBlock:
    只用到外部局部變量、成員屬性變量,且沒有強指針引用的block都是StackBlock。
    StackBlock的生命周期由系統(tǒng)控制的,一旦返回之后,就被系統(tǒng)銷毀了。

  • _NSConcreteMallocBlock:
    有強指針引用或copy修飾的成員屬性引用的block會被復(fù)制一份到堆中成為MallocBlock,沒有強指針引用即銷毀,生命周期由程序員控制

  • _NSConcreteGlobalBlock:
    沒有用到外界變量或只用到全局變量、靜態(tài)變量的block為_NSConcreteGlobalBlock,生命周期從創(chuàng)建到應(yīng)用程序結(jié)束。

ARC 下的 Block 類型

在 ARC 下,任何的 block 對強引用變量賦值操作將會觸發(fā) block 的 copy 操作,如上文中 第二個 block 的輸出,照規(guī)則來說應(yīng)該是 _NSConcreteStackBlock
,但是此 block 賦值給了 block 變量,所以這時候會變成了_NSConcreteMallocBlock,如果 block 變量由__weak修飾,那么不會發(fā)生 copy 也就是還是原來的_NSConcreteStackBlock

Block 的 copy 相關(guān)

在變量捕獲那一部分,我們看到了 copy 和 dispose 相關(guān)的內(nèi)容,這些內(nèi)容是拷貝和釋放相關(guān)的部分內(nèi)容,我們一起來探討一下。

copy

拷貝方法我們可以在系統(tǒng)庫中看到_Block_copy方法,方法也是開源點擊查看

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

內(nèi)部調(diào)用了 _Block_copy_internal方法

簡化版_Block_copy_internal

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;

    // 1
    if (!arg) return NULL;

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

    // 5
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;

    // 6
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first

    // 7
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;

    // 8
    result->isa = _NSConcreteMallocBlock;

    // 9
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }

    return result;
}

可以看出內(nèi)部處理手段,通過 Block 的 descriptor 獲取 Block 大小,之后直接 malloc 在堆上創(chuàng)建一個新個 block ,之后使用memmove拷貝內(nèi)存空間,將 isa 設(shè)置為 _NSConcreteMallocBlock ,最后如果是 Block 是 copy 的話,調(diào)用 block->descriptor->copy簡化版代碼出處

block->descriptor->copy 是什么玩意呢,
在 block 的 descriptor 中,新增了 2 個函數(shù)指針 copy 和 dispose,指向相應(yīng) block 的捕獲變量的 assign(基礎(chǔ)類型) 和 retain (對象)這也是為什么 block 會循環(huán)引用所在
copy 這個指針最后會調(diào)用_Block_object_assign,根據(jù)不同類型,會傳入不同 flags調(diào)用不同方法

/*
 * When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
 * to do the assignment.
 */
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }
    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
        // copying a __block reference from the stack Block to the heap
        // flags will indicate if it holds a __weak reference and needs a special isa
        _Block_byref_assign_copy(destAddr, object, flags);
    }
    // (this test must be before next one)
    else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
        // copying a Block declared variable from the stack Block to the heap
        _Block_assign(_Block_copy_internal(object, flags), destAddr);
    }
    // (this test must be after previous one)
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        //printf("retaining object at %p\n", object);
        _Block_retain_object(object);
        //printf("done retaining object at %p\n", object);
        _Block_assign((void *)object, destAddr);
    }
}
  • 如果 block 里面引用了對象類型會傳入BLOCK_FIELD_IS_OBJECT。最終會調(diào)用_Block_retain_object來 retain 對象,使用_Block_assign來賦值。
  • 如果 block 里面引用了 Block 會傳入BLOCK_FIELD_IS_BLOCK最終也使用_Block_copy_internal來進行 copy
  • 如果 block 里面引用了__block變量,會傳入BLOCK_FIELD_IS_BYREF,使用_Block_byref_assign_copy來設(shè)置 forwarding 指向,這個我們后面說。

dispose

釋放過程的簡化版的代碼如下

void _Block_release(void *arg) {
    // 1
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;

    // 2
    int32_t newCount;
    newCount = latching_decr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK;

    // 3
    if (newCount > 0) return;

    // 4
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)(*aBlock->descriptor->dispose)(aBlock);
        _Block_deallocator(aBlock);
    }

    // 5
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        ;
    }

    // 6
    else {
        printf("Block_release called upon a stack Block: %p, ignored\n", (void *)aBlock);
    }
}

代碼比較簡單,最終也可以看到會調(diào)用Block->descriptor->dispose,這和 copy 是相對應(yīng)的,可以自己去挖掘一下,這邊就不寫出來了。

__Block 中的 __forwarding

前文__block部分我們會發(fā)現(xiàn)出現(xiàn)__forwarding這個東西,既然存在必然有存在的意義,__forwarding在初始化的時候我們可以看到是指向自己的。

這里有幾個情況,

  1. 當(dāng) block 是 _NSConcreteStackBlock 的時候,這個時候__forwarding指向自己,在執(zhí)行的函數(shù)中,使用 __forwarding->xxx 訪問變量。或者設(shè)置 __forwarding->xxx 來修改 __block 指向的對象,則也是為什么 __block 可以修改外界變量的原因。
  2. 當(dāng)一個 block 被 copy 情況下呢,如果這個時候棧上的 block 已經(jīng)出了作用域,如果 __forwarding 還是自己,那么必然指向已經(jīng)釋放的地址,這不是我們要的結(jié)果,上一節(jié)中,談到過 Block 被 copy 會 malloc 出一片新空間,那么 __block 變量也會被 copy,這時候?qū)?__forwarding 指向堆上的那個 block 的 __forwarding,這時候 __forwarding->xxx 就是訪問堆上的新的變量,即使出了作用域,我們的結(jié)果也是正確的。

下面有一張圖可以加深理解

1194012-5f5f486bab68191f

圖片來自http://www.lxweimin.com/p/ee9756f3d5f6

那么 __forwarding 是如何被修改的呢?
還記得上一下小結(jié)提到的_Block_byref_assign_copy嗎。我們可以看下實現(xiàn)。

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

可以看出 __block 在 copy 的時候會設(shè)置 __forwarding

copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy; 

小結(jié)

Block 的在提供便利性的同時也會引入循環(huán)引用等問題,我們需要知道如何合理使用 Block。當(dāng)然 Block 還有大量內(nèi)容需要去研究。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,436評論 2 378

推薦閱讀更多精彩內(nèi)容

  • 前言 Blocks是C語言的擴充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,781評論 0 23
  • Block基礎(chǔ)回顧 1.什么是Block? 帶有局部變量的匿名函數(shù)(名字不重要,知道怎么用就行),差不多就與C語言...
    Bugfix閱讀 6,790評論 5 61
  • 摘要block是2010年WWDC蘋果為Objective-C提供的一個新特性,它為我們開發(fā)提供了便利,比如GCD...
    西門吹雪123閱讀 934評論 0 4
  • Block 梳理與疑問 時隔一年,再次讀 《Objective-C 高級編程》,看到 block 一章,這一次從頭...
    DeerRun閱讀 651評論 0 2
  • 《Objective-C高級編程》這本書就講了三個東西:自動引用計數(shù)、block、GCD,偏向于從原理上對這些內(nèi)容...
    WeiHing閱讀 9,865評論 10 69