block的分类
1、NSGlobalBlock
位于全局区;
在Block内部不使用外部变量,或者只使用静态变量和全局变量;
2、NSMallocBlock
位于堆区;
在Block内部使用局部变量或者OC属性,并且赋值给强引用或者Copy修饰的变量
3、NSStackBlock
位于栈区;
与MallocBlock一样,可以在内部使用局部变量或者OC属性。但是不能赋值给强引用或者Copy修饰的变量。
探索block
int a = 10;
void (^ block)(void) = ^{
NSLog(@"Cooci - %d",a);
};
上面是堆block,因为引用了外部变量,且默认被强引用,注意=
。
int a = 10;
void (__weak ^ block)(void) = ^{
NSLog(@"Cooci - %d",a);
};
加入__weak
之后变为弱引用,因此是栈block。
面试题
#pragma mark - block 捕获外部变量-对外部变量的引用计数处理
- (void)blockDemo2{
NSObject *objc = [NSObject new];
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 被引用一次,所以引用计数为1
// block在编译时候在栈区,运行时候需要拷贝到堆区,引用计数+1
// block捕获局部变量,引用计数+1
void(^strongBlock)(void) = ^{
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
//这里是栈block,不需要拷贝到堆区,因此引用计数不变
//捕获局部变量,引用计数+1
void(^__weak weakBlock)(void) = ^{ // + 1
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
NSLog(@" %@ \n %@ \n %@ \n",strongBlock,weakBlock,mallocBlock);
}
打印:
---1
---3
---4
---5
<__NSGlobalBlock__: 0x10b9021b0>
<__NSStackBlock__: 0x7ff7b45fd2c0>
<__NSMallocBlock__: 0x6000009434e0>
小结
根据打印结果,也验证了 全局block存在全局区;栈block存在栈区;堆block存在堆区。
- (void)block_copy_usingBlock {
void (^usingBlock)(id obj, NSUInteger idx, BOOL *stop) = ^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop){
};
NSArray *array = @[usingBlock];
[array enumerateObjectsUsingBlock:usingBlock];
NSLog(@"%@", array.firstObject);
}
打印:
2021-10-07 16:11:52.358633+0800 [36477:3894313] <__NSGlobalBlock__: 0x105fa8180>
#pragma mark - 内存拷贝的理解
- (void)blockDemo1{
int a = 0;
void(^ __weak weakBlock)(void) = ^{
NSLog(@"-----%d", a);
};
//重写block,LGBlock,便于对block的invoke进行修改,也就是hook到invoke
struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
// 深浅拷贝,如果不做copy会崩溃
id __strong strongBlock = [weakBlock copy];
blc->invoke = nil;
void(^strongBlock1)(void) = strongBlock;
strongBlock1();
// 预警什么 ???
}
打印:
2021-10-07 16:09:35.110759+0800 [36440:3891656] ----- 0
block 堆栈释放差异
- (void)blockDemo3{
NSObject *a = [NSObject alloc];//a在堆区
void(^__weak weakBlock)(void) = nil;//weakBlock 在栈区
{
// 栈区
void(^__weak strongBlock)(void) = ^{
NSLog(@"---%@", a);
};
weakBlock = strongBlock;
NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
}
打印:
2021-10-07 16:22:00.079784+0800 [36553:3901246] 1 - <__NSStackBlock__: 0x7ffee94f5390> - <__NSStackBlock__: 0x7ffee94f5390>
2021-10-07 16:22:00.079889+0800 [36553:3901246] ---(null) //因为strongBlock所拥有的内存空间已经不存在了,那么strongBlock里面的所有内容也都不存在了,所以对a的引用也就不存在了。
分析: 上面的 weakBlock 和 strongBlock 都是栈block
对比blockDemo3:
- (void)blockDemo4{
// 压栈 内存平移
NSObject *a = [NSObject alloc];
void(^__weak weakBlock)(void) = nil;
{
// 堆区
void(^ strongBlock)(void) = ^{
NSLog(@"---%@", a);
};
weakBlock = strongBlock;
NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
}
打印:
崩溃
2021-10-07 16:39:48.781468+0800[36676:3912167] 1 - <__NSMallocBlock__: 0x600001be16b0> - <__NSMallocBlock__: 0x600001be16b0>
(lldb)
- (void)blockDemo5{
// 压栈 内存平移
NSObject *a = [NSObject alloc];
void(^strongBlock1)(void) = nil;
{
// 堆区
void(^ strongBlock)(void) = ^{
NSLog(@"---%@", a);
};
strongBlock1 = strongBlock;
NSLog(@"1 - %@ - %@",strongBlock1,strongBlock);
}
weakBlock();
}
打印:
2021-10-07 16:48:20.985483+0800 [36780:3919533] 1 - <__NSMallocBlock__: 0x60000190cf90> - <__NSMallocBlock__: 0x60000190cf90>
2021-10-07 16:48:20.985592+0800 [36780:3919533] ---<NSObject: 0x6000015542f0>
小结:如果是堆block在底层会对block进行一次copy,因此objc在堆block中copy一次、捕获一次,引用计数最终加2。区分堆栈区,明确变量的作用域生命周期。
block拷贝到堆block
1、手动Copy
2、Block作为返回值
3、被强引用或者Copy修饰
4、系统柜API包含usingblock
栈Block -> 堆Block 源码
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
// KC重点提示: 这里是核心重点 block的拷贝操作: 栈Block -> 堆Block
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy. -> heap
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount -- 对象 isa 联合体位于
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
block flag标识
Block 捕获外界变量的操作
// Block 捕获外界变量的操作
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
default:
break;
}
}
循环引用
两个对象时间相互引用,都在等待对方释放,最终双方都不释放,造成循环引用,内存泄漏。
解决循环引用的方式
weak-strong-dance
__weak typeof(self) weakSelf = self;
self.block = ^(void){
__strong __typeof(weakSelf)strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.block();
手动置为nil
// self -> block -> vc -> self
__block ViewController *vc = self;
self.block = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;
});
};
self.block();
使用参数
self.block = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.block(self);
面试题
1、第一题
- (void)blockWeak_static {
// weakSelf 和 self 是同一片内存空间
__weak typeof(self) weakSelf = self;
staticSelf_ = weakSelf;
// staticSelf_ -> weakSelf -> self
}
以上代码会造成循环引用。weakSelf 和 self 是同一片内存空间,staticSelf_是全局静态变量,因此self不会释放。
2、第二题
- (void)block_weak_strong {
__weak typeof(self) weakSelf = self;
self.doWork = ^{
__strong typeof(self) strongSelf = weakSelf;
weakSelf.doStudent = ^{
NSLog(@"%@", strongSelf);
};
weakSelf.doStudent();
};
self.doWork();
}
以上代码会造成循环引用,因为doStudent的block对strongSelf进行了捕获等操作,引用计数会增加3。