iOS - Block底層解析

BlockiOS開發(fā)中一種比較特殊的數(shù)據(jù)結(jié)構(gòu)绅项,它可以保存一段代碼,在合適的地方再調(diào)用比肄,具有語法簡介快耿、回調(diào)方便、編程思路清晰芳绩、執(zhí)行效率高等優(yōu)點润努,受到眾多猿猿的喜愛。但是Block在使用過程中示括,如果對Block理解不深刻铺浇,容易出現(xiàn)Cycle Retain的問題。本文主要從ARC模式下解析一下Block的底層實現(xiàn)垛膝,以及Block的三種類型(棧鳍侣、堆、全局)的區(qū)別吼拥。

一倚聚、Block定義

1. Block 定義及使用

返回值類型 (^block變量名)(形參列表) = ^(形參列表) {
};

// 調(diào)用Block保存的代碼
block變量名(實參);

2. 項目中使用格式

在項目中,通常會重新定義block的類型的別名凿可,然后用別名來定義block的類型

// 定義block類型
typedef void (^Block)(int);

// 定義block
Block block = ^(int a){};

// 調(diào)用block
block(3);

二惑折、Block底層實現(xiàn)

block的底層實現(xiàn)是結(jié)構(gòu)體,和類的底層實現(xiàn)類似枯跑,都有isa指針惨驶,可以把block當成是一個對象。下面通過創(chuàng)建一個控制臺程序敛助,來窺探block的底層實現(xiàn)

1. block內(nèi)存結(jié)構(gòu)

block 的內(nèi)存結(jié)構(gòu)圖

block內(nèi)存結(jié)構(gòu)圖

Block_layout結(jié)構(gòu)體成員含義如下:

  • isa: 指向所屬類的指針粗卜,也就是block的類型
  • flags: 標志變量,在實現(xiàn)block的內(nèi)部操作時會用到
  • Reserved: 保留變量
  • invoke: block執(zhí)行時調(diào)用的函數(shù)指針纳击,block內(nèi)部的執(zhí)行代碼都在這個函數(shù)中
  • descriptor: block的詳細描述续扔,包含 copy/dispose 函數(shù)攻臀,處理block引用外部變量時使用
  • variables: block范圍外的變量,如果block沒有調(diào)用任何外部變量纱昧,該變量就不存在

Block_descriptor結(jié)構(gòu)體成員含義如下:

  • reserved: 保留變量
  • size: block的內(nèi)存大小
  • copy: 拷貝block中被 __block 修飾的外部變量
  • dispose: 和 copy 方法配置應用刨啸,用來釋放資源

具體實現(xiàn)代碼如下(代碼來自Block_private.h):

enum {
    BLOCK_REFCOUNT_MASK =     (0xffff),
    BLOCK_NEEDS_FREE =        (1 << 24),
    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_HAS_DESCRIPTOR =    (1 << 29)
};

/* Revised new layout. */
struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};

struct Block_layout {
    void *isa;
    int flags;
    int reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

2. 創(chuàng)建block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        // 最簡block
        ^{ };
    }
    return 0;
}

3. 轉(zhuǎn)換結(jié)構(gòu)

利用 clang*.m 的文件轉(zhuǎn)換為 *.cpp 文件,就可以看到 block 的底層實現(xiàn)了

$ clang -rewrite-objc main.m 

轉(zhuǎn)換后的代碼:

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

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

從代碼中可以看出识脆,__main_block_impl_0就是blockC++實現(xiàn)(最后面的_0代表是main中的第幾個block)呜投,__main_block_func_0block的代碼塊,__main_block_desc_0block的描述存璃,__block_implblock的定義。

__block_impl成員含義如下:

  • isa: 指向所屬類的指針雕拼,也就是block的類型
  • flags纵东,標志變量,在實現(xiàn)block的內(nèi)部操作時會用到
  • Reserved啥寇,保留變量
  • FuncPtr偎球,block執(zhí)行時調(diào)用的函數(shù)指針

