Block
Block内引用外部变量的问题
// 强引用
#define BLog(prefix,obj) {NSLog(@"位置和指针变量名:%@ ,指针内存地址:%p, 指针值:%p ,指向的对象:%@ ",prefix,&obj,obj,obj);}
- (void)blockVariableStrongReferenceTest { NSLog(@"\n"); NSObject *obj = [[NSObject alloc] init]; BLog(@"StrongRef obj",obj); void(^testBlock)()= ^(){ BLog(@"StrongRef in block",obj); }; testBlock(); // Block外部尝试将obj置为nil obj = nil; testBlock(); // 第二次调用block }
运行结果
位置和指针变量名:StrongRef obj ,指针内存地址:0x7fff543d0c98, 指针值:0x7fcb1bd22390 ,指向的对象:<NSObject: 0x7fcb1bd22390> 位置和指针变量名:StrongRef in block ,指针内存地址:0x7fcb1c903fb0, 指针值:0x7fcb1bd22390 ,指向的对象:<NSObject: 0x7fcb1bd22390> 位置和指针变量名:StrongRef in block ,指针内存地址:0x7fcb1c903fb0, 指针值:0x7fcb1bd22390 ,指向的对象:<NSObject: 0x7fcb1bd22390
分析
// 弱引用 - (void)blockVariableWeakReferenceTest { NSLog(@"\n"); NSObject *obj = [[NSObject alloc] init]; BLog(@"StrongRef obj",obj); __weak NSObject *weakObj = obj; BLog(@"WeakRef weakObj", weakObj); void(^testBlock)()= ^(){ BLog(@"weakObj in block",weakObj); }; testBlock(); obj = nil; testBlock(); }
运行结果
位置和指针变量名:StrongRef obj ,指针内存地址:0x7fff543d0c98, 指针值:0x7fcb1bd2fee0 ,指向的对象:<NSObject: 0x7fcb1bd2fee0> 位置和指针变量名:WeakRef weakObj ,指针内存地址:0x7fff543d0c90, 指针值:0x7fcb1bd2fee0 ,指向的对象:<NSObject: 0x7fcb1bd2fee0> 位置和指针变量名:weakObj in block ,指针内存地址:0x7fcb1bc072b0, 指针值:0x7fcb1bd2fee0 ,指向的对象:<NSObject: 0x7fcb1bd2fee0> 位置和指针变量名:weakObj in block ,指针内存地址:0x7fcb1bc072b0, 指针值:0x0 ,指向的对象:(null)
分析:
Block生命周期内的对象安全
在block中__weak声明的指针去引用对象 可以避免循环引用的问题,但是当外部对象被释放了,block 内部会访问不到这个对象. 这种问题如何解决呢?先来看一段代码:
//多线程时Block生命周期内对象安全 - (void)blockVariableMutiThreadTest { NSObject *obj = [[NSObject alloc]init]; //obj强引用,<NSObject: 0x7f9413c1c040>对象引用计数+1,=1 BLog(@"obj", obj); __weak NSObject *weakObj = obj;//weakObj弱引用,<NSObject: 0x7f9413c1c040>对象引用计数不变,=1 BLog(@"weakObj-0", weakObj); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ __strong NSObject *strongObj = weakObj; //strongObj强引用,<NSObject: 0x7f9413c1c040>对象引用计数+1,=2 sleep(3); BLog(@"weakObj - block", weakObj); BLog(@"strongObj - block", strongObj); }); sleep(1); obj = nil; //obj被置为nil,<NSObject: 0x7f9413c1c040>对象引用计数-1,=1 BLog(@"weakObj-1", weakObj); //没被释放 sleep(4); //block在异步线程中执行完毕(在另一块内存中执行),block内存被释放,<NSObject: 0x7f9413c1c040>对象引用计数-1,=0;ARC开始把0x7f9413c1c040对象内存回收,把弱引用weakObj置为nil BLog(@"weakObj-2", weakObj); }
执行结果如下:
位置和指针变量名:obj ,指针内存地址:0x7fff51888c98, 指针值:0x7f9413c1c040 ,指向的对象:<NSObject: 0x7f9413c1c040> 位置和指针变量名:weakObj-0 ,指针内存地址:0x7fff51888c90, 指针值:0x7f9413c1c040 ,指向的对象:<NSObject: 0x7f9413c1c040> 位置和指针变量名:weakObj-1 ,指针内存地址:0x7fff51888c90, 指针值:0x7f9413c1c040 ,指向的对象:<NSObject: 0x7f9413c1c040> 位置和指针变量名:weakObj - block ,指针内存地址:0x7f9413d9a880, 指针值:0x7f9413c1c040 ,指向的对象:<NSObject: 0x7f9413c1c040> 位置和指针变量名:strongObj - block ,指针内存地址:0x1187e2e08, 指针值:0x7f9413c1c040 ,指向的对象:<NSObject: 0x7f9413c1c040> 位置和指针变量名:weakObj-2 ,指针内存地址:0x7fff51888c90, 指针值:0x0 ,指向的对象:(null)
总结:
多线程的时候,在 block 外部用__weak声明的变量指向一个对象, 通过把__weak声明的变量值赋值给block内部的__strong变量```,实现在block内对该对象进行强引用,这样可以在block生命周期内保留该对象不被释放,在block生命周期结束后,对象内存被释放。
block修改外部变量 __block变量
- (void)blockVariable { NSObject *obj = [[NSObject alloc]init]; //指向的对象:<NSObject: 0x7fb4039063b0> BLog(@"obj",obj); __block NSObject *blockObj = obj; //blockObj指向对象:0x7fb4039063b0 obj = nil; BLog(@"blockObj -1",blockObj); //blockObj,0x7fff52365c90 void(^testBlock)() = ^(){ BLog(@"blockObj - block",blockObj); //blockObj,0x7fb401c7d7f8,指向的对象:<NSObject: 0x7fb4039063b0> NSObject *obj2 = [[NSObject alloc]init]; // obj2 ,0x7fff52365bc8,指向的对象:<NSObject: 0x7fb401c83c40> BLog(@"obj2",obj2); blockObj = obj2; BLog(@"blockObj - block",blockObj); //blockObj,0x7fb401c7d7f8,指向对象:<NSObject: 0x7fb401c83c40> }; NSLog(@"%@",testBlock); //block 的地址 <__NSMallocBlock__: 0x7fb401c07d20> BLog(@"blockObj -2",blockObj); //blockObj地址发生变化,0x7fb401c7d7f8,/指向的对象:<NSObject: 0x7fb4039063b0> testBlock(); BLog(@"blockObj -3",blockObj); //blockObj,0x7fb401c7d7f8,指向对象:<NSObject: 0x7fb401c83c40> }
分析:
关于 block访问外部变量原理
在oc,在block中直接访问外部变量,访问的是外部变量的copy。用clang后将 .m翻译为.cpp文件后发现,外部函数是通过传值方式将变量值传给block(block结构体、block最终要执行的函数代码). 使用了__block后,外部函数是通过指针传递,将变量传递到 block 内,所以可以修改变量值.
Block在内存中的位置
Block作为C语言的扩展,并不是高新技术,和其他语言的闭包或lambda表达式是一回事。需要注意的是由于Objective-C在iOS中不支持GC机制,使用Block必须自己管理内存,而内存管理正是使用Block坑最多的地方,错误的内存管理 要么导致return cycle内存泄漏要么内存被提前释放导致crash。 Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外、词法作用域以内的外部变量的值。换句话说,Block不仅 实现函数的功能,还能携带函数的执行环境。
可以这样理解,Block其实包含两个部分内容:
根据Block在内存中的位置分为三种类型:NSConcreteGlobalBlock,NSConcreteMallocBlock, _NSConcreteStackBlock。
_NSConcreteGlobalBlock:
Blocks that don‘t capture any variables are global blocks. Since all instances of the block are the same, the compiler can just allocate one copy statically for the life of the program。
NSConcreteStackBlock 和 NSConcreteMallocBlock:
Blocks that capture variables (closures) are either stack or heap (malloc) blocks. Blocks start out on the stack, as stack blocks. When a stack block is copied for the first time, it is moved to the heap. Copying a heap block does not create another copy; but simply retains it. 在开启 ARC 时,大部分情况下编译器通常会将创建在栈上的 block 自动拷贝到堆上。
ARC中自动copy block的例子
- (void)blockObjectInMemory { // global block void (^globalBlockInMemory)(int number) = ^(int number){ printf("%d \n",number); }; globalBlockInMemory(90); BLog(@"global block %@", globalBlockInMemory); // malloc block int outVariable = 100; void (^mallocBlockInMemory)(int number) = ^(int number){ printf("%d \n",outVariable+number); }; BLog(@"stackBlock block %@", mallocBlockInMemory); // ARC 自动将栈中block拷贝到堆上 }
运行结果:
位置和指针变量名:global block %@ ,指针内存地址:0x7fff5b422c78, 指针值:0x1047e01f0 ,指向的对象:<__NSGlobalBlock__: 0x1047e01f0> 位置和指针变量名:stackBlock block %@ ,指针内存地址:0x7fff5b422c68, 指针值:0x7fe6849085f0 ,指向的对象:<__NSMallocBlock__: 0x7fe6849085f0>
- (id)returnBlock { int outVariable = 100; void (^mallocBlockInMamory)(void) = ^(void){ NSLog(@"in block"); }; BLog(@" block ", mallocBlockInMamory); return mallocBlockInMamory; } - (void)blockInmemory { id block = [self returnBlock]; BLog(@"a block %@", block); }
运行结果:
位置和指针变量名: block ,指针内存地址:0x7fff516a9c30, 指针值:0x10e559250 ,指向的对象:<__NSGlobalBlock__: 0x10e559250> 位置和指针变量名:a block %@ ,指针内存地址:0x7fff516a9c78, 指针值:0x10e559250 ,指向的对象:<__NSGlobalBlock__: 0x10e559250>
在没有ARC之前,由于ARC 自动将栈中block拷贝到堆上,所以当returnBlock函数退出,在栈中内存释放后,仍然可以访问到block对象。
ARC 中需要手动拷贝Block的例子
在以下情形中, block 会从栈拷贝到堆:
其他情况需要手动拷贝。
- (void)stackBlockInMemory { NSArray *array = [self getBlockArray]; id block = array[0]; BLog(@"block %@", block); } - (id)getBlockArray { int val = 10; return [[NSArray alloc] initWithObjects: ^{NSLog(@"value:%d", val);}, ^{NSLog(@"value:%d", val);}, nil]; }
程序会报EXC_BAD_ACCESS ,getBlockArray返回的数组里面的 block 是不可访问的。
手动copy后,block拷贝到堆上,getBlockArray函数返回的栈帧被销毁后,仍可以访问堆中的block拷贝。
- (id)getBlockArray { int val = 10; return [[NSArray alloc] initWithObjects: [^{NSLog(@"value:%d", val);} copy], [^{NSLog(@"value:%d", val);} copy], nil]; }
Block中造成内存泄漏的一些场景
推荐文章:
http://www.tanhao.me/pieces/310.html/
How blocks are implemented (and the consequences
原文:http://www.cnblogs.com/sueZheng/p/4954938.html