Block

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 是异步的吗?
不是,需要自己开线程。


block 底层分析
iOS Block捕获外部变量和ARC自动拷贝block
推荐 : 深入理解 block

posted @ 2017-04-11 10:38  上水的花  阅读(272)  评论(8)    收藏  举报