參考文檔1:BlocksRuntime/runtime.c
參考文檔2:BlocksRuntime/Block_private.h
參考文檔3:BlocksRuntime/Block.h
理解block的拷貝只是開(kāi)始,
1.block(int any)
,block(NSString * any)
,block(__block int any)
,block(__block NSString * any)
真的就這四類block
嗎?
2.iOS Block Part6所提到的block拷貝是棧block拷貝生成堆block的過(guò)程,那么對(duì)全局block進(jìn)行拷貝會(huì)有什么效果?對(duì)堆block進(jìn)行拷貝又會(huì)有什么效果?block的內(nèi)存到底如何管理?
3.block如何造成循環(huán)引用?
4.如何避免?為什么用weak
修飾的self
就不會(huì)產(chǎn)生循環(huán)引用?
......諸如此類的問(wèn)題.
本系列文章也只是拋磚引玉,很多東西,還得自己去看去理解!
1. 四小類block,其實(shí)有些狹隘
/*******************************************************
Entry points used by the compiler - the real API!
A Block can reference four different kinds of things that require help when the Block is copied to the heap.
1) C++ stack based objects
2) References to Objective-C objects
3) Other Blocks
4) __block variables
In these cases helper functions are synthesized by the compiler for use in Block_copy and Block_release, called the copy and dispose helpers. The copy helper emits a call to the C++ const copy constructor for C++ stack based objects and for the rest calls into the runtime support function _Block_object_assign. The dispose helper has a call to the C++ destructor for case 1 and a call into _Block_object_dispose for the rest.
The flags parameter of _Block_object_assign and _Block_object_dispose is set to
* BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
* BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
* BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.
If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16).
So the Block copy/dispose helpers should only ever generate the four flag values of 3, 7, 8, and 24.
When a __block variable is either a C++ object, an Objective-C object, or another Block then the compiler also generates copy/dispose helper functions. Similarly to the Block copy helper, the "__block" copy helper (formerly and still a.k.a. "byref" copy helper) will do a C++ copy constructor (not a const one though!) and the dispose helper will do the destructor. And similarly the helpers will call into the same two support functions with the same values for objects and Blocks with the additional BLOCK_BYREF_CALLER (128) bit of information supplied.
So the __block copy/dispose helpers will generate flag values of 3 or 7 for objects and Blocks respectively, with BLOCK_FIELD_IS_WEAK (16) or'ed as appropriate and always 128 or'd in, for the following set of possibilities:
__block id 128+3
__weak block id 128+3+16
__block (^Block) 128+7
__weak __block (^Block) 128+7+16
The implementation of the two routines would be improved by switch statements enumerating the eight cases.
以上是BlocksRuntime/runtime.c內(nèi)的一段話.
很容易看出,block捕獲的外圍參數(shù)絕不僅限于int any,NSString * any,__block int any,__block NSString * any
這四種.
本系列文章未深入解析這部分內(nèi)容的原因是:文字陳述功力有限,不敢越雷池.
就以上面提到的3) Other Blocks
為例子:
block捕獲block,這內(nèi)存關(guān)系,臣妾做不到啊!所以筆者只寫出基本的四類.想了解更復(fù)雜類型block
的內(nèi)存關(guān)系,得自己對(duì)block
的原理有了一定了解后,自己再看源碼.
2.block的內(nèi)存管理
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, WANTS_ONE);
}
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
//printf("_Block_copy_internal(%p, %x)\n", arg, flags);
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// Its a stack block. Make a 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
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;
}
}
回到block拷貝調(diào)用的_Block_copy_internal
,前篇文章已經(jīng)說(shuō)了,棧block拷貝生成堆block
只用了這個(gè)方法內(nèi)的部分代碼.
- 對(duì)全局block進(jìn)行拷貝會(huì)怎么樣
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
返回原來(lái)的block
- 對(duì)堆block進(jìn)行拷貝會(huì)怎么樣
堆block的flags為什么是BLOCK_NEEDS_FREE,棧block拷貝生成堆block的時(shí)候有賦值,很容易看明白,不多解釋
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
latching_incr_int
加引用計(jì)數(shù).
在看block的結(jié)構(gòu):
//Block_private.h內(nèi)Block_layout的結(jié)構(gòu):
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
//編譯的結(jié)構(gòu):
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_layout = __main_block_impl_0與__block_impl的合二為一
既有isa
又有引用計(jì)數(shù)
(記在flags內(nèi)),所以block也是一個(gè)對(duì)象.block是存在還是銷毀全看block的引用計(jì)數(shù)
.
有_Block_copy
,就有_Block_release
.
有latching_incr_int
,就有latching_decr_int
.
相生相克,無(wú)休無(wú)止,有興趣可以在BlocksRuntime/runtime.c里看看.
3.造成循環(huán)引用
3.1引用對(duì)象
NSString * any = [NSString stringWithFormat:@"1"];
void (^test)() = ^ {
NSLog(@"%@",any);
};
test();
block捕獲參數(shù)的拷貝方法
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
.
.
.
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
//printf("retaining object at %p\n", object);
_Block_retain_object(object);//變量加引用計(jì)數(shù)
//printf("done retaining object at %p\n", object);
_Block_assign((void *)object, destAddr);
}
}
由上圖很明顯能看出,由block
編譯生成的結(jié)構(gòu)體__main_block_impl_0
是如何持有NSString * any
的.
再加上代碼,我也能看到,持有變量的引用計(jì)數(shù)相應(yīng)加1.
所以block
銷毀,NSString * any
才有可以釋放.
3.2循環(huán)引用
以ViewController
內(nèi)使用block
來(lái)說(shuō)明問(wèn)題.
一概而論,block
寫了self
就會(huì)造成循環(huán)引用肯定是錯(cuò)的.
比如在ViewController
寫了如下代碼:
[UIView animateWithDuration:0.5 animations:^{
[self doSomethings];
}];
無(wú)論block
內(nèi)的代碼執(zhí)行與否.ViewController
的銷毀都會(huì)正常.
那怎么才會(huì)造成循環(huán)引用呢?
所謂循環(huán)引用
多數(shù)情況下都是多方在內(nèi)存上構(gòu)成了一個(gè)引用閉環(huán)
.
- 持有型Block
在日常開(kāi)發(fā)中,用Block
做消息傳遞十分常見(jiàn).請(qǐng)看一下代碼:
@interface ZCOnView : UIView
@property(nonatomic,copy)void (^clickMarkBlock)(BOOL isMarked);
@end
ZCOnView * any = [[ZCOnView alloc]initWithFrame:CGRectMake(64.0, 64.0, 64.0, 64.0)];
any.backgroundColor = [UIColor redColor];
any.clickMarkBlock = ^(BOOL isMarked){
[self doSomethings];
};
[self.view addSubview:any];
VC與UI控件關(guān)系不必多言--強(qiáng)
Block被UI控件持有,UI控件存在,Block也就存在--強(qiáng)
Block持有VC--強(qiáng)
VC
/ \
/ \
UI控件------Block
引用閉環(huán)形成.
在以上代碼的Block
內(nèi)寫上self
(或者用了成員變量),肯定會(huì)造成循環(huán)引用.所以我稱這種Block
為持有型Block
.
- 使用型Block
再反觀代碼:
[UIView animateWithDuration:0.5 animations:^{
[self doSomethings];
}];
VC在堆上
Block在堆上
Block執(zhí)行完畢,Block銷毀==>Block不持有VC.
沒(méi)有形成引用閉環(huán)
,當(dāng)然就沒(méi)有循環(huán)引用.
我們姑且稱這類block
為使用型Block
吧!
Block可以分為持有型Block+使用型Block.持有型Block內(nèi)部肯定不能用self.而使用型Block內(nèi)部可以放心的使用self.才怪!!!(?? 使用型Block內(nèi)部用self,雖然不會(huì)造成循環(huán)引用,但會(huì)有Block使用時(shí)的另一個(gè)問(wèn)題==>延遲.本文就不細(xì)說(shuō)了.想了解請(qǐng)戳)
4.避免循環(huán)引用
4.1 ARC環(huán)境下
區(qū)分Block
類型再看看能不能用self
.如果覺(jué)得麻煩,一竿子打死也行,全用__weak __typeof(&*self)weakSelf = self
也可以.
那么weak
修飾的變量又是如何躲過(guò)循環(huán)引用的呢?
環(huán)境:ARC
命令:clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations AnyThing.m
- strong self
#import <Foundation/Foundation.h>
@interface AnyThing : NSObject
@end
#import "AnyThing.h"
@implementation AnyThing
- (void)someAct{
void (^test)() = ^ {
NSLog(@"%@",self);
};
test();
}
@end
// @implementation AnyThing
struct __AnyThing__someAct_block_impl_0 {
struct __block_impl impl;
struct __AnyThing__someAct_block_desc_0* Desc;
AnyThing *const __strong self;//<<<不同
__AnyThing__someAct_block_impl_0(void *fp, struct __AnyThing__someAct_block_desc_0 *desc, AnyThing *const __strong _self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __AnyThing__someAct_block_func_0(struct __AnyThing__someAct_block_impl_0 *__cself) {
AnyThing *const __strong self = __cself->self; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qp_p2pj3jmj65n39jgl4wx9_l9w0000gn_T_AnyThing_51e757_mi_0,self);
}
static void __AnyThing__someAct_block_copy_0(struct __AnyThing__someAct_block_impl_0*dst, struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __AnyThing__someAct_block_dispose_0(struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __AnyThing__someAct_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __AnyThing__someAct_block_impl_0*, struct __AnyThing__someAct_block_impl_0*);
void (*dispose)(struct __AnyThing__someAct_block_impl_0*);
} __AnyThing__someAct_block_desc_0_DATA = { 0, sizeof(struct __AnyThing__someAct_block_impl_0), __AnyThing__someAct_block_copy_0, __AnyThing__someAct_block_dispose_0};
static void _I_AnyThing_someAct(AnyThing * self, SEL _cmd) {
void (*test)() = ((void (*)())&__AnyThing__someAct_block_impl_0((void *)__AnyThing__someAct_block_func_0, &__AnyThing__someAct_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);
}
// @end
- weak self
#import <Foundation/Foundation.h>
@interface AnyThing : NSObject
@end
#import "AnyThing.h"
@implementation AnyThing
- (void)someAct{
__weak __typeof(&*self)weakSelf = self;
void (^test)() = ^ {
NSLog(@"%@",weakSelf);
};
test();
}
@end
// @implementation AnyThing
struct __AnyThing__someAct_block_impl_0 {
struct __block_impl impl;
struct __AnyThing__someAct_block_desc_0* Desc;
__weak typeof (&*self) weakSelf;//<<<不同
__AnyThing__someAct_block_impl_0(void *fp, struct __AnyThing__someAct_block_desc_0 *desc, __weak typeof (&*self) _weakSelf, int flags=0) : weakSelf(_weakSelf) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __AnyThing__someAct_block_func_0(struct __AnyThing__someAct_block_impl_0 *__cself) {
__weak typeof (&*self) weakSelf = __cself->weakSelf; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qp_p2pj3jmj65n39jgl4wx9_l9w0000gn_T_AnyThing_fda013_mi_0,weakSelf);
}
static void __AnyThing__someAct_block_copy_0(struct __AnyThing__someAct_block_impl_0*dst, struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __AnyThing__someAct_block_dispose_0(struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_dispose((void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __AnyThing__someAct_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __AnyThing__someAct_block_impl_0*, struct __AnyThing__someAct_block_impl_0*);
void (*dispose)(struct __AnyThing__someAct_block_impl_0*);
} __AnyThing__someAct_block_desc_0_DATA = { 0, sizeof(struct __AnyThing__someAct_block_impl_0), __AnyThing__someAct_block_copy_0, __AnyThing__someAct_block_dispose_0};
static void _I_AnyThing_someAct(AnyThing * self, SEL _cmd) {
__attribute__((objc_ownership(weak))) __typeof(&*self)weakSelf = self;
void (*test)() = ((void (*)())&__AnyThing__someAct_block_impl_0((void *)__AnyThing__someAct_block_func_0, &__AnyThing__someAct_block_desc_0_DATA, weakSelf, 570425344));
((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);
}
// @end
根據(jù)上面的不同
,知道:
strong
struct __AnyThing__someAct_block_impl_0對(duì)self保持強(qiáng)引用
weak
struct __AnyThing__someAct_block_impl_0對(duì)self保持弱引用
但
strong weak 對(duì)捕獲對(duì)象的拷貝,傳入flag一模一樣
static void __AnyThing__someAct_block_copy_0(struct __AnyThing__someAct_block_impl_0*dst, struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __AnyThing__someAct_block_copy_0(struct __AnyThing__someAct_block_impl_0*dst, struct __AnyThing__someAct_block_impl_0*src) {
_Block_object_assign((void*)&dst->weakSelf, (void*)src->weakSelf, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
//printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
......
else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
//printf("retaining object at %p\n", object);
_Block_retain_object(object);//加引用計(jì)數(shù)?
//printf("done retaining object at %p\n", object);
_Block_assign((void *)object, destAddr);
}
}
strong self 加引用計(jì)數(shù),可以理解!
weak self 加引用計(jì)數(shù),是不是有些矛盾?
不要奇怪,ARC環(huán)境有了更完善的內(nèi)存管理禽绪,如果外部變量由__strong
怎静、copy
胁孙、strong
修飾時(shí)告组,Block會(huì)把捕獲的變量用__strong
來(lái)修飾進(jìn)而達(dá)到持有的目的.這里的_Block_retain_object
只不過(guò)是一個(gè)空操作.
以下代碼見(jiàn)于BlocksRuntime/runtime.c
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
static void _Block_retain_object_default(const void *ptr __unused) {
}
4.2 MRC環(huán)境下
在一些文章里也看到過(guò),在MRC環(huán)境下,用__block
修飾對(duì)象也能避免循環(huán)引用.那我們也通過(guò)編譯后的代碼來(lái)看一下其內(nèi)在原理.
注意上面提到過(guò)的_Block_retain_object
方法,在ARC中,它是一個(gè)空操作.
在MRC中,_Block_retain_object
可不是一個(gè)空控制,_Block_retain_object
會(huì)給傳入的對(duì)象加引用計(jì)數(shù).
在iOS Block Part5里,我們已經(jīng)知道,在ARC環(huán)境下__block
修飾對(duì)象,會(huì)有以下對(duì)對(duì)象的拷貝動(dòng)作.
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
131==BLOCK_FIELD_IS_OBJECT||BLOCK_FIELD_IS_CALLER;
而MRC環(huán)境下也是一樣的.
所以在_Block_object_assign
內(nèi)走的是以下代碼片段,很好的避開(kāi)了給對(duì)象加引用計(jì)數(shù)的_Block_retain_object
方法,所以也就不會(huì)有循環(huán)引用.
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
//printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
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);
}
}
...
}
5.拋磚引玉
雖然我已經(jīng)仔細(xì)的檢查了自己的相關(guān)代碼和相關(guān)的措辭忌怎,但是請(qǐng)不要盲目相信本文的正確性。
我已經(jīng)見(jiàn)過(guò)非常多的經(jīng)驗(yàn)開(kāi)發(fā)者對(duì)于 Block 有錯(cuò)誤的理解(我也不會(huì)例外)吼砂。請(qǐng)一定保持一顆懷疑的心绩鸣。
by 酷酷的哀殿
關(guān)于Block的認(rèn)識(shí)文章改了許多遍,每回都以為自己已經(jīng)得其精髓,但一次一次證明自己還是太淺薄.本系列文章也只是拋磚引玉,很多東西,還得自己去看去理解!
參考文獻(xiàn):
Block技巧與底層解析 by tripleCC