Block理解

  • 1: 什么是block?

    • 1.0: Block的語法
    • 1.1: block編譯轉換結構
    • 1.2: block實際結構
  • 2: block的類型

    • 2.1: NSConcreteGlobalBlock和NSConcreteStackBlock
    • 2.2: NSConcreteMallocBlock
  • 3: 捕捉變量對block結構的影響

    • 3.1: 局部變量
    • 3.2: 全局變量
    • 3.3: 局部靜態變量
    • 3.4: __block修飾的變量
    • 3.5: self隱式循環引用
  • 4: 不同類型block的復制

    • 4.1: 棧block
    • 4.2: 堆block
    • 4.3: 全局block
  • 5: block輔助函數

    • 5.1: __block修飾的基本類型的輔助函數
    • 5.2: 對象的輔助函數
  • 6: ARC中block的工作

    • 6.1: block試驗
    • 6.2: block作為參數傳遞
    • 6.3: block作為返回值
    • 6.4: block屬性
  • 7: 參考博文

1: 什么是block?

Block是C語言的擴充功能, C語言中為了調用函數, 我們, 必須使用該函數的名稱func

int result = func(100);

當然我們可以函數指針調用

int (*funcPstr) (int) = &func;
int result = (*funcPstr)(10);

通過Blocks, 源代碼就能夠使用匿名函數, 不帶名稱的函數

1.0: Block的語法

完整的形式的block語法和C語言函數定義相比, 僅有兩點不同。

  • 1:沒有函數名, 匿名函數或者函數指針理解就可以了
  • 2: 帶有 ^.便于區分

^ 返回值類型 參數列表 表達式

例如:

int  (^blk)(int) = ^(int count){
       return count + 1;
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        ^{ };
    }
    return 0;
}
1.1: block編譯轉換結構

對其執行clang -rewrite-objc編譯轉換成C++實現,得到以下代碼:

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, char const *argv[])
{
 ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
 return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

其中的__main_block_impl_0就是block的一個C++的實現

1: 其中__block_impl的定義如下:其結構體成員如下:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  • 1: isa,指向所屬類的指針,也就是block的類型
  • 2: flags,標志變量,在實現block的內部操作時會用到
  • 3: Reserved,保留變量
  • 4: FuncPtr,block執行時調用的函數指針
    可以看出,它包含了isa指針, 也就是說block也是一個對象(runtime里面,對象和類都是用結構體表示)。

**2: __main_block_desc_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)};

其結構成員含義如下:

  • 1: reserved:保留字段
  • 2: Block_size:block大小(sizeof(struct __main_block_impl_0))

代碼在定義__main_block_desc_0結構體時,同時創建了__main_block_desc_0_DATA,并給它賦值,以供在main函數中對__main_block_impl_0進行初始化。

3: __main_block_impl_0定義了顯式的構造函數,其函數體如下:

  __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;
  }
  • 1: __main_block_impl_0的isa指針指向了_NSConcreteStackBlock
  • 2: 從main函數中看, __main_block_impl_0FuncPtr指向了函數__main_block_func_0
  • 3: __main_block_impl_0的Desc也指向了定義__main_block_desc_0時就創建的__main_block_desc_0_DATA,其中紀錄了block結構體大小等信息。

根據編譯轉換的結果,對一個簡單block的解析,后面會將block操作不同類型的外部變量,對block結構的影響進行相應的說明。

1.2: block實際結構

接下來觀察下Block_private.h文件中對block的相關結構體的真實定義:

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

有了上文對編譯轉換的分析,這里只針對略微不同的成員進行分析:

  • 1: invoke,同上文的FuncPtr,block執行時調用的函數指針,block定義時內部的執行代碼都在這個函數中
  • 2: Block_descriptor,block的詳細描述.
    • copy/dispose,輔助拷貝/銷毀函數,處理block范圍外的變量時使用

總體來說,block就是一個里面存儲了指向函數體中包含定義block時的代碼塊的函數指針,以及block外部上下文變量等信息的結構體。

2: block的類型

block的常見類型有3種:

  • 1: _NSConcreteGlobalBlock(全局)
  • 2: _NSConcreteStackBlock(棧)
  • 3: _NSConcreteMallocBlock(堆)

http://upload-images.jianshu.io/upload_images/608238-2393520e3fec4271.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

