前言
??之前寫過一篇block的文章,參考的源碼是libclosure-38的,跟libclosure-67有所區別,且由于之前理解不足文章有些細小錯誤,決定重新寫一篇。
??關于Block的文章各個博客各個論壇都已經數不勝數,每一篇都有自己獨特的特點和見解。但很多文章深度不夠,只是簡單的把一些源碼放上去,并未分析每一步操作的作用,比較不友好,只能看得一知半解。筆者剛看的時候也是一頭霧水,花費了很長時間查閱了各種資料才漸漸理解。鑒于此,決定寫一篇事無巨細的文章來剖析Block的每一個步驟,來幫助大家理解Block的奧秘。
??Block的使用這里就不再做過多解釋,下面會通過分析源碼來講解Block的實現、變量的捕獲以及__block的作用等。
Block
Block類型
??看一段代碼,以下在ARC下執行。
int main(int argc, const char * argv[]) {
@autoreleasepool {
int num = 1;
static int staticNum = 2;
char str[] = "hello";
void(^aBlock)(void) = ^(void) {
printf("%d\n", num);
};
void(^bBlock)(void) = ^(void) {
printf("%d\n", staticNum);
};
void(^cBlock)(char *) = ^(char *word) {
printf("%s\n", word);
};
aBlock();
bBlock();
cBlock(str);
NSLog(@"%@----%@----%@----%@", aBlock, bBlock, cBlock, ^{NSLog(@"%d", num);});
}
return 0;
}
打印結果如下:
<__NSMallocBlock__: 0x100607950>----<__NSGlobalBlock__: 0x100002118>--
--<__NSGlobalBlock__: 0x100002158>----<__NSStackBlock__: 0x7fff5fbff5c0>
??其中因為bBlock和cBlock只使用了靜態變量和入參,不需要捕獲外部變量,所以為全局Block __NSGlobalBlock__
,存在于全局區,內存在程序結束后由系統回收。最后一個使用了外部變量num,為棧Block__NSStackBlock__
,內存由編譯器控制,過了作用域就會被回收。而aBlock雖然也只使用了外部變量,但由于在ARC下會自動調用一次copy方法,將Block從棧區copy到堆區,所以aBlock為堆Block__NSMallocBlock__
,內存由ARC控制,沒有強指針指向時釋放。而在MRC中,賦值不會執行copy操作,所以aBlock依然會存在于棧中,所以在MRC中一般都需要執行copy,否則很容易造成crash。
??在ARC中,當Block作為屬性被strong、copy修飾或被強指針應用或作為返回值時,都會默認執行copy方法。而MRC中,只有被copy修飾時,Block才會執行copy。所以MRC中Block都需要用copy來修飾,而在ARC中用copy修飾只是沿用了MRC的習慣,此時用copy和strong效果是相同的。下面會講到copy的具體實現。
Block數據結構定義
??Block的定義在Block_private.h
中,點擊查看源碼。
// Values for Block_layout->flags to describe block objects
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
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
void (*copy)(void *dst, const void *src);
void (*dispose)(const void *);
};
#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
void (*invoke)(void *, ...);
struct Block_descriptor_1 *descriptor;
// imported variables
};
??用一張被用爛的圖,點擊查看原文。
??事實上該圖適用于libclosure-38,在最新的源碼中結構已經有所改變,但整體并未改變,只是在基礎上新增了
signature
和layout
,依然可以用其來理解。
- isa指針
指向父類的結構體,就是_NSConcreteStackBlock
,_NSConcreteMallocBlock
,_NSConcreteGlobalBlock
這幾個,說明OC本身也是一個對象。 - flags
就是上面那幾個枚舉,用來保留block的一些信息,用到的如下:
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime 用來標識棧Block
BLOCK_NEEDS_FREE = (1 << 24), // runtime 用來標識堆Block
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler compiler 含有copy_dispose助手
BLOCK_IS_GLOBAL = (1 << 28), // compiler 是否為全局Block
};
BLOCK_REFCOUNT_MASK、BLOCK_IS_GLOBAL和BLOCK_NEEDS_FREE用來標識Block類型,BLOCK_HAS_COPY_DISPOSE判斷Block是否有copy_dispose助手,即description2中的copy和dispose函數,用來管理捕獲對象的內存,下面會細講。
- reserved
保留信息 - invoke
函數指針,指向block具體的執行函數 - description
block附加描述信息,主要保存了內存size以及copy和dispose函數的指針及簽名和layout等信息,通過源碼可發現,layout中只包含了Block_descriptor_1,并未包含Block_descriptor_2和Block_descriptor_3,這是因為在捕獲不同類型變量或者沒用到外部變量時,編譯器會改變結構體的結構,按需添加Block_descriptor_2和Block_descriptor_3,所以才需要BLOCK_HAS_COPY_DISPOSE和BLOCK_HAS_SIGNATURE等枚舉來判斷 - variables capture的外部變量,如果Block中使用了外部變量,結構體中就會有相應的信息,下面會解釋。Block將使用的變量或者變量指針copy過來,內部才可以訪問。
Block_byref的結構定義
??上面介紹了Block也就是Block_layout
的源碼,Block_private.h
中還有定義了一個結構體Block_byref
,變量在被__block修飾時由編譯器來生成,結構如下:
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
};
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
void (*byref_keep)(struct Block_byref *dst, struct Block_byref *src);
void (*byref_destroy)(struct Block_byref *);
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
??當其結構與Block_layout
結構類似
- isa指針
指向父類,一般直接指向0,后面會說。 - forwarding指針
在棧中指向自己,Block執行copy后指向堆中的byref。 - flags
runtime中用到的如下:
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
BLOCK_BYREF_LAYOUT_EXTENDED表示含有layout,BLOCK_BYREF_HAS_COPY_DISPOSE表示byref中含有copy_dispose函數,在__block捕獲的變量為對象時就會生成copy_dispose函數用來管理對象內存,BLOCK_BYREF_NEEDS_FREE判斷是否要釋放。
- size
所占內存大小 - Block_byref_2、Block_byref_3
Block_byref_2和Block_byref_3用來保存附加信息
_NSConcreteGlobalBlock的實現
??使用Clang可以將OC轉成C++,就可以看到Block具體做了什么。clang -rewrite-objc filename,比如我需要轉換main.m中的代碼就可以寫clang -rewrite-objc main.m,注意路徑正確。轉換后的.cpp文件有10萬行,只需要看最后幾行。
??上面說到,在block內部不使用外部變量或者為靜態或者全局變量時,該block就是_NSConcreteGlobalBlock
,看下編譯后的結果。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
^{
printf("hello block");
}();
}
return 0;
}
Clang后
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) {
printf("hello block");
}
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;
}
??從上可以看到,__block_impl
、__main_block_impl_0
以及__main_block_desc_0
這三個結構體一起組成了整個Block,生成的結構體各個參數跟上面介紹的一致。編譯器將Block內的函數定義成一個獨立的函數__main_block_func_0
,在最后調用。
??不過有兩點問題:
- 1、該Block并未使用外部變量,應該是一個_NSConcreteGlobalBlock,而用clang顯示的卻是_NSConcreteStackBlock,唐巧的說法是:clang 改寫的具體實現方式和 LLVM 不太一樣。這點不是很清楚,如果有了解的可以釋疑一下。
- 2、把編譯后的代碼copy到mm中后,
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();
此行會crash,因為((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))
指向的是impl的isa,也就是_NSConcreteStackBlock的指針。需要改寫成struct __main_block_impl_0 impl_0 = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA); ((void (*)())(impl_0.impl.FuncPtr))();
才能執行成功。或許clang改寫的實現跟LLVM確實不一樣吧。
_NSConcreteStackBlock的實現
??如果block內部使用了普通的外部變量,就是_NSConcreteStackBlock
。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int a = 1;
NSObject *obj = [[NSObject alloc] init];
^{
obj, a;
};
}
return 0;
}
Clang后
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;
NSObject *obj;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int _a, int flags=0) : obj(_obj), 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) {
NSObject *obj = __cself->obj; // bound by copy
int a = __cself->a; // bound by copy
obj, a;
}
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*/);}
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;
int a = 1;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, a, 570425344));
}
return 0;
}
??該例在Block中同時使用了普通變量和實例變量,Block在捕獲外部變量的操作基本一致,都是在生成結構體的時候將所有Block里用到的外部變量作為屬性存起來。這就是為什么在self.block里調用self會造成循環引用,因為Block捕獲了self并把self當做一個值保存了起來。
??而且如果此時在Block里面改變a的值是會報錯,因為普通變量的值傳遞為復制,所有Block里的a只是copy過去的a的值,在Block里改變a的值也不會影響外面,編譯器避免這個錯誤就報錯。同樣的,捕獲對象時也是對指針的copy,生成一個指針指向obj對象,所以如果在Block中直接讓obj指針指向另外一個對象也會報錯。這點編譯器已經加了注釋// bound by copy。
??同時,該例與上例有所區別,多了__main_block_copy_0
和__main_block_dispose_0
這兩個函數,并在描述__main_block_desc_0
結構體中保存了這兩個函數指針。這就是上面所說的copy_dispose助手,C語言結構體中,編譯器很難處理對象的初始化和銷毀操作,所以使用runtime來管理相關內存。BLOCK_FIELD_IS_OBJECT
是在捕獲對象時添加的特別標識,此時是3,下面會細講。
??此Block是為棧Block_NSConcreteStackBlock
,不過在ARC中,因為賦值給aBlock,會執行一次copy,將其中棧中copy到堆中,所以在MRC中aBlock為_NSConcreteStackBlock
,在ARC中就是_NSConcreteMallocBlock
。
_NSConcreteMallocBlock && Block_copy()
??上面說到,ARC中生成的_NSConcreteStackBlock
在賦值給強指針時會默認執行一次copy變成_NSConcreteMallocBlock
,下面介紹下copy的實現。
??在Block.h
中定義了Block_copy的宏
#define Block_copy(...) ((__typeof(__VA_ARGS__))_Block_copy((const void *)(__VA_ARGS__)))
??其實現在runtime.h
中
void *_Block_copy(const void *arg) {
//1、
struct Block_layout *aBlock;
if (!arg) return NULL;
//2、
// The following would be better done as a switch statement
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;
}
else {
//5、
// Its a stack block. Make a copy.
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return NULL;
//6、
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
//7、
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
//8、
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
//9、
result->isa = _NSConcreteMallocBlock;
return result;
}
}
static int32_t latching_incr_int(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return BLOCK_REFCOUNT_MASK;
}
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
return old_value+2;
}
}
}
static void _Block_call_copy_helper(void *result, struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->copy)(result, aBlock); // do fixup
}
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
??以上就是_NSConcreteStackBlock
轉換成_NSConcreteMallocBlock
的過程,下面按步解釋每一步的作用。
- 1、先聲明一個
Block_layout
結構體類型的指針,如果傳入的Block為NULL就直接返回。 - 2、如果Block有值就強轉成
Block_layout
的指針類型。 - 3、如果Block的flags表明該Block為堆Block時,就對其引用計數遞增,然后返回原Block。
latching_incr_int
這個函數進行了一次死循環,如果flags含有BLOCK_REFCOUNT_MASK
證明其引用計數達到最大,直接返回,需要三萬多個指針指向,正常情況下不會出現。隨后做一次原子性判斷其值當前是否被其他線程改動,如果被改動就進入下一次循環直到改動結束后賦值。OSAtomicCompareAndSwapInt
的作用就是在where
取值與old_value
相同時,將old_value+2
賦給where
。 注:Block的引用計數以flags的后16位代表,以 2為單位,每次遞增2,1被BLOCK_DEALLOCATING
正在釋放占用。 - 4、如果Block為全局Block就不做其他處理直接返回。
- 5、該else中就是棧Block了,按原Block的內存大小分配一塊相同大小的內存,如果失敗就返回NULL。
- 6、memmove()用于復制位元,將aBlock的所有信息copy到result的位置上。
- 7、將新Block的引用計數置零。
BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING
就是0xffff
,~(0xffff)
就是0x0000
,result->flags
與0x0000
與等就將result->flags
的后16位置零。然后將新Block標識為堆Block并將其引用計數置為2。 - 8、如果有copy_dispose助手,就執行Block的保存的copy函數,就是上面的
__main_block_copy_0
。在_Block_descriptor_2
函數中,用BLOCK_HAS_COPY_DISPOSE
來判斷是否有Block_descriptor_2
,且取Block的Block_descriptor_2
時,因為有無是編譯器確定的,在Block結構體中并無保留,所以需要使用指針相加的方式來確定其指針位置。有就執行,沒有就return掉。 - 9、將堆Block的isa指針置為
_NSConcreteMallocBlock
,返回新Block,end。
??因為在ARC中的賦值的自動copy,所以:
NSObject *obj = [[NSObject alloc] init];
void(^aBlock)() = ^{
obj;
};
NSLog(@"%ld", CFGetRetainCount((__bridge void *)obj));
??其打印結果為3,因為Block在生成棧Block捕獲外部變量的時候會生成一份指針指向obj,在將Block賦值給aBlock時從棧中copy到堆區中,又持有了一份。
Block_release()
??有Block_copy()
就有對應的Block_release()
,其在Block.h
中的定義如下:
#define Block_release(...) _Block_release((const void *)(__VA_ARGS__))
??其實現在runtime.h
中
void _Block_release(const void *arg) {
//1、
struct Block_layout *aBlock = (struct Block_layout *)arg;
if (!aBlock) return;
//2、
if (aBlock->flags & BLOCK_IS_GLOBAL) return;
//3、
if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;
//4、
if (latching_decr_int_should_deallocate(&aBlock->flags)) {
//5、
_Block_call_dispose_helper(aBlock);
//6、
_Block_destructInstance(aBlock);
//7、
free(aBlock);
}
}
static bool latching_decr_int_should_deallocate(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return false; // latched high
}
if ((old_value & BLOCK_REFCOUNT_MASK) == 0) {
return false; // underflow, latch low
}
int32_t new_value = old_value - 2;
bool result = false;
if ((old_value & (BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING)) == 2) {
new_value = old_value - 1;
result = true;
}
if (OSAtomicCompareAndSwapInt(old_value, new_value, where)) {
return result;
}
}
}
static void _Block_call_dispose_helper(struct Block_layout *aBlock)
{
struct Block_descriptor_2 *desc = _Block_descriptor_2(aBlock);
if (!desc) return;
(*desc->dispose)(aBlock);
}
static void _Block_destructInstance_default(const void *aBlock __unused) {}
static void (*_Block_destructInstance) (const void *aBlock) = _Block_destructInstance_default;
- 1、將入參arg強轉成
(struct Block_layout *)
類型,如果入參為NULL則直接返回。 - 2、如果入參為全局Block則返回不做處理。
- 3、如果入參不為堆Block則返回不做處理。
- 4、判斷aBlock的引用計數是否需要釋放內存。與copy同樣的,
latching_decr_int_should_deallocate
也做了一次循環和原子性判斷保證原子性。如果該block的引用計數過高(0xfffe)或者過低(0)返回false不做處理。如果其引用計數為2,則將其引用計數-1即BLOCK_DEALLOCATING
標明正在釋放,返回true,如果大于2則將其引用計數-2并返回false。 - 5、如果步驟4標明了該block需要被釋放,就進入步驟5。如果aBlock含有copy_dispose助手就執行aBlock中的dispose函數,與copy中的對應不再多做解釋。
- 6、
_Block_destructInstance
默認沒做其他操作,可見源碼。 - 7、最后一步釋放掉aBlock,end。
__block的作用及實現
??前面說到,Block捕獲的變量不允許在內部改變指針指向,因為內部的對象是重新copy的一份指針或者普通變量值。如果需要改變普通變量的值或指針的指向,這個時候就需要用到__block。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
__block int a = 1;
__block NSObject *obj = [[NSObject alloc] init];
^{
a++;
obj;
}();
}
return 0;
}
Clang后
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);
}
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __Block_byref_obj_1 {
void *__isa;
__Block_byref_obj_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__Block_byref_obj_1 *obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_obj_1 *_obj, int flags=0) : a(_a->__forwarding), obj(_obj->__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
__Block_byref_obj_1 *obj = __cself->obj; // bound by ref
(a->__forwarding->a)++;
(obj->__forwarding->obj);
}
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*)&dst->obj, (void*)src->obj, 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*/);_Block_object_dispose((void*)src->obj, 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), 1};
__attribute__((__blocks__(byref))) __Block_byref_obj_1 obj = {(void*)0,(__Block_byref_obj_1 *)&obj, 33554432, sizeof(__Block_byref_obj_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_obj_1 *)&obj, 570425344))();
}
return 0;
}
??可以看到加了__block的a和obj已經被編譯成了兩個結構體__Block_byref_a_0
和__Block_byref_obj_1
,這兩個就是上面所說的Block_byref
??梢钥吹狡鸾Y構大同小異,都有__isa指針指向0,__forwarding指針指向自己,__flags用來標記,__size用來保存內存大小,a和obj指向最初的變量。可以發現,對象的結構體多了兩個方法用來處理obj的內存。注釋也變成了// bound by ref,這時候就可以修改變量a和對指針obj重新賦值。對象結構體比普通變量多了兩個函數,用來處理NSOject的內存。
??上面說Block捕獲的對象會持有該對象,使其引用計數增加,然后看下__block修飾的對象會怎樣。
__block NSObject *obj = [[NSObject alloc] init];
NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
^{
NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
}();
NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
打印結果如下:
1---0x60c000007d50---0x7fff5e913c28
1---0x60c000007d50---0x7fff5e913c28
1---0x60c000007d50---0x7fff5e913c28
??可以看到,在棧Block中,自始只有強指針指向obj,就是__block生成的結構體。
??然后看下在copy到堆中的效果:
__block NSObject *obj = [[NSObject alloc] init];
NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
void(^aBlock)() = ^{
NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
};
aBlock();
NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
結果如下:
1---0x604000012ca0---0x7fff50631c28
1---0x604000012ca0---0x600000058c28
1---0x604000012ca0---0x600000058c28
??可以看到,obj的內存地址一直在棧中,而執行了BlockCopy后,obj指針的地址從棧變到了堆中,而obj的引用計數一直是1。在copy操作之后,結構體obj也被復制到了堆中,并斷開棧中的obj結構體對obj對象的指向。那如果這個時候取棧中的obj不就有問題了?__forwarding就派上用場了,上面編譯的結果發現,結構體對象在使用obj的時候會使用obj->__forwarding->obj,如果所有__forwarding都指向自己,這一步還有什么意義?棧Block在執行copy操作后,棧obj結構體的__forwarding就會指向copy到堆中的obj結構體。此時再取值,操作的就是同一份指針了。證明如下:
__block NSObject *obj = [[NSObject alloc] init];
NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
void(^aBlock)() = ^{
NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
};
aBlock();
void(^bBlock)() = [aBlock copy];
bBlock();
aBlock();
NSLog(@"%ld---%p---%p", CFGetRetainCount((__bridge void *)obj), obj, &obj);
注:該段在MRC下執行,否則棧Block賦值給aBlock就會默認執行copy操作變成堆block。
打印結果:
1---0x1006229d0---0x7fff5fbff6b8
1---0x1006229d0---0x7fff5fbff6b8
1---0x1006229d0---0x100508c98
1---0x1006229d0---0x100508c98
1---0x1006229d0---0x100508c98
??由上可知,在copy之前,aBlock的打印結果都是初始化生成的指針,而copy之后打印就和bBlock的打印結果相同了。總結一下就是,在棧中的obj結構體__forwarding指向的就是棧中的自己,執行copy之后,會在堆中生成一份obj結構體并斷開棧中對obj的引用,此時堆中的obj結構體__forwarding就指向自己,而棧中的__forwarding就指向堆中的obj結構體。下面也會通過分析源碼來具體解釋。
_Block_object_assign
??雖然上面已經講了很多,但細心的同學可以發現,在Clang后的代碼中還有兩個沒有講到,就是編譯器生成的copy_dispose代碼中調用的兩個用來管理對象內存的函數_Block_object_assign
和_Block_object_dispose
。
??然后先講_Block_object_assign
。大家可以在Block.h
中看到:
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
??其實現在runtime.c
中,其中用到很多枚舉,如果不懂其含義,很難理解其上面的代碼,所以在此之前先解釋下各種枚舉所代表的意思。
??其枚舉定義在Block_private.h
中:
// Values for _Block_object_assign() and _Block_object_dispose() parameters
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_FIELD_IS_BLOCK = 7, // a block variable
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
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.
};
enum {
BLOCK_ALL_COPY_DISPOSE_FLAGS =
BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_BYREF |
BLOCK_FIELD_IS_WEAK | BLOCK_BYREF_CALLER
};
-
BLOCK_FIELD_IS_OBJECT
OC對象類型; -
BLOCK_FIELD_IS_BLOCK
為另一個block; -
BLOCK_FIELD_IS_BYREF
為一個被__block修飾后生成的結構體; -
BLOCK_FIELD_IS_WEAK
被__weak修飾過的弱引用,只在Block_byref
管理內部對象內存時使用,也就是__block __weak id
; -
BLOCK_BYREF_CALLER
在處理Block_byref
內部對象內存的時候會加的一個額外標記,配合上面的枚舉一起使用; -
BLOCK_ALL_COPY_DISPOSE_FLAGS
上述情況的整合,即以上都會包含copy_dispose助手。
??現在再看_Block_object_assign
:
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
//1.
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
//2.
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
//3.
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
//4.
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
//5.
*dest = object;
break;
default:
break;
}
}
static void _Block_retain_object_default(const void *ptr __unused) { }
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
static struct Block_byref *_Block_byref_copy(const void *arg) {
//3.1
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
//3.2
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
//3.3
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
//3.4
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
//3.5
copy->size = src->size;
if (src->flags & BLOCK_BYREF_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
//3.6
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
//3.7
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
//3.8
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
//3.9
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
//3.10
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
//3.11
latching_incr_int(&src->forwarding->flags);
}
//3.12
return src->forwarding;
}
??destArg為執行Block_copy()
后的block中的對象、block、或者BYREF指針的指針,obj為copy之前的變量指針。
- 1、當block捕獲的變量為OC對象時執行此步,ARC中引用計數由強指針來確定,所以
_Block_retain_object
默認是不做任何操作,只進行簡單的指針賦值。 - 2、當block捕獲的變量為另外一個block時執行此步,copy一個新的block并賦值。
- 3、當block捕獲的變量為__block修飾的變量時會執行此步,執行byref_copy操作。
3.1、強轉入參為(struct Block_byref *)
類型。
3.2、當入參為棧byref時執行此步。分配一份與當前byref相同的內存,并將isa指針置為NULL。
3.3、將新byref的引用計數置為4并標記為堆,一份為調用方、一份為棧持有,所以引用計數為4。
3.4、然后將當前byref和malloc的byref的forwading都指向堆byref,然后操作堆棧都是同一份東西。
3.5、最后將size賦值
3.6、如果byref含有需要內存管理的變量即有copy_dispose助手,執行此步。分別取出src的Block_byref_2
和copy的Block_byref_2
。
3.7、將src的管理內存函數指針賦值給copy。
3.8、如果src含有Block_byref_3
,則將src的Block_byref_3
賦值給copy。
3.9、執行byref的byref_keep
函數(即_Block_object_assign
,不過會加上BLOCK_BYREF_CALLER
標記),管理捕獲的對象內存。
3.10、如果捕獲的是普通變量,就沒有Block_byref_2
,copy+1和src+1指向的就是Block_byref_3
,執行字節拷貝。
3.11、如果該byref是存在于堆,則只需要增加其引用計數。
3.12、返回forwarding。 - 4、如果管理的是__block修飾的對象或者block的內存會執行此步,直接進行指針賦值。
- 5、同時被__weak和__block修飾的對象或者block執行此步,也是直接進行指針賦值。
_Block_object_dispose
??與_Block_object_assign
對應的。
void _Block_object_dispose(const void *object, const int flags) {
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
// get rid of the __block data structure held in a Block
//1.
_Block_byref_release(object);
break;
case BLOCK_FIELD_IS_BLOCK:
//2.
_Block_release(object);
break;
case BLOCK_FIELD_IS_OBJECT:
//3.
_Block_release_object(object);
break;
//4.
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
break;
default:
break;
}
}
static void _Block_byref_release(const void *arg) {
//1.1
struct Block_byref *byref = (struct Block_byref *)arg;
// dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
byref = byref->forwarding;
if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
//1.2
int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
os_assert(refcount);
if (latching_decr_int_should_deallocate(&byref->flags)) {
//1.3
if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
//1.4
struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
(*byref2->byref_destroy)(byref);
}
//1.5
free(byref);
}
}
}
static void _Block_release_object_default(const void *ptr __unused) { }
static void (*_Block_release_object)(const void *ptr) = _Block_release_object_default;
- 1、如果需要管理的變量為byref,則執行該步。
1.1、將入參強轉成(struct Block_byref *)
,并將入參替換成forwarding。
1.2、如果該byref在堆上執行此步,如果該byref還被標記為棧則執行斷言。
1.3、此函數上面有講就不多提,判斷是否需要釋放內存。
1.4、如果有copy_dispose助手就執行byref_destroy管理捕獲的變量內存。
1.5、釋放byref。 - 2、如果block則調用
_Block_release
釋放block,上面有講。 - 3、如果是OC對象就進行release,默認沒有做操作,由ARC管理。
- 4、如果是其他就不做處理,__block修飾的變量只有一個強指針引用。
測試
??如果這么長都看完了,想必你已經對block有了深刻的認識,來波測試吧(本文只有此測試是抄的)。下面的選項均為:A、ARC可以正常執行。B、ARC和MRC均可以正常執行。C、均不可正常執行。
Example A
void exampleA() {
char a = 'A';
^{
printf("%c\n", a);
}();
}
Example B
void exampleB_addBlockToArray(NSMutableArray *array) {
char b = 'B';
[array addObject:^{
printf("%c\n", b);
}];
}
void exampleB() {
NSMutableArray*array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void(^block)() = [array objectAtIndex:0];
block();
}
Example C
void exampleC_addBlockToArray(NSMutableArray *array) {
[array addObject:^{
printf("C\n");
}];
}
void exampleC() {
NSMutableArray *array = [NSMutableArray array];
exampleC_addBlockToArray(array);
void(^block)() = [array objectAtIndex:0];
block();
}
Example D
typedef void(^dBlock)();
dBlock exampleD_getBlock() {
char d = 'D';
return^{
printf("%c\n", d);
};
}
void exampleD() {
exampleD_getBlock()();
}
Example E
typedef void(^eBlock)();
eBlock exampleE_getBlock() {
char e = 'E';
void(^block)() = ^{
printf("%c\n", e);
};
return block;
}
void exampleE() {
eBlock block = exampleE_getBlock();
block();
}
??答案?不存在的!看在辛苦碼這么字的份上,先star一發我就發咯。