最近發(fā)現(xiàn)很多開發(fā)者對block的理解并不是很深,很多項目當中使用的時候多多少會有些問題,今天給大家詳細講講block的內(nèi)存管理, 主要從以下幾個方面來講:
- 根據(jù)內(nèi)存劃分block的類型
- block內(nèi)存管理
- 防止循環(huán)引用
B?lock類型
根據(jù)Block在內(nèi)存中的位置,系統(tǒng)把Block分為3類:NSGlobalBlock,NSStackBlock, NSMallocBlock;
-
NSGlobalBlock:位于內(nèi)存全局區(qū) -
NSStackBlock:位于內(nèi)存棧區(qū) -
NSMallocBlock:位于內(nèi)存堆區(qū)
我們通過block引用不同的變量來
全局區(qū)block(NSGlobalBlock)
沒有引用局部變量的block叫做NSGlobalBlock,如下實例:
//類型1:沒有使用任何外部變量
-(void)test
{
void (^gBlock1)(int , int ) =^(int a, int b){
NSLog(@"a + b = %d", a+b);
};
NSLog(@"%@", gBlock1);
//打印結(jié)果為:
//<__NSGlobalBlock__: 0x1025e8110>
}
//類型2:使用全局變量
//全局變量
int a = 10;
-(void)test
{
void (^gBlock)() = ^(){
NSLog(@"%d", a);
};
NSLog(@"%@", gBlock);
//輸出結(jié)果為:
//<__NSGlobalBlock__: 0x103676110>
}
棧區(qū)block(NSStackBlock)
引用了局部變量的block叫做NSStackBlock, 實例如下:
-(void)test
{
//局部變量
NSArray *arr = @[@"zhangsan", @"lisi"];
void (^sBlock)() = ^(){
NSLog(@"arr = %@", arr);
};
NSLog(@"%@", sBlock);
//輸出結(jié)果為:
//<__NSStackBlock__: 0x7fff5bbf1a58>
}
PS:棧區(qū)block在方法返回后就會被釋放,所以只能在方法內(nèi)部使用,如果將他賦值給其他對象或者存儲起來,后面使用時將會出現(xiàn)錯誤.
堆區(qū)Block(***NSMallocBlock ***)
在非ARC下,我們一般不手動創(chuàng)建NSMallocBlock,我們把從棧區(qū)復制(copy)過來的block稱為堆區(qū)block。實例如下:
-(void)test
{
NSArray *arr = @[@"zhangsan", @"lisi"];
//棧區(qū)block
void (^sBlock)() = ^(){
NSLog(@"arr = %@", arr);
};
NSLog(@"%@", sBlock);
//堆區(qū)block
void (^mBlock)() = [sBlock copy];
NSLog(@"%@", mBlock);
//輸出結(jié)果為:
//<__NSStackBlock__: 0x7fff59bf9a38>
//<__NSMallocBlock__: 0x7fc173f0dd80>
}
Block內(nèi)存管理
對block自身內(nèi)存的管理
對于block,有兩個內(nèi)存管理方法:Block_copy, Block_release;Block_copy與copy等效, Block_release與release等效;
不管是對block進行retian,copy,release,block的引用計數(shù)都不會增加,始終為1;
NSGlobalBlock:使用retain,copy,release都無效,block依舊存在全局區(qū),且沒有釋放, 使用copy和retian只是返回block的指針;NSStackBlock:使用retain,release操作無效;棧區(qū)block會在方法返回后將block空間回收; 使用copy將棧區(qū)block復制到堆區(qū),可以長久保留block的空間,以供后面的程序使用;NSMallocBlock:支持retian,release,雖然block的引用計數(shù)始終為1,但內(nèi)存中還是會對引用進行管理,使用retain引用+1,release引用-1; 對于NSMallocBlock使用copy之后不會產(chǎn)生新的block,只是增加了一次引用,類似于使用retian;
對引用變量的內(nèi)存管理
在block中經(jīng)常會用到外部變量/對象,如果這個block是存儲在堆區(qū),或者被復制到堆區(qū),則對象對應(yīng)的實例引用+1,當block釋放后block的引用-1;
-(void)test
{
NSArray *arr = @[@"zhangsan", @"lisi"];
NSLog(@"arr.retianCount = %ld", arr.retainCount);
//棧區(qū)block
void (^sBlock)() = ^(){
NSLog(@"arr = %@", arr);
};
//棧區(qū)block不會對引用的變量引用計數(shù)+1
NSLog(@"arr.retianCount = %ld", arr.retainCount);
//堆區(qū)block
void (^mBlock)() = [sBlock copy];
//復制到堆區(qū)后,引用計數(shù)+1
NSLog(@"arr.retianCount = %ld", arr.retainCount);
}
循環(huán)引用
因為block中會對引用的對象進行持有(引用計數(shù)+1),從而導致相互持有引起循環(huán)引用;解決這種問題的方式是對引用變量使用修飾詞__block或者__weak;
-
__block:在非ARC中使用,NSMallocBlock類型的block不會對__block修飾的的變量引用計數(shù)+1,從而消除循環(huán)引用;在ARC中使用__block無效 -
__weak:在ARC中使用,作用和__block一樣,從而消除循環(huán)引用;在非ARC中不可以使用__weak;
防止循環(huán)引用案例:
//TestClass.h
@interface testClass : NSObject
@property (nonatomic, copy)void (^myBlock)(void);
@end
//TestClass.m
#define TestClassExample3 1
@implementation TestClass
-(void)dealloc
{
NSLog(@"測試對象 被釋放了。。。");
[super dealloc];
}
-(instancetype)init
{
self = [super init];
if (self) {
#if TestClassExample1
//會引起循環(huán)應(yīng)用,當前對象無法被釋放
self.myBlock = ^(){
//增加自己本身的引用計數(shù)
[self doSomething];
};
#elif TestClassExample2
//在非ARC下有效,防止循環(huán)引用
//在ARC下無效,會產(chǎn)生循環(huán)引用
__block TestClass *weakSelf = self;
self.myBlock = ^(){
//在非ARC下不會增加self的引用計數(shù)
[weakSelf doSomething];
};
#elif TestClassExample3
//在非ARC下無效,會產(chǎn)生循環(huán)引用
//在ARC下有效,防止循環(huán)應(yīng)用
__weak TestClass *weakSelf = self;
self.myBlock = ^(){
//在非ARC下不會增加self的引用計數(shù)
[weakSelf doSomething];
};
#endif
}
return self;
}
-(void)doSomething
{
NSLog(@"測試程序");
}
@end
//main.h
int main(int argc, char * argv[]) {
@autoreleasepool {
TestClass *tc = [[TestClass alloc] init];
[tc release];
tc = nil;
}
}
歡迎大家踴躍評論,讓我們一起探討技術(shù)??!
如果覺得文章不錯,請幫忙點擊文章下方的喜歡??!
你的支持將是對我最好的鼓勵, 謝謝?。?!