摘要:
我們在開發(fā)的過程中經(jīng)常使用到block,不僅如此,Apple的api里面也有很多使用到block,好比gcd里面也是大量使用了block。block在語法上來說比較簡潔,不過還是需要注意不要引起了循環(huán)引用。
裸block:
我們先來看看最基本的block編譯之后長啥樣:
typedef void (^Block)();
int main(int argc, const char * argv[]) {
@autoreleasepool
{
Block b = ^(){
printf("Hello Gay");
};
}
return 0;
}
我們使用 clang-rewrite-objc filename 指令編譯一下到底是什么鬼
struct __block_impl {
void *isa; //什么類型的block
int Flags; //block的一些附加信息杈女,里面包含了何種類型的block
//以及引用計數(shù),下面講到copy的時候就能知道到底是干嘛的了
int Reserved; //保留位
void *FuncPtr; //函數(shù)指針
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc; //block的描述
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,int flags=0)
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Hello Gay");
}
static struct __main_block_desc_0
{
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
{
__AtAutoreleasePool __autoreleasepool;
Block b = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
}
return 0;
}
我們可以看到聲明的block以結(jié)構(gòu)體的方式進(jìn)行了存儲吊圾,從側(cè)面上來說达椰,block是一個指向結(jié)構(gòu)體的指針。其中__block_impl結(jié)構(gòu)體記錄的是block的相關(guān)信息包括block的類型以及引用計數(shù)等项乒。
block里面捕獲變量之后
我們假設(shè)在block里面捕獲了一個變量啰劲,看看內(nèi)部會變成什么樣?
typedef void (^Block)();
int main(int argc, const char * argv[]) {
@autoreleasepool
{
int i = 0;
Block b = ^(){
printf("%d",i);
};
}
return 0;
}
同樣clang編譯之后
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int i; //相對于未捕獲變量的block來說檀何,多了一個變量來保存值
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
int i = __cself->i; // bound by copy
printf("%d",i);
}
int main(int argc, const char * argv[])
{
{
__AtAutoreleasePool __autoreleasepool;
int i = 0;
Block b = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, i));
//注意這里的i傳的是值蝇裤,因此意味著在當(dāng)我們在外部修改這個值的時候,你調(diào)用block打印出來的時候频鉴,這個值依舊是之前的值
}
return 0;
}
乍一看栓辜,好像跟剛才的大差不差,__main_block_impl_0里面產(chǎn)生了一個變量用來保存之前捕獲的值垛孔。
block捕獲可修改的變量之后
貼上low B代碼
typedef void (^Block)();
int main(int argc, const char * argv[]) {
@autoreleasepool
{
__block int i = 0;
Block b = ^(){
printf("%d",i);
};
}
return 0;
}
我們再次編譯一下??
struct __Block_byref_i_0 {
void *__isa; //什么類型的數(shù)據(jù)
__Block_byref_i_0 *__forwarding; //這個到copy的時候就用得著了藕甩,主要是保證copy之后能夠找到在堆上的那個變量
int __flags;//變量的引用計數(shù)
int __size;
int i;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref 這里跟之前不一樣了,這次是用指針來保存的
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding)
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
__Block_byref_i_0 *i = __cself->i; // bound by ref
printf("%d",(i->__forwarding->i));
}
//輔助block copy的時候似炎,對捕獲變量的存儲方案
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
//輔助block release的時候辛萍,對捕獲變量的釋放策略
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
//這里面添加copy和dispose兩個函數(shù)
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
{ __AtAutoreleasePool __autoreleasepool;
//這里將結(jié)構(gòu)體i的__forwarding指針指向了自身
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
Block b = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
//570425344干嘛的?后面識別block類型的時候就能用上了
}
return 0;
}
通過編譯我們可以看到羡藐,block的描述里面多了兩個函數(shù)(關(guān)于copy和release)
Block_copy的實現(xiàn)
之前的熱身內(nèi)容是為了讓大家思路更加清(meng)晰(bi)。
我們可以在編譯器上使用Block_copy()或者在Block.h里面查看到關(guān)于Block_copy的定義
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
在runtime.c中_Block_copy函數(shù)以這種方式實現(xiàn)了
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, WANTS_ONE);
}
該函數(shù)內(nèi)部調(diào)用了_Block_copy_internal,同樣我們在runtime.c中能夠查看到該函數(shù)的實現(xiàn)方式
先貼上幾個block類型的枚舉,在這里能夠看到Block_private.h
enum{
BLOCK_REFCOUNT_MASK = (0xffff),
BLOCK_NEEDS_FREE = (1 << 24), //堆block
BLOCK_HAS_COPY_DISPOSE = (1 << 25),
BLOCK_HAS_CTOR = (1 << 26), */* Helpers have C++ code. */*
BLOCK_IS_GC = (1 << 27),
BLOCK_IS_GLOBAL = (1 << 28), //全局block
BLOCK_HAS_DESCRIPTOR = (1 << 29)
};
_Block_copy_internal實現(xiàn)如下
static void *_Block_copy_internal(const void *arg, const int flags)
{
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
if (!arg) return NULL;
aBlock = (struct Block_layout *)arg;
// 還記得上面的那個初始化bloc時候賦值給flags標(biāo)志位的570425344嗎
// 570425344 & (1 << 24) = 0 不滿足跳過
if (aBlock->flags & BLOCK_NEEDS_FREE) {
latching_incr_int(&aBlock->flags);
return aBlock;
}
//這邊也是不滿足條件的
else if (aBlock->flags & BLOCK_IS_GC) {
//此處省略若干行代碼悯许。仆嗦。。
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL)
{
return aBlock;
}
//最后來到這
//isGC在runtime.c文件里面能夠找到先壕,該變量被初始化為false
// Its a stack block. Make a copy.
// 對棧block的copy
if (!isGC) {
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
//這里是重點給flags標(biāo)識為加上BLOCK_NEEDS_FREE標(biāo)識位
//同時增加了一個引用計數(shù)
result->flags |= BLOCK_NEEDS_FREE | 1;
//修改isa指針
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE)
{
//如果存在copy的輔助函數(shù)瘩扼,會調(diào)用該輔助函數(shù)谆甜,
//當(dāng)捕獲了引用的時候,顯然是滿足條件的集绰。
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
else {
//此處省略规辱。。栽燕。
}
}
至此罕袋,相信都知道了copy是怎么樣一個過程了。當(dāng)對一個堆上的block再次進(jìn)行調(diào)用copy的時候碍岔,因為我們之前給flags打入了BLOCK_NEEDS_FREE這個值浴讯,所以最后走的是這個判定條件
if (aBlock->flags & BLOCK_NEEDS_FREE) {
latching_incr_int(&aBlock->flags);
return aBlock;
}
結(jié)果就是增加引用計數(shù),然后返回該block蔼啦。
輔助copy/dispose函數(shù)
1.普通變量的copy
在前面我們說到榆纽,如果有block復(fù)制到堆上的時候,有copy輔助函數(shù)的捏肢,該函數(shù)會被調(diào)用奈籽。以__block int i = 0為例子生成的輔助函數(shù)如下
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);
}
在此之前,先賦上block中copy輔助函數(shù)鸵赫,flags標(biāo)識位能夠支持的類型
enum {
BLOCK_FIELD_IS_OBJECT = 3, /* id, NSObject, __attribute__((NSObject)), block, ... */
BLOCK_FIELD_IS_BLOCK = 7, /* a block variable */
BLOCK_FIELD_IS_BYREF = 8, // __block修飾的基本數(shù)據(jù)類型
BLOCK_FIELD_IS_WEAK = 16, /* declared __weak, only used in byref copy helpers */
BLOCK_BYREF_CALLER = 128 /* called from __block (byref) copy/dispose support routines. */
};
在runtime.c里面_Block_object_assign函數(shù)的實現(xiàn)方式如下
void _Block_object_assign(void *destAddr, const void *object, const int flags)
{
...
此處省略部分代碼
...
//之前的生成的輔助函數(shù),flags = 8 => BLOCK_FIELD_IS_BYREF
else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)
{
//在這里調(diào)用了_Block_byref_assign_copy唠摹,看下面的那個函數(shù)
_Block_byref_assign_copy(destAddr, object, flags);
}
...
此處省略部分代碼
...
}
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
struct Block_byref **destp = (struct Block_byref **)dest;
struct Block_byref *src = (struct Block_byref *)arg;
if (src->forwarding->flags & BLOCK_IS_GC) {
; // don't need to do any more work
} else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
//當(dāng)初次拷貝時,flags為0奉瘤,進(jìn)入此分支會進(jìn)行復(fù)制操作并改變flags值勾拉,置入BLOCK_NEEDS_FREE和初始的引用計數(shù)
bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // _Byref_flag_initial_value
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (isWeak) {
copy->isa = &_NSConcreteWeakBlockVariable; // mark isa field so it gets weak scanning
}
// 當(dāng)發(fā)現(xiàn)對象存在輔助的copy函數(shù)的時候,把copy函數(shù)的賦值給在堆上的對象
if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
copy->byref_keep = src->byref_keep;
copy->byref_destroy = src->byref_destroy;
(*src->byref_keep)(copy, src);
}
else {
// just bits. Blast 'em using _Block_memmove in case they're __strong
_Block_memmove(
(void *)©->byref_keep,
(void *)&src->byref_keep,
src->size - sizeof(struct Block_byref_header));
}
}
// already copied to heap
//當(dāng)再次拷貝對象時盗温,則僅僅增加其引用計數(shù)
else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
// 將原來對象的forwarding 指向現(xiàn)在在堆上的對象
_Block_assign(src->forwarding, (void **)destp);
}
上面就是一個被__block修飾的基本數(shù)據(jù)類型拷貝到堆上的時候藕赞,copy函數(shù)的實際調(diào)用過程。
普通oc對象的復(fù)制
以捕獲NSObject對象為栗子
輔助函數(shù)如下:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
同樣是調(diào)用_Block_object_assign這個函數(shù)卖局,不過最終走的是這個
if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT)
{
_Block_retain_object(object);
_Block_assign((void *)object, destAddr);
}
把該對象retain之后斧蜕,然后再賦值,這也就是說為什么block里面的對象需要使用weak的原因砚偶。
__block修飾的oc對象的復(fù)制
還是以NSObject為栗子批销,輔助函數(shù)如下:
//131即為BLOCK_FIELD_IS_OBJECT|BLOCK_BYREF_CALLER
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
同樣是調(diào)用_Block_object_assign這個函數(shù),最終走的是這個
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);
}
}
門面上看起來似乎沒有retain住被__block修飾的對象染坯,實際上在這里:
struct __Block_byref_test_0 {
void *__isa;
__Block_byref_test_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *object; (在ARC下均芽,這里retain住了對象,所以說__block在ARC下并不能解決循環(huán)引用問題单鹿,但是在MRC下掀宋,此處相當(dāng)于__unsafe_unretained, 在MRC下可以解決問題)
};
小結(jié)
通過上面的分析,相信大家對block有了更加清晰的理解。??
如果你看完以上內(nèi)容覺得so easy劲妙,那么你可能是大神湃鹊。
如果你看完以上內(nèi)容覺得啥玩意,那么可能是我寫的太渣了镣奋。
如果文章中币呵,有錯誤的地方歡迎大家提出指正。
附錄
本文參考了以下兩篇帖子侨颈,特此奉上:
沒事蹦蹦的Block實現(xiàn)原理
BobooO的iOS中block介紹(四)揭開神秘面紗(下)