__main_block_impl_0解釋如下:

  • impl: block對象
  • Desc: block對象的描述

其中,__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) 這是顯式構(gòu)造函數(shù)辑甜,flags的默認值為0衰絮,函數(shù)體如下:

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

可以看出:

  • __main_block_impl_0的isa指針指向了_NSConcreteStackBlock,所有的局部block都是在棧上門創(chuàng)建
  • 從main函數(shù)中看磷醋, __main_block_impl_0的FuncPtr指向了函數(shù)__main_block_func_0
  • __main_block_impl_0的Desc也指向了定義__main_block_desc_0時就創(chuàng)建的__main_block_desc_0_DATA猫牡,其中Block_size記錄了block結(jié)構(gòu)體大小等信息

__main_block_desc_0成員含義如下:

  • reserved: 保留變量
  • Block_size: block內(nèi)存大小,sizeof(struct __main_block_impl_0)

三邓线、Block類型

block有三種類型:

  • _NSConcreteGlobalBlock: 存儲在全局數(shù)據(jù)區(qū)
  • _NSConcreteStackBlock: 存儲在棧區(qū)
  • _NSConcreteMallocBlock: 存儲在堆區(qū)

APUE的進程虛擬內(nèi)存段分布圖如下:

內(nèi)存分布圖

其中淌友,_NSConcreteGlobalBlock_NSConcreteStackBlock 可以由程序創(chuàng)建,而 _NSConcreteMallocBlock 則無法由程序創(chuàng)建骇陈,只能由 _NSConcreteStackBlock 通過拷貝生成震庭。

1. 全局block 和 棧block

測試代碼如下:

void (^globalBlock)() = ^{

};

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    
        void (^stackBlock1)() = ^{

        };
    }
    return 0;
}

clang轉(zhuǎn)換后的代碼如下:

// globalBlock
struct __globalBlock_block_impl_0 {
  ...
  __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    ...
  }
};
...

// stackBlock
struct __main_block_impl_0 {
  ...
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    ...
  }
};
...
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
        void (*stackBlock)() = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    }
    return 0;
}

可以看出, globalBlockisa 指向了 _NSConcreteGlobalBlock你雌,即在全局區(qū)域創(chuàng)建器联,編譯時其具體代碼在代碼段中,block變量則存儲在全局數(shù)據(jù)區(qū)婿崭;而stackBlockisa 則指向了 _NSConcreteStackBlock拨拓,表明在棧區(qū)創(chuàng)建。

2. 捕獲外部參數(shù)對棧區(qū)block結(jié)構(gòu)的影響

由于堆區(qū) block 是由棧區(qū) block 轉(zhuǎn)化而成氓栈, 所以下面主要分析棧區(qū) block 如何轉(zhuǎn)化為堆區(qū) block千元。

捕獲局部非靜態(tài)變量

代碼:

    int a = 0;
    ^{a;};

轉(zhuǎn)化后:

struct __Person__test_block_impl_0 {
  ...
  int a;
  // a(_a)是構(gòu)造函數(shù)的參數(shù)列表初始化形式,相當于a = _a颤绕。從_I_Person_test看幸海,傳入的就是a
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    ...
  }
};

static void _I_Person_test(Person * self, SEL _cmd) {
    int a;
    (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, a);
}

可以看到祟身,block 對于棧區(qū)變量的引用只是值傳遞,由于 block 內(nèi)部變量 a 和 外部變量 a 不在同一個作用域物独,所以在 block 內(nèi)部不能把變量 a 作為左值(left-value)袜硫,因為賦值沒有意義。所以挡篓,如果出現(xiàn)如下代碼婉陷,編譯器會提示錯誤:

a = 10;

捕獲局部靜態(tài)變量

代碼:

    static int a;
    ^{
        a = 10;
    };

轉(zhuǎn)換后:

struct __Person__test_block_impl_0 {
  ...
  int *a;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    ...
  }
};
static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  int *a = __cself->a; // bound by copy
  // 這里通過局部靜態(tài)變量a的地址來對其進行修改
  (*a) = 10;
}

