iOS Block
2017-07-09 本文已影响6人
ThaiLanKing
block类型
根据Block在内存中的位置,系统把Block分为3类:NSGlobalBlock,NSStackBlock, NSMallocBlock;
- NSGlobalBlock(_NSConcreteGlobalBlock):位于内存全局区
只要没有对block外的变量进行引用,创建后的block就是NSGlobalBlock。相当于普通的函数,在内存中存在于程序代码段。
- NSStackBlock(_NSConcreteStackBlock):位于内存栈区
只要block对外部的变量进行引用的话,默认创建的就是NSStackBlock。
- NSMallocBlock(_NSConcreteMallocBlock):位于内存堆区
只有对NSStackBlock进行copy才会得到NSMallocBlock。NSMallocBlock在内存中存在堆中。在ARC的情况下会自动对NSStackBlock进行copy
//在ARC下很少出现NSStackBlock,因为 arc 下会自动往堆内存中copy,只有weak的时候才不会copy
int (^globalBlock)(int) = ^(int a){return a*a};
int v = 10;
int (^mallocBlock)(int) = ^(int a){return a*v};
__weak int (^stackBlock)(int) = ^(int a){return a*v};
NSLog(@"stackBlock %@", stackBlock);
对block自身内存的管理
- Block_copy与copy等效,Block_release与release等效;
- 对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
- NSGlobalBlock:retain、copy、release操作都无效;
- NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlock copy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。
- NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
- 尽量不要对Block使用retain操作。
- Block被另一个Block使用时,另一个Block被copy到堆上时,被使用的Block也会被copy。但作为参数的Block是不会发生copy的。
- 当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。这些方法会在内部对传递进来的 block 调用 copy 或 _Block_copy 进行拷贝。
循环引用
-
__block
:在非ARC中使用,NSMallocBlock类型的block不会对__block修饰的的变量引用计数+1,从而消除循环引用;在ARC中使用__block无效 -
__weak
:在ARC中使用,作用和__block一样,从而消除循环引用;在非ARC中不可以使用__weak;
系统API的循环引用
系统的某些block api中,UIView的block版本写动画时不需要考虑,但也有一些api需要考虑:
- 所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题,比如这些:
[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded];}];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz;}];
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * notification) {
self.someProperty = xyz; }];
- 但如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:
__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
} );
类似的:
__weak __typeof__(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
__typeof__(self) strongSelf = weakSelf;
[strongSelf dismissModalViewControllerAnimated:YES];
}];
self --> _observer --> block --> self 显然这也是一个循环引用。