由于無法直接創建_NSConcreteMallocBlock類型的block,所以這里只對前面2種進行手動創建分析,最后1種通過源代碼分析。

2.1 NSConcreteGlobalBlock和NSConcreteStackBlock(全局和棧區的block)
// 全局block
void (^globalBlock)() = ^{

};


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

        };
    }
    return 0;
}

對其進行編譯轉換后得到以下縮略代碼:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
 

// globalBlock的實現
struct __globalBlock_block_impl_0 {
  struct __block_impl impl;
  struct __globalBlock_block_desc_0* Desc;
  __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// globalBlock的方法
static void __globalBlock_block_func_0(struct __globalBlock_block_impl_0 *__cself) {


}

// globalBlock的描述
static struct __globalBlock_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __globalBlock_block_desc_0_DATA = { 0, sizeof(struct __globalBlock_block_impl_0)};
static __globalBlock_block_impl_0 __global_globalBlock_block_impl_0((void *)__globalBlock_block_func_0, &__globalBlock_block_desc_0_DATA);
void (*globalBlock)() = ((void (*)())&__global_globalBlock_block_impl_0);


// mainBlock的棧block
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 (*stackBlock1)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    }
    return 0;
}
  • 1: 可以看出globalBlock的isa指向了_NSConcreteGlobalBlock,即在全局區域創建,編譯時具體的代碼就已經確定在上圖中的代碼段中了,block變量存儲在全局數據存儲區;
  • 2: stackBlock的isa指向了_NSConcreteStackBlock,即在棧區創建。
2.2 NSConcreteMallocBlock

堆中的block,堆中的block無法直接創建,其需要由_NSConcreteStackBlock類型的block拷貝而來(也就是說block需要執行copy之后才能存放到堆中)。由于block的拷貝最終都會調用_Block_copy_internal函數,所以觀察這個函數就可以知道堆中block是如何被創建的了:

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    ...
    aBlock = (struct Block_layout *)arg;
    ...
    // Its a stack block.  Make a copy.
    if (!isGC) {
        // 申請block的堆內存
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        // 拷貝棧中block到剛申請的堆內存中
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        // 改變isa指向_NSConcreteMallocBlock,即堆block類型
        result->isa = _NSConcreteMallocBlock;
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
    else {
        ...
    }
}

從以上代碼以及注釋可以很清楚的看出,函數通過memmove將棧中的block的內容拷貝到了堆中,并使isa指向了_NSConcreteMallocBlock
block主要的一些學問就出在棧中block向堆中block的轉移過程中了。

3: 捕捉變量對block結構的影響

接下來會編譯轉換捕捉不同變量類型的block,以對比它們的區別。

3.1 局部變量
  • 前:
- (void)test
{
    int a;
    ^{a;};
}
  • 后:
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;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : 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) {
  int a = __cself->a; // bound by copy
a;}

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; 
       int a;
     ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    }
    return 0;
}

可以看到,block相對于文章開頭增加了一個int類型的成員變量,他就是用來存儲外部變量a的。可以看出,這次拷貝只是一次值傳遞。并且當我們想在block中進行以下操作時,將會發生錯誤

^{a = 10;};

http://upload-images.jianshu.io/upload_images/608238-712f1c798d842cbd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240

在block對a進行賦值是沒有意義的,所以編譯器給出了錯誤。我們可以通過地址傳遞來消除以上錯誤:

- (void)test
{
    int a = 0;
    // 利用指針p存儲a的地址
    int *p = &a;

    ^{
        // 通過a的地址設置a的值
        *p = 10;
    };
}

但是變量a的生命周期是和方法test的棧相關聯的,當test運行結束,棧隨之銷毀,那么變量a就會被銷毀,p也就成為了野指針。如果block是作為參數或者返回值,這些類型都是跨棧的,也就是說再次調用會造成野指針錯誤。

3.2 全局變量
  • 1: 前:
// 全局靜態
static int a;
// 全局
int b;
- (void)test
{

    ^{
        a = 10;
        b = 10;
    };
}
  • 2: 后:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

static int a;