static void _I_Person_test(Person * self, SEL _cmd) {
    static int a;
    // 傳入a的地址
    (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, &a);
}

由于局部靜態(tài)變量也存儲在靜態(tài)數(shù)據(jù)區(qū),和程序擁有一樣的生命周期官研,但是其作用范圍局限在定義它的函數(shù)中秽澳,所有在block里是通過地址來訪問。

捕獲全局變量

代碼:

// 全局靜態(tài)
static int a;
// 全局
int b;
- (void)test
{

    ^{
        a = 10;
        b = 10;
    };
}

轉(zhuǎn)換后:

static int a;
int b;

static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {

  a = 10;
  b = 10;
}

static void _I_Person_test(Person * self, SEL _cmd) {

    (void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA);
}

可以看出戏羽,因為全局變量都在靜態(tài)數(shù)據(jù)區(qū)担神,在程序結(jié)束前不會被銷毀,所以block直接訪問了對應的變量始花,而沒有在Persontest_block_impl_0結(jié)構(gòu)體中給變量預留位置妄讯。

捕獲對象的變量

代碼:

@interface Person()
{
    int _a;
}

@end

@implementation Person

- (void)test
{
    void (^block)() = ^{
        _a;
    };
}

@end

轉(zhuǎn)換后:

struct __Person__test_block_impl_0 {
  ...
  Person *self;
  __Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    ...
  }
};

static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy

  (*(int *)((char *)self + OBJC_IVAR_$_Person$_a));
}

static void _I_Person_test(Person * self, SEL _cmd) {
    void (*block)() = ((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
}

可以看到,即使 block只是引用對象的變量酷宵,但是底層依然引用的是對象本身 self亥贸,這和直接使用 self.a產(chǎn)生的循環(huán)引用的問題是一樣的。所以浇垦,要在 block 內(nèi)使用對象的弱引用炕置,即可解決循環(huán)引用的問題。并且男韧,對變量a的訪問也是通過 self的地址加 a的偏移量的形式讹俊。

捕獲__block修飾的基本變量

代碼:

    __block int a;
    ^{
        a = 10;
    };

轉(zhuǎn)換后:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  ...
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    ...
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref
  (a->__forwarding->a) = 10;
}

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

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

static struct __main_block_desc_0 {
  ...
  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};

        ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    }
    return 0;
}

可以看到,被__block修飾的變量被封裝成了一個對象煌抒,類型為__Block_byref_a_0仍劈,然后把&a作為參數(shù)傳給了block

__Block_byref_a_0 成員含義如下:

  • __isa: 指向所屬類的指針寡壮,被初始化為 (void*)0
  • __forwarding: 指向?qū)ο笤诙阎械目截?/li>
  • __flags: 標志變量贩疙,在實現(xiàn)block的內(nèi)部操作時會用到
  • __size: 對象的內(nèi)存大小
  • a: 原始類型的變量

其中,isa况既、__flags__size 的含義和之前類似这溅,而 __forwarding 是用來指向?qū)ο笤诙阎械目截悾?code>runtime.c 里有源碼說明:

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    ...
    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
    // 堆中拷貝的forwarding指向它自己
    copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
    // 棧中的forwarding指向堆中的拷貝
    src->forwarding = copy;  // patch stack to point to heap copy
    copy->size = src->size;
    ...
}

這樣做是為了保證在 block內(nèi) 或 block 變量后面對變量a的訪問,都是直接訪問堆內(nèi)的對象棒仍,而不上棧上的變量悲靴。同時,在 block 拷貝到堆內(nèi)時莫其,它所捕獲的由 __block 修飾的局部基本類型也會被拷貝到堆內(nèi)(拷貝的是封裝后的對象)癞尚,從而會有 copydispose處理函數(shù)耸三。

Block_byref的結(jié)構(gòu)定義在 Block_private.h 文件里有介紹:

struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    int flags; /* refcount; */
    int size;
    void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
    void (*byref_destroy)(struct Block_byref *);
    /* long shared[0]; */
};

// flags/_flags類型
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
        BLOCK_FIELD_IS_BLOCK    =  7,  /* a block variable */
        // 被__block修飾的變量
        BLOCK_FIELD_IS_BYREF    =  8,  /* the on stack structure holding the __block variable */
        // 被__weak修飾的變量,只能被輔助copy函數(shù)使用
        BLOCK_FIELD_IS_WEAK     = 16,  /* declared __weak, only used in byref copy helpers */
        // block輔助函數(shù)調(diào)用(告訴內(nèi)部實現(xiàn)不要進行retain或者copy)
        BLOCK_BYREF_CALLER      = 128  /* called from __block (byref) copy/dispose support routines. */
};

可以看到浇揩,Block_byref__Block_byref_a_0 的前4個成員類型相同仪壮,可以互相轉(zhuǎn)化。

** copy 函數(shù)

copy 的實現(xiàn)函數(shù)是 _Block_object_assign胳徽,它根據(jù)對象的 flags 來判斷是否需要拷貝积锅,或者只是賦值,函數(shù)實現(xiàn)在 runtime.c 里:

// _Block_object_assign源碼
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
...
    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);
    }
...
}

// _Block_byref_assign_copy源碼
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    // 這里因為前面4個成員的內(nèi)存分布一樣养盗,所以直接轉(zhuǎn)換后缚陷,使用Block_byref的成員變量名,能訪問到__Block_byref_a_0的前面4個成員
    struct Block_byref **destp = (struct Block_byref **)dest;
    struct Block_byref *src = (struct Block_byref *)arg;
...
    else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // 從main函數(shù)對__Block_byref_a_0的初始化往核,可以看到初始化時將flags賦值為0
        // 這里表示第一次拷貝箫爷,會進行復制操作,并修改原來flags的值
        // static int _Byref_flag_initial_value = BLOCK_NEEDS_FREE | 2;
        // 可以看出铆铆,復制后,會并入BLOCK_NEEDS_FREE丹喻,后面的2是block的初始引用計數(shù)
        ...
        copy->flags = src->flags | _Byref_flag_initial_value;
        ...
    }
    // 已經(jīng)拷貝到堆了薄货,只增加引用計數(shù)
    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    // 普通的賦值,里面最底層就*destptr = value;這句表達式
    _Block_assign(src->forwarding, (void **)destp);
}

主要操作都在代碼注釋中了碍论,總體來說谅猾,__block修飾的基本類型會被包裝為對象,并且只在最初block拷貝時復制一次鳍悠,后面的拷貝只會增加這個捕獲變量的引用計數(shù)税娜。

** dispose 函數(shù)

dispose 的實現(xiàn)函數(shù)是 _Block_object_dispose,代碼依然可以在 runtime.c 里:

void _Block_object_dispose(const void *object, const int flags) {
    //printf("_Block_object_dispose(%p, %x)\n", object, flags);
    if (flags & BLOCK_FIELD_IS_BYREF)  {
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
    }
    ...
}

// Old compiler SPI
static void _Block_byref_release(const void *arg) {
    struct Block_byref *shared_struct = (struct Block_byref *)arg;
    int refcount;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    shared_struct = shared_struct->forwarding;
    
    ...
    refcount = shared_struct->flags & BLOCK_REFCOUNT_MASK;
    ...
    else if ((latching_decr_int(&shared_struct->flags) & BLOCK_REFCOUNT_MASK) == 0) {
        ...
    }
}

可以看到藏研,被__block修改的變量敬矩,釋放時要 latching_decr_int減引用計數(shù),直到計數(shù)為0蠢挡,就釋放改對象弧岳;而普通的對象、block业踏,就直接釋放銷毀禽炬。

捕獲沒有__block修飾的對象

代碼:

    NSObject *a = [[NSObject alloc] init];
    Block block = ^ {
        a;
    };

