本文將淺分析幾個(gè)Block使用問(wèn)題:
- 解析問(wèn)題一:Block作為類(lèi)變量屬性時(shí)為啥用copy修飾凯楔?堆棧存儲(chǔ)位置是怎樣的锦募?
- 解析問(wèn)題二:為什么需要__block 修飾自動(dòng)變量后糠亩,才能在Block中更改?
- 解析問(wèn)題三:關(guān)于常見(jiàn)block中self的循環(huán)引用問(wèn)題富弦,可以用__weak打破強(qiáng)引用鏈氛驮;那么為什么AFN或像UIView動(dòng)畫(huà)不需要__weak修飾self?
Block:帶有自動(dòng)變量(局部變量)的匿名函數(shù)盏缤。
Block類(lèi)型:
內(nèi)聯(lián)Block(inline):說(shuō)白了就是Block被嵌入到一個(gè)函數(shù)中唉铜,較常使用;
獨(dú)立Block:即在方法外定義的竞惋。不能直接訪(fǎng)問(wèn) self灰嫉,只能通過(guò)將 self 當(dāng)作參數(shù)傳遞到 Block 中才能使用,并且此時(shí)的 self 只能通過(guò) setter 或 getter 方法訪(fǎng)問(wèn)其屬性浑厚,不能使用句點(diǎn)式方法根盒。但內(nèi)聯(lián) Block 不受此限制.
// 獨(dú)立Block
void (^correctBlockObject)(id) = ^(id self){
// 將self作為參數(shù)傳遞
NSLog(@"self = %@", self);
// 訪(fǎng)問(wèn)self變量
NSLog(@"self = %@", [self strName]);
};
使用注意點(diǎn):
Block作為屬性用copy修飾:
Block作為類(lèi)屬性炎滞,要用copy修飾,把Block從椖频迹拷貝到堆中击奶,防止被釋放掉柜砾;不能修改外部自動(dòng)變量問(wèn)題:
Block里面能修改全局或靜態(tài)的變量换衬,但是不能修改在Block外并且定義在所在方法內(nèi)的自動(dòng)變量瞳浦,這時(shí)需要__block符修飾此變量;
例:
__block NSInteger val = 0;
void (^block)(void) = ^{
val = 1;
};
block();
NSLog(@"val = %ld", val);
//(這是在ARC下這樣使用蝇完,MRC下__block含義是不增加此對(duì)象引用計(jì)數(shù),相當(dāng)于ARC下的__weak)
(自動(dòng)(automatic)變量氢架,即局部變量:不作專(zhuān)門(mén)說(shuō)明的局部變量朋魔,均是自動(dòng)變量。自動(dòng)變量也可用關(guān)鍵字auto作出說(shuō)明孙援,auto int c=3拓售;/c為自動(dòng)變量/洼裤。自動(dòng)變量只有一種存儲(chǔ)方式,就是存儲(chǔ)在棧中值骇。由于自動(dòng)變量存儲(chǔ)在棧中移国,所以自動(dòng)變量的作用域只在函數(shù)內(nèi)迹缀,其生命周期也只持續(xù)到函數(shù)調(diào)用的結(jié)束。)
- 循環(huán)引用問(wèn)題:
Block在方法中被定義時(shí)票摇,該方法執(zhí)行完是需要被釋放的砚蓬,Block會(huì)強(qiáng)引用自己持有的對(duì)象灰蛙,使其引用計(jì)數(shù)+1,如果所持有的對(duì)象也持有此Block時(shí)物延,需要__weak 在外修飾此對(duì)象仅父,標(biāo)識(shí)不+1浑吟,常見(jiàn)的是self或一些強(qiáng)類(lèi)型的對(duì)象:
例如:(ARC下)
__weak HomeViewController * VC = self;
__weak typeof(self) weakSelf = self;
__weak __typeof(&*self)weakSelf = self;
__weak typeof(_tableView) weakTableView = _tableView;
(MRC下买置,無(wú)__weak)
__block HomeViewController * VC = self;
ARC與MRC下Block內(nèi)存分配機(jī)制不一樣强霎,ARC中iOS5+用
__weak
城舞,之前是用__unsafe_unretained
修飾符;
__unsafe_unretained缺點(diǎn)是指針?biāo)玫膶?duì)象被回收后脱柱,自己不會(huì)置為nil拉馋,因此可能導(dǎo)致程序崩潰煌茴;而weak指針不同,對(duì)象被回收后矩乐,指針會(huì)被賦值為nil回论。一般來(lái)說(shuō)傀蓉,weak修飾符更加安全。
另一種寫(xiě)法:
有時(shí)候weakSelf會(huì)在Block未執(zhí)行完就會(huì)釋放掉成為nil误甚,為了防止這種情況出現(xiàn)萨蚕,在Block內(nèi)需要__strong對(duì)weakSelf強(qiáng)引用一下(更高級(jí)寫(xiě)法見(jiàn)RAC的@weakify(self)岳遥,@strongify(self))裕寨。
//宏定義 - Block循環(huán)引用
#define weakify(var) __weak typeof(var) weakSelf = var
#define strongify(var) __strong typeof(var) strongSelf = var
weakify(self);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
strongSelf(weakSelf);
[strongSelf doSomething];//防止weakSelf釋放掉
[strongSelf doOtherThing];
});
這樣不會(huì)造成循環(huán)引用是因?yàn)閟trongSelf是在Block里面聲明的一個(gè)指針,當(dāng)Block執(zhí)行完畢后捻艳,strongSelf會(huì)釋放认轨,這個(gè)時(shí)候?qū)⒉辉購(gòu)?qiáng)引用weakSelf,所以self會(huì)正確的釋放恩急。
解析問(wèn)題一:Block的存儲(chǔ)位置
在Objective-C語(yǔ)言中纪蜒,一共有3種類(lèi)型的block:
1._NSConcreteGlobalBlock 全局的靜態(tài)block纯续,不會(huì)訪(fǎng)問(wèn)任何外部變量。
2._NSConcreteStackBlock 保存在棧中的block窗看,當(dāng)函數(shù)返回時(shí)會(huì)被銷(xiāo)毀兔魂。
3._NSConcreteMallocBlock 保存在堆中的block(從棧中copy過(guò)去的)析校,當(dāng)引用計(jì)數(shù)為0時(shí)會(huì)被銷(xiāo)毀。
Block內(nèi)捕獲變量會(huì)改變自身存儲(chǔ)位置遂唧,包括讀取變量和__block這種寫(xiě)變量吊奢,兩種方式(其實(shí)結(jié)果是一樣的)页滚。
【在MRC下】:存在棧、全局隧熙、堆這三種形式贞盯。
【在ARC下】:大部分情況下系統(tǒng)會(huì)把Block自動(dòng)copy到堆上。
Block作為變量:
- 方法中聲明一個(gè) block 的時(shí)候是在棧上闷愤;
- 引用了外部局部變量或成員變量,并且有賦值操作(有名字)件余,會(huì)被 copy 到堆上啼器;
- 賦值給附有__strong修飾符的id類(lèi)型的類(lèi)或者Blcok類(lèi)型成員變量時(shí);
- 賦值給一個(gè) weak 變量不會(huì)被 copy坟漱;
Block作為屬性:
- 用 copy 修飾會(huì)被 copy更哄;
Block作為函數(shù)參數(shù):
- 作為參數(shù)傳入函數(shù)不會(huì)被 copy成翩,依然在棧上,方法執(zhí)行完即釋放栅炒;
- 作為函數(shù)的返回值會(huì)被 copy 到堆赢赊;
例如:
-(void)method {
//在ARC環(huán)境下级历,Block也是存在__NSStackBlock的時(shí)候的寥殖,平時(shí)見(jiàn)到最多的是_NSConcreteMallocBlock,是因?yàn)槲覀儠?huì)對(duì)Block有賦值操作熏纯,所以ARC下粤策,block 類(lèi)型通過(guò)=進(jìn)行傳遞時(shí)掐场,會(huì)導(dǎo)致調(diào)用objc_retainBlock->_Block_copy->_Block_copy_internal方法鏈。并導(dǎo)致 __NSStackBlock__ 類(lèi)型的 block 轉(zhuǎn)換為 __NSMallocBlock__ 類(lèi)型萍膛。
int a = 3;
void(^block)() = ^{
NSLog(@"調(diào)用block%d",a);//這里的變量a蝗罗,和self.string是一樣效果
};
NSLog(@"%@",block);
//打印結(jié)果:<__NSMallocBlock__: 0x7fc498746000>
//此時(shí)后面的匿名函數(shù)賦值給block指針(創(chuàng)建帶名字的block)蝌戒,且引用了外部局部變量,block會(huì)copy到堆
NSLog(@"%@",^{NSLog(@"調(diào)用block%d",a);});
//打印結(jié)果:<__NSStackBlock__: 0x7fff54f0c700>
//匿名函數(shù)無(wú)賦值操作北苟,只存于棧上,會(huì)不定釋放
static int b = 2;
void(^block)() = ^{
NSLog(@"調(diào)用block%d",b);//若不引用任何變量傻昙,也是__NSGloBalBlock__
};
NSLog(@"%@",block);
}
//打印結(jié)果:<__NSGloBalBlock__: 0x7fc498746000>
//此時(shí)引用了全局變量,block放在全局區(qū)
解析問(wèn)題二:為什么__block
修飾自動(dòng)變量后妆档,就能在Block中更改虫碉?
首先,為什么Block不能修改外部自動(dòng)變量须板?
自動(dòng)變量存于棧中逼纸,在當(dāng)前方法執(zhí)行完济蝉,會(huì)釋放掉王滤。一般來(lái)說(shuō),在 block 中用的變量值是被復(fù)制過(guò)來(lái)的雁乡,自動(dòng)變量是值類(lèi)型復(fù)制第喳,新開(kāi)辟棧空間踱稍,所以對(duì)于新復(fù)制變量的修改并不會(huì)影響這個(gè)變量的真實(shí)值(也稱(chēng)只讀拷貝)曲饱。大多情況下悠抹,block是作為參數(shù)傳遞以供后續(xù)回調(diào)執(zhí)行的。所以在你想要在block中修改此自動(dòng)變量時(shí)扩淀,變量可能已被釋放,所以不允許block進(jìn)行修改是合理的驻谆。
對(duì)于 static 變量卵凑,全局變量胜臊,在 block中是有讀寫(xiě)權(quán)限的勺卢,因?yàn)榇俗兞看嬗谌謹(jǐn)?shù)據(jù)區(qū)(非棧區(qū)),不會(huì)隨時(shí)釋放掉象对,也不會(huì)新開(kāi)辟內(nèi)存創(chuàng)建變量黑忱, block 拷貝的是指向這些變量的指針,可以修改原變量勒魔。
那么怎么讓自動(dòng)變量不被釋放杨何,還能被修改呢?
__block修飾符把所修飾的變量包裝成一個(gè)
結(jié)構(gòu)體對(duì)象
沥邻,即可完美解決危虱。Block既可以強(qiáng)引用此結(jié)構(gòu)體對(duì)象,使之不會(huì)自動(dòng)釋放唐全,也可以拷貝到指向該結(jié)構(gòu)體對(duì)象的指針埃跷,通過(guò)指針修改結(jié)構(gòu)體的變量,也就是__block所修飾的自動(dòng)變量邮利。
將下面main.m文件進(jìn)行clang -rewrite-objc main.m命令查看編譯時(shí)的c++代碼:
//寫(xiě)在main.m文件的main方法里
__block NSInteger val = 0;
void (^block)(void) = ^{
val = 1;
};
block();
NSLog(@"val = %ld", val);
如下:
//把val變量封裝成了結(jié)構(gòu)體
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;//注意這個(gè)__forwarding指針
int __flags;
int __size;
NSInteger val;
};
//block變量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//結(jié)構(gòu)體作為參數(shù)是通過(guò)指針傳遞弥雹,這里__cself指針傳入變量所在結(jié)構(gòu)體,并在__main_block_func_0中實(shí)現(xiàn)變量值的改變
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;//通過(guò)__forwarding指針找到val變量
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
//block的描述
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};
在__main_block_func_0里延届,發(fā)現(xiàn)是通過(guò)(val->__forwarding->val) = 1;
找到val變量,這么做的原因是什么剪勿?
__Block_byref_val_0 *val = __cself->val;
(val->__forwarding->val) = 1;
我們知道,ARC下Block很多情況會(huì)被自動(dòng)拷貝到堆方庭,而在棧上的__block變量被復(fù)制到堆上之后厕吉,會(huì)將結(jié)構(gòu)體__Block_byref_val_0
的成員變量__Block_byref_val_0 *__forwarding;
的值替換為堆上的__block變量的地址。因此使用 __forwarding 指針就是為了無(wú)論在棧還是在堆械念,都能正確訪(fǎng)問(wèn)到該__block變量头朱。
解析問(wèn)題三:循環(huán)引用問(wèn)題
循環(huán)引用的根本原因是Block和另一個(gè)對(duì)象互相持有,導(dǎo)致都無(wú)法釋放龄减,經(jīng)常碰到的是self(當(dāng)前控制器)项钮,導(dǎo)致控制器無(wú)法釋放,內(nèi)存泄漏嚴(yán)重。
解決循環(huán)引用問(wèn)題主要有兩個(gè)辦法:
- 事前避免烁巫,我們?cè)跁?huì)產(chǎn)生循環(huán)引用的地方使用 weak 弱引用署隘,以避免產(chǎn)生循環(huán)引用。
- 事后補(bǔ)救亚隙,我們明確知道會(huì)存在循環(huán)引用磁餐,但是我們?cè)诤侠淼奈恢弥鲃?dòng)斷開(kāi)環(huán)中的一個(gè)引用,使得對(duì)象得以回收恃鞋。
//巧神在YTKNetWorking是這么設(shè)計(jì)的:
//Block應(yīng)該結(jié)束完的時(shí)候,手動(dòng)把該釋放的Block置空
- (void)clearCompletionBlock {
// nil out to break the retain cycle.
self.successCompletionBlock = nil;
self.failureCompletionBlock = nil;
}
有時(shí)候納悶為什么AFN或像UIView的Block動(dòng)畫(huà)不需要weakSelf也可以自己釋放掉亦歉。其實(shí)明白了無(wú)法釋放的原理恤浪,也就明白了。雖然Block強(qiáng)引用了self肴楷,但是self不強(qiáng)引用這個(gè)Block呀水由。
舉例說(shuō)明(需要和不需要使用__weak打破循環(huán)引用):
typedef void(^Block)();
@interface SecViewController ()
@property (nonatomic , strong) NSString *str;
@property (nonatomic , copy) Block blk;
@end
@implementation SecViewController
- (void)viewDidLoad {
self.str = @"string";
//以下2種Block都不會(huì)被self強(qiáng)引用
//doSomthing1方法執(zhí)行完,pop后此控制器會(huì)被釋放掉
[self doSomthing1:^{
NSLog(@"str111:%@",self.str);
}];
//doSomthing2方法執(zhí)行完赛蔫,pop后此控制器也會(huì)被釋放掉
[self doSomthing2:^(int a, int b) {
NSLog(@"str111:%@",self.str);
}];
//self持有此Block砂客,要用__weak
__weak typeof(self) weakSelf = self;
self.blk = ^(){
NSLog(@"str111:%@",weakSelf.str);
};
//在doSomthing3方法中block參數(shù)賦值給self.blk,這樣block參數(shù)就間接被self強(qiáng)引用,需用weakSelf呵恢,用self會(huì)導(dǎo)致循環(huán)引用鞠值,當(dāng)前控制器無(wú)法釋放;
//經(jīng)常碰到的都是這種間接持有導(dǎo)致的循環(huán)引用問(wèn)題
[self doSomthing3:^{
NSLog(@"str111:%@",weakSelf.str);
}];
}
- (void)doSomthing1:(void(^)())block{
if(block){
block();
}
}
- (void)doSomthing2:(Block)block{
if(block){
block();
}
}
- (void)doSomthing3:(Block)block{
if(block){
self.blk = block;
block();
}
}
-(void)dealloc{
NSLog(@"SecVC釋放了");
}