int b;

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) {
        a = 10;
        b = 10;
}

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[]) {

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

可以看出,因為全局變量都是在靜態數據存儲區,在程序結束前不會被銷毀,所以block直接訪問了對應的變量,而沒有在Persontest_block_impl_0結構體中給變量預留位置。

3.3 局部靜態變量
  • 1: 前:
- (void)test
{
    static int a;
    ^{
        a = 10;
    };
}
  • 2: 后:
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;
  int *a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_a, int flags=0) : 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) {
  int *a = __cself->a; // bound by copy

        (*a) = 10;
    }

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[]) {

   static int a;
    ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a));
    return 0;
}

靜態局部變量是存儲在靜態數據存儲區域的,也就是和程序擁有一樣的生命周期,也就是說在程序運行時,都能夠保證block訪問到一個有效的變量。但是其作用范圍還是局限于定義它的函數中,所以只能在block通過靜態局部變量的地址來進行訪問。

3.4 __block修飾的變量
  • 1: 前:
- (void)test
{
   __block int a;
    ^{
        a = 10;
    };
}
  • 2: 后:

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;
  __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;
    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
        (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 {
  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_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0)};
;
    ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    return 0;
}

對比上面的結果多了__Block_byref_a_0結構體,這個結構體中含有isa指針,所以也是一個對象,它是用來包裝局部變量a的。當block被copy到堆中時,__Person__test_block_impl_0的拷貝輔助函數__Person__test_block_copy_0會將__Block_byref_a_0拷貝至堆中,所以即使局部變量所在堆被銷毀,block依然能對堆中的局部變量進行操作。其中__Block_byref_a_0成員指針__forwarding用來指向它在堆中的拷貝,其依據源碼如下:

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;

    ...
    // 堆中拷貝的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
    ...
}

為了保證操作的值始終是堆中的拷貝,而不是棧中的值。(處理在局部變量所在棧還沒銷毀,就調用block來改變局部變量值的情況,如果沒有__forwarding指針,則修改無效)
至于block如何實現對局部變量的拷貝,下面會詳細說明。

3.5: self隱式循環引用
  • 1: 前:
@implementation Person
{
    int _a;
    void (^_block)();
}
- (void)test
{
  void (^_block)() = ^{
        _a = 10;
    };
}

@end
  • 2: 后:
struct __Person__test_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_desc_0* Desc;
  // 可以看到,block強引用了self
  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;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
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)) = 10;
    }
static void __Person__test_block_copy_0(struct __Person__test_block_impl_0*dst, struct __Person__test_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __Person__test_block_dispose_0(struct __Person__test_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static struct __Person__test_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __Person__test_block_impl_0*, struct __Person__test_block_impl_0*);
  void (*dispose)(struct __Person__test_block_impl_0*);
} __Person__test_block_desc_0_DATA = { 0, sizeof(struct __Person__test_block_impl_0), __Person__test_block_copy_0, __Person__test_block_dispose_0};

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

如果在編譯轉換前,將_a改成self.a,能很明顯地看出是產生了循環引用(self強引用block,block強引用self)。那么使用_a呢?經過編譯轉換后,依然可以在__Person__test_block_impl_0看見self的身影。且在函數_I_Person_test中,傳入的參數也是self。通過以下語句,可以看出,不管是用什么形式訪問實例變量,最終都會轉換成self+變量內存偏移的形式。所以在上面例子中使用_a也會造成循環引用。

static void __Person__test_block_func_0(struct __Person__test_block_impl_0 *__cself) {
  Person *self = __cself->self; // bound by copy
        // self+實例變量a的偏移值
        (*(int *)((char *)self + OBJC_IVAR_$_Person$_a)) = 10;
    }

4: 不同類型block的復制

block的復制代碼在_Block_copy_internal函數中。

4.1 :棧block

棧block的復制不僅僅復制了其內容,還添加了一些額外的東西

  • 1: 往flags中并入了BLOCK_NEEDS_FREE(這個標志表明block需要釋放,在release以及再次拷貝時會用到)
  • 2: 如果有輔助copy函數BLOCK_HAS_COPY_DISPOSE, 那么就調用(這個輔助copy函數是用來拷貝block捕獲的變量
...
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
   result->flags |= BLOCK_NEEDS_FREE | 1;
   result->isa = _NSConcreteMallocBlock;
   if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
       //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
       (*aBlock->descriptor->copy)(result, aBlock); // do fixup
   }
   return result;
...
4.2 :堆block

