Block
是iOS
開發(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_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
就是block
的C++
實現(xiàn)(最后面的_0
代表是main
中的第幾個block
)呜投,__main_block_func_0
是block
的代碼塊,__main_block_desc_0
是block
的描述存璃,__block_impl
是block
的定義。
__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)存段分布圖如下:
其中淌友,_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;
}
可以看出, globalBlock
的 isa
指向了 _NSConcreteGlobalBlock
你雌,即在全局區(qū)域創(chuàng)建器联,編譯時其具體代碼在代碼段中,block
變量則存儲在全局數(shù)據(jù)區(qū)婿崭;而stackBlock
的 isa
則指向了 _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)(拷貝的是封裝后的對象)癞尚,從而會有 copy
和 dispose
處理函數(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對象進行retain
或copy
;以下為_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