Block
Block 对象与一般的类实例对象有所不同,一个主要的区别就是分配的位置不同,block 默认在栈上分配,一般类的实例对象在堆上分配。在 MRC 中使用 Block_copy() 和 Block_release() 将 block 变量拷贝到堆内存。在 ARC 中如果 block 参数和返回值中都没有 __strong id ,那么会自动加上 copy 和 release。copy 会将 block 变量拷贝到堆内存中。
_NSConcreteStackBlock
:位于栈内存,函数返回后Block将无效_NSConcreteMallocBlock
:位于堆内存_NSConcreteGlobalBlock
:没有引用外部变量
Block内引用外部变量的问题
打印注释 : [指针地址 | 指针指向内存地址 | 对象引用计数]
强引用:
Block 强引用所引用变量。
- (void) blockVariableStrongReferenceTest {
NSObject *obj = [[NSObject alloc] init];
BLog(@"%@",obj); // [0x7fff543d0c98(栈) | 0x7fcb1bd22390(堆) | 1]
void(^testBlock)() = ^(){
BLog(@"%@",obj);
};
testBlock(); // [0x7fcb1c903fb0(堆) | 0x7fcb1bd22390(堆) | 2]
// Block 外部尝试将 obj 置为 nil
obj = nil; // ARC 中 obj = nil 相当于 MRC 中的 [obj release]
testBlock(); // [0x7fcb1c903fb0(堆) | 0x7fcb1bd22390(堆) | 1]
}
分析:
方法内部的 obj 变量在栈中,变量内存地址 0x7fff543d0c98,所指堆内存地址 0x7fcb1bd22390,引用计数 1。
Block 中 obj 指针地址变化是因为 block 对象 copy 到堆中,block 对象强引用 obj,obj 也会复制到堆中。此时两个变量(一堆一栈)同时拥有一块堆内存的所有权,obj 引用计数 2。
上面的例子不会造成循环引用,因为 obj 并不是强引用 testBlock 对象。但是,在平时开发过程中,难免会遇到 当前对象强引用 block 对象 :
@property (nonatomic , copy) void (^blockT) (void);
- (void) testStrongRef {
__weak typeof(self) weakSelf = self;
self.blockT = ^{
// 正常
NSLog(@"%@",weakSelf);
};
self.blockT();
}
- (void) testBlock {
self.blockT = ^{
// 系统会提示造成循环引用
NSLog(@"%@",self);
};
self.blockT();
}
弱引用:
- (void)blockVariableWeakReferenceTest {
NSObject *obj = [[NSObject alloc] init];
BLog(@"%@",obj); // [0x7fff543d0c98(栈) | 0x7fcb1bd2fee0(堆) | 1]
__weak NSObject *weakObj = obj;
BLog(@"%@", weakObj); // [0x7fff543d0c90(栈)) | 0x7fcb1bd2fee0(堆) | 1]
void(^testBlock)()= ^(){
BLog(@"%@",weakObj);
};
testBlock(); // [0x7fcb1bc072b0(堆) | 0x7fcb1bd2fee0(堆) | 1]
obj = nil;
testBlock(); // [0x7fcb1bc072b0(堆) | 0x0(堆) | 0]
}
分析:
第一次调用 testBlock() 会发现 weakObj 的指针地址已经转移到堆上。
使用 __weak 限定符确实可以规避循环引用的问题,但是仔细阅读代码会发现其实还隐藏着另一个问题。 testBlock 弱引用了 weakObj,但是 weakObj 只是 obj 的弱引用,weakObj 只是和 obj 指向同一块堆内存,但是这块内存是否释放与 weakObj 没有关系。那么问题就来了,如果在 testBlock 执行之前,obj 被释放了(__weak 是安全释放 = nil),就会导致 testBlcok 引用一个 nil 对象,虽然不会造成野指针问题,但是达不到我们预期的效果。所以,这种情况下,testBlock 又需要强引用 weakObj :
// 多线程时Block生命周期内对象安全
- (void)blockVariableMutiThreadTest {
NSObject *obj = [[NSObject alloc]init]; // [0x7fff51888c98(栈) | 0x7f9413c1c040(堆) | 1]
__weak NSObject *weakObj = obj; // [0x7fff51888c90(栈) | 0x7f9413c1c040(堆) | 1]
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong NSObject *strongObj = weakObj;
sleep(3);
// 此时 obj = nil; 已经调用
BLog(@"weakObj - block", weakObj); // [0x7f9413d9a880(堆) | 0x7f9413c1c040(堆) | 1]
BLog(@"strongObj - block", strongObj); // [0x1187e2e08(栈) | 0x7f9413c1c040(堆) | 2]
});
sleep(1);
obj = nil;
// 此时 strongObj 还未释放,因为 block 还未执行完
BLog(@"weakObj-1", weakObj); // [0x7fff51888c90(栈) | 0x7f9413c1c040(堆) | 1]
sleep(4);
// 此时 strongObj 已经释放
BLog(@"weakObj-2", weakObj); // [0x7fff51888c90(栈) | 0x0(堆) | 0]
}
这样只能相对的减少 blcok 使用中引用对象提前释放带来的影响。如果引用对象在 block 执行之前就被释放,那么我们暂时是无能为力,只能加条件判定了。
__block:
__block 限定符在 MRC 中不会保留对象(不会调用 -retain),在 ARC 中会。
- (void)blockVariable {
NSObject *obj = [[NSObject alloc]init]; // [0x7fff5dd2ec78(栈) | 0x7fa082661a00(堆) | 1]
__block NSObject *blockObj = obj; // [0x7fff5dd2ec70(栈) | 0x7fa082661a00(堆) | 2]
obj = nil;
void(^testBlock)() = ^(){
BLog(@"内部blockObj - block",blockObj); // [0x7fa084905838(堆) | 0x7fa082661a00(堆) | 1]
NSObject *obj2 = [[NSObject alloc]init]; // [0x7fff5dd2eba8(栈) | 0x7fa082661a00(堆) | 1]
blockObj = obj2;
BLog(@"blockObj - block",blockObj); // [0x7fa084905838(堆) | 0x7fa082661a00(堆) | 1]
};
testBlock();
BLog(@"外部blockObj -3",blockObj); // [0x7fa084905838(堆) | 0x7fa084916660(堆) | 1]
}
关于 block 的疑问:
block 是异步的吗?
不是,需要自己开线程。