如果block的flags中有BLOCK_NEEDS_FREE標志(block從棧中拷貝到堆時添加的標志),就執行latching_incr_int操作,其功能就是讓block的引用計數加1。所以堆中block的拷貝只是單純地改變了引用計數

  if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
  }
4.3 全局block

從以下代碼看出,對于全局block,函數沒有做任何操作,直接返回了傳入的block

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

5: block輔助函數

block輔助copy與dispose處理函數,這里分析下這兩個函數的內部實現。在捕獲變量為__block修飾的基本類型,或者為對象時,block才會有這兩個輔助函數。
block捕捉變量拷貝函數為_Block_object_assign。在調用復制block的函數_Block_copy_internal時,會根據block有無輔助函數來對捕捉變量拷貝函數_Block_object_assign進行調用。而在_Block_object_assign函數中,也會判斷捕捉變量包裝而成的對象(Block_byref結構體)是否有輔助函數,來進行調用。

5.1 __block修飾的基本類型的輔助函數

編寫以下代碼:

typedef void(^Block)();
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block int a;
        Block block = ^ {
            a;
        };
}

換成C++代碼后

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

            (a->__forwarding->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, 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 {
  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)};
;
        Block block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    }
}

從上面代碼中,被__block修飾的a變量變為了__Block_byref_a_0類型,根據這個格式,從源碼中查看得到相似的定義:

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

// 做下對比
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

// 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函數使用
        BLOCK_FIELD_IS_WEAK     = 16,  /* declared __weak, only used in byref copy helpers */
        // block輔助函數調用(告訴內部實現不要進行retain或者copy)
        BLOCK_BYREF_CALLER      = 128  /* called from __block (byref) copy/dispose support routines. */
    };

可以看出,__block將原來的基本類型包裝成了對象。因為以上兩個結構體的前4個成員的類型都是一樣的,內存空間排列一致,所以可以進行以下操作:

// 轉換成C++代碼
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 _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個成員的內存分布一樣,所以直接轉換后,使用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函數對__Block_byref_a_0的初始化,可以看到初始化時將flags賦值為0
        // 這里表示第一次拷貝,會進行復制操作,并修改原來flags的值
        // static int _Byref_flag_initial_value = BLOCK_NEEDS_FREE | 2;
        // 可以看出,復制后,會并入BLOCK_NEEDS_FREE,后面的2是block的初始引用計數
        ...
        copy->flags = src->flags | _Byref_flag_initial_value;
        ...
    }
    // 已經拷貝到堆了,只增加引用計數
    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拷貝時復制一次,后面的拷貝只會增加這個捕獲變量的引用計數。

5.2 對象的輔助函數
  • 1: 沒有__block修飾
typedef void(^Block)();
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *a = [[NSObject alloc] init];
        Block block = ^ {
            a;
        };
    }
    return 0;
}

首先,在沒有__block修飾時,對象編譯轉換的結果如下,刪除了一些變化不大的代碼:

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

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),

對象在沒有__block修飾時,并沒有產生__Block_byref_a_0結構體,只是將標志位修改為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捕捉對象,以增加其引用計數。

  • 2: 有__block修飾
typedef void(^Block)();
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block NSObject *a = [[NSObject alloc] init];
        Block block = ^ {
            a;
        };
    }
    return 0;
}

在這種情況下,編譯轉換的部分結果如下:

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字節)+2個int變量(8字節)=40字節)
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另外增加了兩個輔助函數__Block_byref_id_object_copy__Block_byref_id_object_dispose,以實現對對象內存的管理。其中兩者的最后一個參數131表示BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_OBJECTBLOCK_BYREF_CALLER表示在內部實現中不對a對象進行retaincopy;以下為相關源碼:

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函數的以下代碼會對上面的輔助函數(__Block_byref_id_object_copy_131)進行調用;570425344表示BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR,所以會執行以下相關源碼:

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

6: ARC中block的工作

蘋果文檔提及,在ARC模式下,在棧間傳遞block時,不需要手動copy棧中的block,即可讓block正常工作。主要原因是ARC對棧中的block自動執行了copy,將_NSConcreteStackBlock類型的block轉換成了_NSConcreteMallocBlock的block