轉(zhuǎn)換后部分結(jié)果如下:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  NSObject *a = __cself->a; // bound by copy
  a;
}

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

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

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        ...
        Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, 570425344));
    }
    return 0;
}

對象在沒有__block修飾時,并沒有產(chǎn)生__Block_byref_a_0結(jié)構(gòu)體勤家,只是將標志位修改為BLOCK_FIELD_IS_OBJECT腹尖。而在_Block_object_assign中對應的判斷分支代碼如下:

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

可以看到,block復制時伐脖,會retain捕捉對象热幔,以增加其引用計數(shù)乐设。

捕獲有__block修飾的對象

代碼:

    __block NSObject *a = [[NSObject alloc] init];
    Block block = ^ {
        a;
    };

轉(zhuǎn)化后部分結(jié)果如下:

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *a;
};
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, 33554432, sizeof(__Block_byref_a_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131,....};
Block block = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344);
}

// 以下的40表示__Block_byref_a_0對象a的位移(4個指針(32字節(jié))+2個int變量(8字節(jié))=40字節(jié))
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);
}

可以看到,對于對象断凶,處理增加了__Block_byref_a_0外伤提,還另外增加了兩個輔助函數(shù)__Block_byref_id_object_copy__Block_byref_id_object_dispose,以實現(xiàn)對對象內(nèi)存的管理认烁。其中兩者的最后一個參數(shù)131表示BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_OBJECT肿男,BLOCK_BYREF_CALLER表示在內(nèi)部實現(xiàn)中不對a對象進行retaincopy;以下為_Block_object_assign函數(shù)的部分代碼:

if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
    ...
    else {
        // do *not* retain or *copy* __block variables whatever they are
        _Block_assign((void *)object, destAddr);
    }
}

_Block_byref_assign_copy函數(shù)的以下代碼會對上面的輔助函數(shù)__Block_byref_id_object_copy_131進行調(diào)用却嗡;570425344表示BLOCK_HAS_COPY_DISPOSE | BLOCK_HAS_DESCRIPTOR舶沛,即(1<<25 | 1<<29),_Block_byref_assign_copy函數(shù)的部分代碼:

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

四窗价、ARC中block的工作特點

ARC模式下如庭,在棧間傳遞block時,不需要手動copy棧中的block撼港,即可讓block正常工作坪它。主要原因是ARC對棧中的block自動執(zhí)行了copy,將_NSConcreteStackBlock類型的block轉(zhuǎn)換成了_NSConcreteMallocBlock類型的block帝牡。

1. block往毡,非函數(shù)參數(shù)

代碼:

    int i = 10;
    void (^block)() = ^{i;};
    
    __unsafe_unretained void (^weakBlock)() = ^{i;};
    
    void (^stackBlock)() = ^{};
    
    NSLog(@"%@", ^{i;});
    
    NSLog(@"%@", block);
    
    NSLog(@"%@", weakBlock);
    
    NSLog(@"%@", stackBlock);
    
    /* ARC
     <__NSStackBlock__: 0x7fff5fbff708>
     <__NSMallocBlock__: 0x100300000>
     <__NSStackBlock__: 0x7fff5fbff738>
     <__NSGlobalBlock__: 0x100001110>
     */
    
    /* MRC
     <__NSStackBlock__: 0x7fff5fbff6e0>
     <__NSStackBlock__: 0x7fff5fbff740>
     <__NSStackBlock__: 0x7fff5fbff710>
     <__NSGlobalBlock__: 0x1000010e0>*/

從打印結(jié)果可以看出,ARC模式下靶溜,block 只有引用了外部的變量开瞭,并且被強引用,才會被拷貝到堆上罩息;只引用了外部的變量嗤详,或者被弱引用都只在棧上創(chuàng)建;如果沒有引用外部變量瓷炮,無論是否被強引用葱色,都會被轉(zhuǎn)換為全局 block,也就是說娘香,在編譯時冬筒,這個block的所有內(nèi)容已經(jīng)在代碼段中生成了。

