block资深解读【精品】

字数 1763阅读 29

block也叫匿名函数,也叫闭包,很多语言都有闭包的概念,block是c语言的闭包演化而来
block的格式一般是(void)(^blockname)(NSString *param); 或者 ^(void)(NSString *param){}
block作为参数,property....http://fuckingblocksyntax.com/

block的内存
block有三种,分别存于不同的内存域,分别是NSGlobalBlock,NSStackBlock, NSMallocBlock
默认是在NSGlobalBlock比如下面的形式,因为他初始化在栈上,而没有与其他内存发生任何交互,所以当代码结束时,block自动释放了,跟一个方法体没有区别,最后OC把他放到TEXT端。
BlkSum blk1 = ^ long (int a, int b) {
return a + b;
};
NSLog(@"blk1 = %@", blk1);

第二种是NSStackBlock,这个是在栈上的内存,block创建时在栈上,并且用到了栈上定义的其他内存,所以这类block暂时在栈上,对外部变量也只有读的权限,没有写权限,因为这个时候只是copy了一份局部变量的值到栈上
int base = 100;
BlkSum blk2 = ^ long (int a, int b) {
return base + a + b;
};
NSLog(@"blk2 = %@", blk2); // blk2 = <NSStackBlock: 0xbfffddf8>

第三种是NSMallocBlock ,这个block是把栈上的block copy到了堆上
BlkSum blk3 = [[blk2 copy] autorelease];
NSLog(@"blk3 = %@", blk3); // blk3 = <NSMallocBlock: 0x902fda0>

然而变量都有其作用域,那么跨作用域的变量值又怎么被block调用和修改呢呢?
使用__block修饰符修改变量的作用域,而不是copy一份到栈上供block使用,
对于static静态变量,由于其地址是固定的,所以block可以对其进行修改,__block修改基本类型的局部变量等效与静态变量
@property (nonatomic, copy) void(^myBlock)(void);
MyClass* obj = [[[MyClass alloc] init] autorelease];
self.myBlock = ^ { [obj doSomething]; };
这个主意几个点,
1,定义block属性需要用copy,这样会把block对象从堆上移动到栈内存上,
2,block引用了obj会让obj的内存引用计数加1

block很容易出现retain cycle的,下面是常见的场景之一,reques引用了block,而block里面又持有request的引用,很多时候我们在self中copy了一个block,在block中又出现调用self的情况,这个时候就出现了retain cycle,解决办法是使用__block或者__weak打断强引用即可,其实以前争论的该用__block还是__weak其实是没有结果的,因为他们都能打断强引用,只要阻止了在block中调用外部OC对象并让引用计数增加就行了。出现retaincycle的情况不局限于两个object,有时候可能涉及到很多的Object,那么解决办法也是同理的,只要知道block调用外部OC变量会使其引用计数加1,还要遵守在block中调用外部变量时打断强引用就行了。
ASIHTTPRequest request = [ASIHTTPRequest requestWithURL:url];
[request setCompletionBlock:^{
NSString
string = [request responseString];
}];
或者:
elf.myBlock = ^ { [self doSomething]; };

还有个非常大的神坑,这次不会retaincycle,而是会crash,在携程代码中经常看到的坑如下:请注意,dispatchBlock跟OC定义的block又是两个概念,dispatch本身是一种安全的block,在进入block时会retain一次block的持有者也就是self,但是当碰到__block参数时,就不会retainself, 而dispatch的block又不属于self的属性,所以当block的任务还没有完成这个时候self已经释放了,那么dispatch的block就挂了,正常使用即可,但是比较坑的是,很多秀才在碰到block的地方就用__block,将Block作为参数传给dispatch_async时,系统会将Block拷贝到堆上,如果Block中使用了实例变量,还将retain self,因为dispatch_async并不知道self会在什么时候被释放,为了确保系统调度执行Block中的任务时self没有被意外释放掉,dispatch_async必须自己retain一次self,任务完成后再release self。但这里使用__block,使dispatch_async没有增加self的引用计数,这使得在系统在调度执行Block之前,self可能已被销毁,但系统并不知道这个情况,导致Block被调度执行时self已经被释放导致crash
__block kkProducView* weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.xx = xx;
});

防止retainCycle总结:
真理:
1,block引用外部基本数据类型使用__block.
2,block引用外部对象使用__weak.
3,block作为某对象的成员变量时使用copy,把block的内存复制到对上.//要不然block跑一遍就释放了

改正:
1,针对retainCycle问题,在arc之前无论对象还是基本数据类型用__block就不会错,但是到了arc的世界,一切都变了,arc下对象默认是strong的,那么在block里面调用__block修饰的对象会使对象的引用计数加1,如果不用__block修饰也会加1,使用__weak修饰就不会加1了,因为__weak把对象转成weak的,在block里面调用外部对象,引用计数不会加1.----理由:http://wufawei.com/2013/06/block-retain-cycle/

二,block结构体
struct
Block_literal_1 {

void
*isa;

int
flags;

int reserved;
void (*invoke)(void *, …);//是一个函数指针,它指向的是Block被转换成函数的地址
struct
Block_descriptor_1 {

unsigned long int
reserved;

unsigned long int
size;

// optional helper functions

void (*copy_helper)(void *dst, void *src); // IFF (1<<25)

void (*dispose_helper)(void *src); // IFF (1<<25)

// required ABI.2014.5.25

const char *signature; // IFF (1<<30)

     } *descriptor;
// imported variables

};
1,isa:证明block是一个OC特性的对象,isa指针指向的是对象的地址,这个地址有两类,分别表示了存放在两种内存的block,
_NSConcreteStackBlock/_NSConcreteGlobalBlock在没有开启ARC的情况下,如果Block中包含有局部变量则isa被初始化为前者,
否则就被初始化为后者。而当ARC开启后,如果Block中包含有局部变量则isa被初始化为 _NSConcreteMallocBlock ,否则就被初始化为 _NSConcreteGlobalBlock
2,invoke是一个函数指针,它指向的是我自己Block被转换成函数的地址
3,最后的imported variables部分是Block需要访问的外部的局部变量,他们在编译就会被拷贝到Block中,这样一来Block就是成为一个闭包了
4,闭包的解释:闭包是可以包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)

阅读参考:
http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/ 正确使用block

推荐阅读更多精彩内容