标签:
摘录:http://www.cnblogs.com/goahead-yingjun/articles/4478425.html
参考:http://www.cocoachina.com/bbs/read.php?tid=152222
Block作为C语言的扩展,并不是高新技术,和其他语言的闭包或lambda表达式是一回事。需要注意的是由于Objective-C在iOS中不支持GC机制,使用Block必须自己管理内存,而内存管理正是使用Block坑最多的地方,错误的内存管理 要么导致return cycle内存泄漏要么内存被提前释放导致crash。 Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外、词法作用域以内的外部变量的值。换句话说,Block不仅 实现函数的功能,还能携带函数的执行环境。
可以这样理解,Block其实包含两个部分内容
Block执行的代码,这是在编译的时候已经生成好的;
一个包含Block执行时需要的所有外部变量值的数据结构。 Block将使用到的、作用域附近到的变量的值建立一份快照拷贝到栈上。
Block与函数另一个不同是,Block类似ObjC的对象,可以使用自动释放池管理内存(但Block并不完全等同于ObjC对象)
根据Block在内存中的位置分为三种类型NSGlobalBlock,NSStackBlock, NSMallocBlock。
NSGlobalBlock:类似函数,位于text段(不在堆栈中);
NSStackBlock:位于栈内存,函数返回后Block将无效;
NSMallocBlock:位于堆内存。
block在自己的环路(代码块)中不会抓取任何外部变量,它不需要在在运行的引用其他外包变量,便不会在运行的时候将block压入栈中,而会作为一个NSGlobalBlock编译。它既不是栈也不是堆,而是代码片段的一部分。所以它不管在什么环境下(MRC、ARC)都能正常运行。
如下,我们可以通过是否引用外部变量识别,未引用外部变量即为NSGlobalBlock。
void exampleA() {
//create a NSGlobalBlock
float (^sum)(float, float) = ^(float a, float b){
return a + b;
};
NSLog(@"block is %@", sum); //block is <__NSGlobalBlock__: 0x47d0>
}
如果block有引用外部的局部变量,那么该block便会被压入调用它的函数的栈中,编译器会将其编译为NSStackBlock类型。当其调用它的函数结束时,栈会被清空,同时在栈中的block也会被清除,如果此时有其他函数调用此block便会野指针报错。要解决这个问题,需要手动对block进行copy,通过copy后,并设为自动释放,会将copy分配到堆中,并交由最近释放池负责释放,如:
[[stackBlock copy] autorelease]
但是,在ARC环境下,block会默认分配到堆中,作为一个自动释放的NSMallocBlock(即不受栈被清空的影响)。
代码如下:
typedef void (^dBlock)();
dBlock exampleB_getBlock(){
char b = ‘B‘;
void (^block)() = ^{
printf("%c\n", b);
};
NSLog(@"block:%@",block);
dBlock block_copy=[[block copy] autorelease];
NSLog(@"block_copy:%@",block_copy);
// return block_copy;
return block;
}
void exampleB() {
dBlock block=exampleB_getBlock();
block();
}
在MRC环境中,运行后,打印为
block:<__NSStackBlock__: 0xbffbff68>
block_copy:<__NSMallocBlock__: 0x78e66430>
说明block经过copy后,被分配到了堆上。
当返回block时,因为是NSStackBlock,所以block();
野指针报错。
当返回block_copy时,因为是NSMallocBlock,运行正常。
如果在ARC环境下,block不经过copy,便默认是NSMallocBlock,直接返回block就可以了。
NSMallocBlock只需要对NSStackBlock进行copy操作就可以获取,但是retain操作就不行,会在下面说明
Block的copy、retain、release操作
不同于NSObjec的copy、retain、release操作:
如果block对外部变量做了引用,那他们之间会有什么影响呢?
1、局部变量
void exampleC() {
char b = ‘B‘;
NSLog(@"%p,%c",&b,b);
void (^block)() = ^{
NSLog(@"%c",b);
};
block();//修改前
b=‘C‘;
NSLog(@"%p,%c",&b,b);
block();//修改后
}
打印:
0xbffa8f8b,B
0xbffa8f6c,B
0xbffa8f8b,C
0xbffa8f6c,B
执行后block两次打印结果为两个B,说明,对于基本数据类型的局部变量,当block对其引用的时候,会创建下该变量的一份快照(新建一个同类型的变量,并保存相同值),变量后面值的改变不会影响快照的值,在创建block的时候本质上已经分为两个独立的变量了。
注意:局部变量(无__block修饰)的快照,是只读的,不能在block中修改其值,包括指针变量也不能修改其引用地址。
2.静态变量 全局变量
void exampleC() {
static int num=1;
NSLog(@"%p,%d",&num,num);
void (^block)() = ^{
NSLog(@"%p,%d",&num,num);
};
block();//修改前
num=2;
NSLog(@"%p,%d",&num,num);
block();//修改后
}
执行后打印了:
0x647f0,1
0x647f0,1
0x647f0,2
0x647f0,2
变量地址都是一样,说明没有创建快照,而是直接引用,因为全局变量或静态变量在内存中的地址是固定的,Block在读取该变量值的时候是直接从其所在内存读出,所以获取到的是最新值。
3、__block修饰的变量
被__block修饰的变量称作Block变量。 Block变量等效于全局变量、或静态变量。当block位于栈区时,block变量使用的都是相同变量,而当block位于堆区时,block变量一样会创建快照,使用快照代替原变量,具体看下面会讲到。
iOS中,oc对象本质上是一个指向结构体的指针变量。 block对oc对象的引用,其本质是对其指针地址的引用,因此,block引用时,实际上是对指针变量做了一份快照(新创建一个指针变量,指向相同的地址)。所以,获取到的oc对象取决于快照(指针变量)指向的值,如果其指向的值发生变化,block引用到的值自然也跟着变了。下面看例子:
void exampleC() {
NSString *str=[NSString stringWithFormat:@"%@",@"str"];
NSLog(@"%p,%p,%@",&str,str,str);
void (^block)() = ^{
NSLog(@"%p,%p,%@",&str,str,str);
NSLog(@"-----------------");
};
block();//修改前
str=[NSString stringWithFormat:@"%@",@"newStr"];
NSLog(@"%p,%p,%@",&str,str,str);
block();//修改后
}
执行后,打印
0xbffd7f84,0x7b7942b0,str
0xbffd7f74,0x7b7942b0,str
-----------------
0xbffd7f84,0x7b896950,newStr
0xbffd7f74,0x7b7942b0,str
-----------------
从结果看,创建的指针变量str与在block内引用的str快照的变量地址是不同的0xbffd7f84和0xbffd7f74,说明,在block在引用该对象的时候,创建了一个相同类型的oc对象快照(指针变量),保存相同的值,因为是指针,所以值为引用地址0x7b7942b0。当修改str时,其指针指向新对象地址0x7b896950,但快照依然指向了0x7b7942b0,因此还是打印出了str字符串。
如果你硬要问,oc对象的处理方式不是用指针变量来理解,那我就验证一下给你看:
void exampleC() {
char b = ‘B‘;
char *pb=&b;
NSLog(@"%p,%p,%c",&pb,pb,*pb);
void (^block)() = ^{
NSLog(@"%p,%p,%c",&pb,pb,*pb);
NSLog(@"-----------------");
};
block();//修改前
*pb=‘C‘;
NSLog(@"%p,%p,%c",&pb,pb,*pb);
block();//修改后
}
打印结果:
0xbffe0f84,0xbffe0f8b,B
0xbffe0f6c,0xbffe0f8b,B
-----------------
0xbffe0f84,0xbffe0f8b,C
0xbffe0f6c,0xbffe0f8b,C
-----------------
验证以上oc对象引用的说法。block的快照就是变量类型相同的是两个变量,保存相同的值,如果是oc对象就引用相同对象(地址)。
依托上面的论证,oc对象变化的时候,如果改变引用对象的地址,block返回到的就一直是最新的值。这种情况在使用oc可变对象的时候会出现,如NSMutableArray、NSMutableString等等。代码如下:
void exampleC() {
NSMutableArray *list=[NSMutableArray arrayWithArray:@[@"a",@"b",@"c"]];
// NSMutableString *mutStr=[NSMutableString stringWithFormat:@"abc"];
NSLog(@"%p,%p,%@",&list,list,list);
void (^block)() = ^{
NSLog(@"%p,%p,%@",&list,list,list);
NSLog(@"-----------------");
};
block();//修改前
//[mutStr appendString:@"123"];
[list removeObjectAtIndex:0];
NSLog(@"%p,%p,%@",&list,list,list);
block();//修改后
}
执行后结果为
0xbffcff7c,0x7b063c10,(
a,
b,
c
)
0xbffcff6c,0x7b063c10,(
a,
b,
c
)
-----------------
0xbffcff7c,0x7b063c10,(
b,
c
)
0xbffcff6c,0x7b063c10,(
b,
c
)
-----------------
从打印结果可看出,虽然其快照与外部变量不同,但可变对象在修改后,其引用地址是不会改变,一直都是0x7b063c10,因此,block一直都能引用到最新的值。因为不会改变快照的引用地址,所以,是可以在block中修改可变oc对象的
block对于objc对象的内存管理较为复杂,当block被分配到堆区的时候,其对引用到的外部变量的持有也会发生变化,如果处理不好,就会造成循环引用等问题。 这里要分static静态变量、 global全局变量 、local局部变量、 block变量分析,还要区分非arc和arc环境的影响。
@interface ViewController : UIViewController
{
NSObject *instanceObj;
}
@end
NSString* __globalObj = nil;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self exampleD];
}
- (void)exampleD{
static NSString *__staticObj;
__globalObj = [[NSString alloc] initWithFormat:@"global"];
instanceObj = [[NSString alloc] initWithFormat:@"instance"];
__staticObj = [[NSString alloc] initWithFormat:@"static"];
NSString* localObj = [[NSString alloc] initWithFormat:@"local"];
__block NSString* blockObj =[[NSString alloc] initWithFormat:@"block"];
NSLog(@"-----------原变量-------------");
NSLog(@"%p,%p,%@",&__globalObj,__globalObj, __globalObj);
NSLog(@"%p,%p,%@",&__staticObj,__staticObj, __staticObj);
NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj);
NSLog(@"%p,%p,%@",&localObj,localObj, localObj);
NSLog(@"%p,%p,%@",&blockObj,blockObj, blockObj);
NSLog(@"---------------------------------");
NSLog(@"self:%d",[self retainCount]);
dBlock aBlock = ^{
NSLog(@"%p,%p,%@",&__globalObj,__globalObj, __globalObj);
NSLog(@"%p,%p,%@",&__staticObj,__staticObj, __staticObj);
NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj);
NSLog(@"%p,%p,%@",&localObj,localObj, localObj);
NSLog(@"%p,%p,%@",&blockObj,blockObj, blockObj);
};
NSLog(@"----------被block引用后的变量---------");
aBlock();
NSLog(@"--------------------------------");
NSLog(@"self:%d",[self retainCount]);
aBlock = [[aBlock copy] autorelease];
NSLog(@"self:%d",[self retainCount]);
NSLog(@"-------block被分配堆区后引用的变量---------");
aBlock();
NSLog(@"--------------------------------");
NSLog(@"\n-------block被分配堆区后引用的变量计数-------------");
NSLog(@"globalObj:%d", [__globalObj retainCount]);
NSLog(@"staticObj:%d", [__staticObj retainCount]);
NSLog(@"instanceObj:%d", [instanceObj retainCount]);
NSLog(@"localObj:%d", [localObj retainCount]);
NSLog(@"blockObj:%d", [blockObj retainCount]);
}
@end
运行后,打印
-----------原变量-------------
0x2f9d4,0x79066450,global
0x2f9d8,0x79062eb0,static
0x790e256c,0x79062e90,instance
0xbffd2f94,0x790628f0,local
0xbffd2f90,0x790e3d20,block
---------------------------------
self:25
----------被block引用后的变量---------
0x2f9d4,0x79066450,global
0x2f9d8,0x79062eb0,static
0x790e256c,0x79062e90,instance
0xbffd2f60,0x790628f0,local
0xbffd2f90,0x790e3d20,block
--------------------------------
self:25
self:26
-------block被分配堆区后引用的变量---------
2016-04-20 16:06:23.827 BlockTest[3567:234612] 0x2f9d4,0x79066450,global
2016-04-20 16:06:23.827 BlockTest[3567:234612] 0x2f9d8,0x79062eb0,static
2016-04-20 16:06:23.827 BlockTest[3567:234612] 0x790e256c,0x79062e90,instance
2016-04-20 16:06:23.827 BlockTest[3567:234612] 0x78f692e8,0x790628f0,local
2016-04-20 16:06:23.827 BlockTest[3567:234612] 0x78f68ab8,0x790e3d20,block
--------------------------------
-------block被分配堆区后引用的变量计数-------------
globalObj:1
staticObj:1
instanceObj:1
localObj:2
blockObj:1
从结果分析可看出,
从内存上讲,在block copy的时候,是重新在堆区开辟一块内存空间,将block保存进去,将NSStackBlock变为NSMallocBlock。
我们都知道,在block引用外包oc的时候,会创建一份oc变量(指针变量)快照,其中只对localObj创建快照,而其他类型是直接使用同一个变量。
为了保证堆区block中的快照(指针变量)指针引用到的localObj和instanceObj有效,避免因栈区清空导致localObj和instanceObj被销毁造成野指针,因此,编译器在copy block的时候,会对其引用到的localObj和instanceObj的self作一次retain。而globalObj、staticObj和blockObj则不存在这种问题。
PS:
blockObj与globalObj、staticObj不同的是,在block未被copy到堆区的时候,在block里blockObj是直接使用的,但是当block被copy时,blockObj其实也会另外做一份快照,blockObj在block copy前后,blockObj变量地址是不同的,只是引用地址相同而已,但是跟局部变量的快照不同,它可以修改其值(引用地址)的。所以其作用是跟globalObj、staticObj一样的。
在arc环境下,引用外部变量的block会被自动分配到堆区(相当于默认帮我们做了copy)。由于arc中没有retain,retainCount的概念。只有强引用和弱引用的概念。当一个变量没有__strong的指针指向它时,就会被系统释放。因此我们可以通过为变量设置一个week弱引用来检测引用对象,一旦引用对象被释放,week就会为nil, 下面代码来测试:
__weak NSString *globalObj_week;
__weak NSString *instanceObj_week;
__weak NSString *staticObj_week;
__weak NSString *localObj_week;
__weak NSString *blockObj_week;
- (void)exampleD{
static NSString *__staticObj;
__globalObj = [[NSString alloc] initWithFormat:@"global"];
instanceObj = [[NSString alloc] initWithFormat:@"instance"];
__staticObj = [[NSString alloc] initWithFormat:@"static"];
NSString* localObj = [[NSString alloc] initWithFormat:@"local"];
__block NSString* blockObj =[[NSString alloc] initWithFormat:@"block"];
NSLog(@"-----------原变量-------------");
NSLog(@"%p,%p,%@",&__globalObj,__globalObj, __globalObj);
NSLog(@"%p,%p,%@",&__staticObj,__staticObj, __staticObj);
NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj);
NSLog(@"%p,%p,%@",&localObj,localObj, localObj);
NSLog(@"%p,%p,%@",&blockObj,blockObj, blockObj);
NSLog(@"---------------------------------");
globalObj_week=__globalObj;
instanceObj_week=instanceObj;
staticObj_week=__staticObj;
localObj_week=localObj;
blockObj_week=blockObj;
dBlock aBlock = ^{
NSLog(@"%p,%p,%@",&__globalObj,__globalObj, __globalObj);
NSLog(@"%p,%p,%@",&__staticObj,__staticObj, __staticObj);
NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj);
NSLog(@"%p,%p,%@",&localObj,localObj, localObj);
NSLog(@"%p,%p,%@",&blockObj,blockObj, blockObj);
NSLog(@"--------------week--------------");
NSLog(@"globalObj_week:%@",globalObj_week);
NSLog(@"instanceObj_week:%@",instanceObj_week);
NSLog(@"staticObj_week:%@",staticObj_week);
NSLog(@"localObj_week:%@",localObj_week);
NSLog(@"blockObj_week:%@",blockObj_week);
};
__globalObj=nil;
instanceObj=nil;
__staticObj=nil;
localObj=nil;
blockObj=nil;
NSLog(@"-------block被分配堆区后引用的变量---------");
aBlock();
NSLog(@"--------------------------------");
}
执行结果:
-----------原变量-------------
0xee9d8,0x7d06f2d0,global
0xee9f0,0x7d06e680,static
0x7be7221c,0x7d0706e0,instance
0xbff13f90,0x7d06e690,local
0xbff13f88,0x7d06f410,block
---------------------------------
-------block被分配堆区后引用的变量---------
0xee9d8,0x0,(null)
0xee9f0,0x0,(null)
0x7be7221c,0x0,(null)
0x7be74ed8,0x7d06e690,local
0x7be74ef8,0x0,(null)
--------------week--------------
globalObj_week:(null)
instanceObj_week:(null)
staticObj_week:(null)
localObj_week:local
blockObj_week:(null)
--------------------------------
从结果可看出,最后只有localObj未被释放掉,说明在block被分配堆区的时候,局部变量的快照变量会对引用对象再做一次强引用。
成员变量instanceObj不做快照不再做强引用,是因为block已经对self做了一次强引用(与MRC同理),这会引发另外一个问题“循环引用”。
PS:
在arc环境下的blockObj,在block分配到堆区的时候跟MRC环境一样会创建一个可修改的快照,但该快照会对blockObj引用的对象作强引用,原变量blockObj销毁(block分配到堆区后,你所使用的变量blockObj其实是新建的快照,其变量地址与原blockObj变量地址是不一样的),所以,在创建block后,blockObj=nil
其实操作的是快照,结果就把引用对象销毁了blockObj_week:(null)
注意:
这里对oc对象赋值的时候可不能使用objec=@"objec";
的方法赋值,因为@"objec"
是常量(不在堆栈),常量是不遵循arc原则的,即创建后就一直存在,不管有多少强引用,它都不会被销毁。如下代码:
NSString* couObj =@"1";
__weak NSString *wobj=couObj;
couObj=nil;
NSLog(@"%@",wobj);
执行后还是会打印1
根据上面的结论, 因为block在拷贝到堆上的时候,会retain(强引用)其引用的外部变量,那么如果block中如果引用了他的宿主对象self(使用其成员变量或者显式使用了self),如果这时self的成员变量或者属性又对block做持有,那就会引起循环引用,如下:
//ARC:myblock为strong
//MRC:myblock为copy
self.myblock = ^{
[self doSomething];
//或者 NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj);
};
或者
//ARC
_myblock = ^{
[self doSomething];
//或者 NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj);
};
如果self的成员变量或者copy属性对block做了持有,如
@property (nonatomic,copy) dBlock myBlock;
dBlock aBlock = ^{
[self doSomething];
NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj);
};
self.myBlock=aBlock;
//或者_myBlock=[aBlock copy];
这里,copy属性的myBlock 对block做了持有。说到这,要搞清楚,只有在堆区的block即NSMallocBlock才会对里面的外部对象做持有,对外部对象做持有才有可能出现循环引用的问题,如果这里block是没经过copy的,只是NSStackBlock,NSStackBlock会因栈清空而释放掉,则不存在循环引用问题。所以,这里讨论的block是NSMallocBlock。
这里self的myBlock属性对在堆区的aBlock做了持有,而aBlock中有引用了self或者成员变量,所以aBlock又对self做了持有,造成循环。
要解决这个问题就必须打破循环,我们可以从aBlock中对self或者成员变量的引用入手,让aBlock间接使用self,而不对其做retain,代码做如下修改:
__block typeof(instanceObj) week_instanceObj=instanceObj;
__block typeof(self) weekself=self;
dBlock aBlock = ^{
[weekself doSomething];
NSLog(@"%p,%p,%@",&week_instanceObj,week_instanceObj, week_instanceObj);
};
使用__block来间接引用,上面“Block 对 OC对象 的持有”MRC环境分析提过,__block变量在block copy中,使用的是week_instanceObj的快照,该快照引用对象与instanceObj一样,但不做retain,不改变引用计数。
@property (nonatomic,strong) dBlock myBlock;
dBlock aBlock = ^{
[self doSomething];
NSLog(@"%p,%p,%@",&instanceObj,instanceObj, instanceObj);
};
self.myBlock=aBlock;
//或者_myBlock=aBlock;
因为arc下,成员变量默认都是strong,引用外部对象的block会自动分配到堆区,为NSMallocBlock。所以,当成员变量或strong属性对blcok做强引用,便形成循环。
要解决循环引用可使用arc中的弱引用weak来间距引用,代码修改:
__weak NSString* week_instanceObj=instanceObj;
__weak typeof(self) weekself = self;
NSLog(@"week_instanceObj:%p,%p,%@",&week_instanceObj,week_instanceObj, week_instanceObj);
dBlock aBlock = ^{
[weekself doSomething];
NSLog(@"week_instanceObj:%p,%p,%@",&week_instanceObj,week_instanceObj, week_instanceObj);
};
这时可不能跟MRC一样时候__block来间距引用,因为__block对象在分配到堆区时,创建出来取代原变量的快照依然会对self做强引用。__weak对象会在block分配到堆区时创建一份快照,该快照对引用对象依然是弱引用。
block类型:取决于block是否引用了外部变量。未引用外部变量的为NSGlobalBlock
NSGlobalBlock:未引用外部变量,不在堆栈中,独立的程序块。
NSStackBlock:引用外部变量 ,位于栈内存,函数返回后Block将无效,存在于MRC环境,通过copy可转为NSMallocBlock分配到堆区,在ARC下自动转换;
NSMallocBlock:NSStackBlock copy得来, 位于堆内存。
对外部变量的引用,除了静态变量 、全局变量、NSStackBlock中的block变量外,会对引用的变量做一份快照,保存创建快照时相同的值,oc对象(指针变量)保存相同引用地址。
关于将block copy到堆区后(或者arc自动copy),对引用的变量的持有变化:
在MRC下,只有局部变量的引用对象计数会+1(快照retain),对成员变量或self的引用也会使self计数+1,__block变量在copy后会取代原变量,但不会再对引用对象做retain。
在ARC下,局部变量会再强引用一次(快照也是强引用),对__block变量不改变强引用个数(快照变量会取代原变量),对成员变量或self的直接引用会使对self再强引用一次。
不论是MRC还是ARC下,当block分配到堆区后,其创建出来的快照的释放转为自动释放,只要block还在,其快照的引用就在。
标签:
原文地址:http://blog.csdn.net/bluefish89/article/details/51142079