2. block茅主,作為參數(shù)傳遞

代碼

typedef void (^Block)();

NSMutableArray *arrays;

void testBlock() {
    int a = 5;
    
    [arrays addObject:^{
        NSLog(@"%d", a);
    }];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here..
        
        arrays = @[].mutableCopy;
        
        testBlock();
        
        Block block = [arrays firstObject];
        
        NSLog(@"%@", block);
        
        /* ARC
         * <__NSMallocBlock__: 0x1006034c0>
         */
        
        /* MRC
         * 崩潰舞痰,野指針
         */
    }
    return 0;
}

可以看出,ARC模式下诀姚,棧區(qū)的block 被拷貝到了堆區(qū)响牛,在 testBlock 函數(shù)結(jié)束后依然可以訪問;而 MRC模式下,由于我們沒有手動執(zhí)行[block copy]來將block拷貝到堆區(qū)呀打,隨著函數(shù)生命周期結(jié)束矢赁,block被銷毀,訪問時出現(xiàn)野指針錯誤贬丛,但是如果把testBlock函數(shù)中的block打印語句刪掉:

NSLog(@"%d", a);

那么撩银,block就變?yōu)槿值模?code>MRC模式下豺憔,再次訪問不會出錯额获。


參考文章

http://www.reibang.com/p/51d04b7639f1
http://www.reibang.com/p/aff2cad778c0
http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy/
runtime.c
Block.private.h

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市恭应,隨后出現(xiàn)的幾起案子抄邀,更是在濱河造成了極大的恐慌,老刑警劉巖昼榛,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件境肾,死亡現(xiàn)場離奇詭異,居然都是意外死亡胆屿,警方通過查閱死者的電腦和手機奥喻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來非迹,“玉大人环鲤,你說我怎么就攤上這事〕垢眩” “怎么了楔绞?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵结闸,是天一觀的道長唇兑。 經(jīng)常有香客問我,道長桦锄,這世上最難降的妖魔是什么扎附? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮结耀,結(jié)果婚禮上留夜,老公的妹妹穿的比我還像新娘。我一直安慰自己图甜,他們只是感情好碍粥,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著黑毅,像睡著了一般嚼摩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天枕面,我揣著相機與錄音愿卒,去河邊找鬼。 笑死潮秘,一個胖子當著我的面吹牛琼开,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播枕荞,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼柜候,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了买猖?” 一聲冷哼從身側(cè)響起改橘,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎玉控,沒想到半個月后飞主,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡高诺,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年碌识,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片虱而。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡筏餐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出牡拇,到底是詐尸還是另有隱情魁瞪,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布惠呼,位于F島的核電站导俘,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏剔蹋。R本人自食惡果不足惜旅薄,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一诫钓、第九天 我趴在偏房一處隱蔽的房頂上張望供炼。 院中可真熱鬧,春花似錦稚照、人聲如沸矫付。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽买优。三九已至妨马,卻和暖如春樟遣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背身笤。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工豹悬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人液荸。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓瞻佛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親娇钱。 傳聞我的和親對象是個殘疾皇子伤柄,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

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

  • 前言 Blocks是C語言的擴充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,768評論 0 23
  • Blocks Blocks Blocks 是帶有局部變量的匿名函數(shù) 截取自動變量值 int main(){ ...
    南京小伙閱讀 929評論 1 3
  • Block基礎回顧 1.什么是Block文搂? 帶有局部變量的匿名函數(shù)(名字不重要适刀,知道怎么用就行),差不多就與C語言...
    Bugfix閱讀 6,766評論 5 61
  • 摘要block是2010年WWDC蘋果為Objective-C提供的一個新特性煤蹭,它為我們開發(fā)提供了便利笔喉,比如GCD...
    西門吹雪123閱讀 916評論 0 4
  • 《Objective-C高級編程》這本書就講了三個東西:自動引用計數(shù)、block硝皂、GCD常挚,偏向于從原理上對這些內(nèi)容...
    WeiHing閱讀 9,810評論 10 69