6.1 : block試驗
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int i = 10;
        void (^block)() = ^{i;};

        __weak void (^weakBlock)() = ^{i;};

        void (^stackBlock)() = ^{};

        // ARC情況下

        // 創建時,都會在棧中
        // <__NSStackBlock__: 0x7fff5fbff730>
        NSLog(@"%@", ^{i;});

        // 因為block為strong類型,且捕獲了外部變量,所以賦值時,自動進行了copy
        // <__NSMallocBlock__: 0x100206920>
        NSLog(@"%@", block);

        // 如果是weak類型的block,依然不會自動進行copy
        // <__NSStackBlock__: 0x7fff5fbff728>
        NSLog(@"%@", weakBlock);

        // 如果block是strong類型,并且沒有捕獲外部變量,那么就會轉換成__NSGlobalBlock__
        // <__NSGlobalBlock__: 0x100001110>
        NSLog(@"%@", stackBlock);

        // 在非ARC情況下,產生以下輸出
        // <__NSStackBlock__: 0x7fff5fbff6d0>
        // <__NSStackBlock__: 0x7fff5fbff730>
        // <__NSStackBlock__: 0x7fff5fbff700>
        // <__NSGlobalBlock__: 0x1000010d0>
    }
    return 0;
}

可以看出,ARC對類型為strong且捕獲了外部變量的block進行了copy。并且當block類型為strong,但是創建時沒有捕獲外部變量,block最終會變成__NSGlobalBlock__類型(這里可能因為block中的代碼沒有捕獲外部變量,所以不需要在棧中開辟變量,也就是說,在編譯時,這個block的所有內容已經在代碼段中生成了,所以就把block的類型轉換為全局類型)

6.2: block作為參數傳遞

再來看下使用在棧中的block需要注意的情況:

NSMutableArray *arrayM;
void myBlock()
{
    int a = 5;
    Block block = ^ {
        NSLog(@"%d", a);
    };

    [arrayM addObject:block];
    NSLog(@"%@", block);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        arrayM = @[].mutableCopy;

        myBlock();

        Block block = [arrayM firstObject];
        // 非ARC這里崩潰
        block();
 }

// ARC情況下輸出
// <__NSMallocBlock__: 0x100214480>

// 非ARC情況下輸出
// <__NSStackBlock__: 0x7fff5fbff738>
// 崩潰,野指針錯誤

可以看到,ARC情況下因為自動執行了copy,所以返回類型為__NSMallocBlock__,在函數結束后依然可以訪問;而非ARC情況下,需要我們手動調用[block copy]來將block拷貝到堆中,否則因為棧中的block生命周期和函數中的棧生命周期關聯,當函數退出后,相應的堆被銷毀,block也就不存在了。

如果把block的以下代碼刪除:

NSLog(@"%d", a);

那么block就會變成全局類型,在main中訪問也不會出崩潰。

6.3: block作為返回值

在非ARC情況下,如果返回值是block,則一般這樣操作:

return [[block copy] autorelease];

對于外部要使用的block,更趨向于把它拷貝到堆中,使其脫離棧生命周期的約束。

6.4: block屬性

這里還有一點關于block類型的ARC屬性。上文也說明了,ARC會自動幫strong類型且捕獲外部變量的block進行copy,所以在定義block類型的屬性時也可以使用strong,不一定使用copy。也就是以下代碼:

/** 假如有棧block賦給以下兩個屬性 **/

// 這里因為ARC,當棧block中會捕獲外部變量時,這個block會被copy進堆中
// 如果沒有捕獲外部變量,這個block會變為全局類型
// 不管怎么樣,它都脫離了棧生命周期的約束

@property (strong, nonatomic) Block *strongBlock;

// 這里都會被copy進堆中
@property (copy, nonatomic) Block *copyBlock;

http://blog.devtang.com/2013/07/28/a-look-inside-blocks/
http://blog.csdn.net/jasonblog/article/details/7756763
http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy/
http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/runtime.c
http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/Block_private.h
http://clang.llvm.org/docs/Block-ABI-Apple.html
https://github.com/EmbeddedSources/BlockRuntime
http://www.lxweimin.com/p/51d04b7639f1

1: 一篇文章看懂iOS代碼塊Block

2: OC中, block(塊)的本質是什么?

3: Objective-C語法之代碼塊(block)的使用

4: Objective-C中的Block

5: Block技巧與底層解析

6: 黑幕背后的__block修飾符

7: 你真的理解__block修飾符的原理么?

8: iOS-Block的使用你看我啊

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

推薦閱讀更多精彩內容