[TOC]
本文主要介绍
1.block的基本类型
2.常见问题以及解决方式:循环引用
3.常见面试题
block的分类
三种: 堆上 栈上 全局
int a = 10;
void (^block)(void) = ^{
};
NSLog(@"%@",block);//<__NSGlobalBlock__: 0x1015f9100>
int a = 10;
void (^block)(void) = ^{
NSLog(@"lr - %d",a);
};
NSLog(@"%@",block);//<__NSMallocBlock__: 0x60000387c0f0>
int a = 10;
void (__weak ^block)(void) = ^{
NSLog(@"lr - %d",a);
};
NSLog(@"%@",block);//<__NSStackBlock__: 0x7ffee1c29428>
GlobalBlock
- 位于全局区
- 在Block内部不适用外部变量,或者只使用静态变量和全局变量
MallocBlock
- 位于堆区
- 在Block内部使用局部变量或者OC属性,并且赋值给强引用或者Copy修饰的额变量
StackBlock
- 位于栈区
- 与MallocBlock一样,可以在内部使用局部变量或者OC属性。但是不能赋值给强引用或者Copy修饰的变量
Block拷贝到堆上的情况:
- 手动copy
- Block作为返回值
- 被强引用 or Copy修饰
- 系统API包含usingBlock
练习
1.block捕获外部变量-对外部变量的引用计数处理
#pragma mark - block 捕获外部变量-对外部变量的引用计数处理
- (void)blockDemo2{
NSObject *objc = [NSObject new];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
// block 底层源码
// 捕获 + 1
// 堆区block
// 栈 - 内存 -> 堆 + 1
void(^strongBlock)(void) = ^{ // 1 - block -> objc 捕获 + 1 = 2
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
void(^__weak weakBlock)(void) = ^{ // + 1
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
}
结果
1
---3
---4
---5
2.内存拷贝的理解
先重写block底层实现 用于后面操作block
NSObject+Block.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(*LGBlockCopyFunction)(void *, const void *);
typedef void(*LGBlockDisposeFunction)(const void *);
typedef void(*LGBlockInvokeFunction)(void *, ...);
enum {
BLOCK_HAS_COPY_DISPOSE = (1 << 25),
BLOCK_HAS_CTOR = (1 << 26), // helpers have C++ code
BLOCK_IS_GLOBAL = (1 << 28),
BLOCK_HAS_STRET = (1 << 29), // IFF BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30),
};
struct _LGBlockDescriptor1 {
uintptr_t reserved;
uintptr_t size;
};
struct _LGBlockDescriptor2 {
// requires BLOCK_HAS_COPY_DISPOSE
LGBlockCopyFunction copy;
LGBlockDisposeFunction dispose;
};
struct _LGBlockDescriptor3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
// 底层
struct _LGBlock {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
// 函数指针
LGBlockInvokeFunction invoke;
struct _LGBlockDescriptor1 *descriptor;
};
static struct _LGBlockDescriptor3 * _LG_Block_descriptor_3(struct _LGBlock *aBlock) {
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return nil;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct _LGBlockDescriptor1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
desc += sizeof(struct _LGBlockDescriptor2);
}
return (struct _LGBlockDescriptor3 *)desc;
}
static const char *LGBlockTypeEncodeString(id blockObj) {
struct _LGBlock *block = (__bridge void *)blockObj;
return _LG_Block_descriptor_3(block)->signature;
}
@interface NSObject (Block)
// 打印Block 签名
- (NSMethodSignature *)getBlcokSignature;
// 打印Block 签名
- (NSString *)getBlcokSignatureString;
// 调用block
- (void)invokeBlock;
@end
NS_ASSUME_NONNULL_END
NSObject+Block.m
#import "NSObject+Block.h"
@implementation NSObject (Block)
- (NSString *)getBlcokSignatureString {
NSMethodSignature *signature = self.getBlcokSignature;
if (signature) {
NSMutableString *blockSignature = [NSMutableString stringWithFormat:@"BlcokSignature: return type: %s, ", [signature methodReturnType]];
for (int i = 0; i < signature.numberOfArguments; i++) {
[blockSignature appendFormat:@"argument number: %d, argument type: %s ", i+1, [signature getArgumentTypeAtIndex:i]];
}
return blockSignature;
}
return nil;
}
- (NSMethodSignature *)getBlcokSignature {
if ([self isKindOfClass:NSClassFromString(@"__NSMallocBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSStackBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSGlobalBlock__")]) {
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:LGBlockTypeEncodeString(self)];
return signature;
}
return nil;
}
- (void)invokeBlock {
NSMethodSignature *signature = self.getBlcokSignature;
if (signature) {
// 动态的消息转发
NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:signature];
[blockInvocation invokeWithTarget:self];
}
}
// block OC 对象
//- (NSString *)description {
// if ([self isKindOfClass:NSClassFromString(@"__NSMallocBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSStackBlock__")] || [self isKindOfClass:NSClassFromString(@"__NSGlobalBlock__")]) {
// //签名
// return [NSString stringWithFormat:@"<%@:%p>--%@", self.class, self, [self getBlcokSignatureString]];
// }
// return [NSString stringWithFormat:@"<%@:%p>", self.class, self];
//}
@end
示例
- (void)blockDemo1{
int a = 0;
void(^ __weak weakBlock)(void) = ^{
NSLog(@"-----%d", a);
};
struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
// 深浅拷贝
id __strong strongBlock = weakBlock;
blc->invoke = nil;
void(^strongBlock1)(void) = strongBlock;//让strongBlock具备block特性 可以执行strongBlock1()
strongBlock1();
}
id __strong strongBlock = weakBlock;
blc->invoke = nil
strongBlock和blc 都指向weakBlock 栈上的block 操作的是同一片内存空间,blc->invoke = nil 置空之后 影响后面strongBlock的调用 导致崩溃
- (void)blockDemo1{
int a = 0;
void(^ __weak weakBlock)(void) = ^{
NSLog(@"-----%d", a);
};
struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
// 深浅拷贝
id __strong strongBlock = [weakBlock copy];
blc->invoke = nil;
void(^strongBlock1)(void) = strongBlock;//让strongBlock具备block特性 可以执行strongBlock1()
strongBlock1();
// 预警什么 ???
}
id __strong strongBlock = [weakBlock copy]; 对weakBlock 进行copy则可以解决崩溃问题,新拷贝到堆上的Block和原来栈上的blc互不影响
3.block 堆栈释放差异
- (void)blockDemo3{
// 压栈 内存平移
int a = 0;
void(^__weak weakBlock)(void) = nil;
{
// 栈区
void(^__weak strongBlock)(void) = ^{
NSLog(@"---%d", a);
};
weakBlock = strongBlock;
NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
}
结果
1 - <__NSStackBlock__: 0x7ffeef5543a0> - <__NSStackBlock__: 0x7ffeef5543a0>
---0
把 void(^__weak strongBlock)(void) = ^{ 换成 void(^ strongBlock)(void) = ^{
- (void)blockDemo3{
int a = 0;
void(^__weak weakBlock)(void) = nil;
{
// 堆区
void(^ strongBlock)(void) = ^{
NSLog(@"---%d", a);
};
weakBlock = strongBlock;
NSLog(@"1 - %@ - %@",weakBlock,strongBlock);//1 - <__NSMallocBlock__: 0x6000007dadf0> - <__NSMallocBlock__: 0x6000007dadf0>
}
weakBlock();
}
1 - <__NSMallocBlock__: 0x6000007dadf0> - <__NSMallocBlock__: 0x6000007dadf0>
(lldb)
堆区的block出了作用域就释放了 后面再调用就会报错
block的循环引用以及解决方法
block最常见的的问题就是循环引用,上图描述了循环引用的过程和原因 产生循环引用的常见代码示例
static ViewController *staticSelf_;
typedef void(^LRBlock)(void);
@interface ViewController ()
@property (nonatomic, strong) LRBlock block;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) LRBlock doWork;
@property (nonatomic, copy) LRBlock doStudent;
@property (nonatomic, strong) UITableView *tableView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 循环引用
[self block1];
}
- (void)block1{
__weak typeof(self) weakSelf = self;
self.block = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakSelf.name);
});
};
self.block();
}
# pragma mark weak面试题
- (void)blockWeak_static {
// 是同一片内存空间
__weak typeof(self) weakSelf = self;
staticSelf_ = weakSelf;
// staticSelf_ -> weakSelf -> self
}
# pragma mark weak_strong_dance
- (void)block_weak_strong {
__weak typeof(self) weakSelf = self;
self.doWork = ^{
__strong typeof(self) strongSelf = weakSelf;
weakSelf.doStudent = ^{
NSLog(@"%@", strongSelf);
};
weakSelf.doStudent();
};
self.doWork();
}
- (void)dealloc{
NSLog(@"dealloc 来了");
}
@end
产生循环引用代码示例:
//代码一
self.block = ^(void){
NSLog(@"%@",self.name);
};
self.block();
//代码二
[UIView animateWithDuration:1 animations:^{
NSLog(@"%@",self.name);
}];
代码一产生了循环引用,在block内部使用了外部变量 name,name是self持有的,则blcok持有了self 而self本来就持有block, 所以 self -> block, block -> self.name -> self
代码二中没有循环引用, 虽然在block内部也使用了外部变量 name,但是block并不被self持有 只有block -> self.name -> self 没有构成循环引用
打破block对self强引用原理:解决self的作用域和block作用域的通讯,通讯方式有 代理、传值、通知等 通常我们有如下几种方式处理
- 1.weak-strong-dance 强弱共舞
- 2.手动置空,使用临时变量(用__block修饰)引用self,在block内部手动置空
- 3.将可能产生循环引用的对象self以block参数的形式传入,在block内部使用参数就不会产生循环引用问题
- 4.NSProxy虚拟类
1.weak-strong-dance 强弱共舞
1.1 常规情况: 使用__weak
- (void)block1{
__weak typeof(self) weakSelf = self;
self.block = ^(void){
NSLog(@"%@",weakSelf.name);
};
self.block();
}
常规情况,我们采用__weak 修饰self 的方式来解除循环引用,使用 __weak不会导致self的引用计数发生变化, weakSelf 和 self 指向同一片内存空间。
1.2 block嵌套的情况:同时使用 __weak 和 __strong
但是 有些情况(例如延迟操作) 会产生下面的问题: 点击进入vc然后返回 ,会调用上面的方法,延时2s之后会打印 (null)
由打印可以发现,当在延时2s打印的时候 weakSelf已经释放了
为此通常采用 __strong创建临时变量的方式来 防止weakSelf在延时过程中被提前释放
- (void)block1{
__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();
}
strongSelf是一个临时变量,作用域在block内部,当block执行完成strongSelf就会释放,从而保证weakSelf不会被提前释放和打破循环引用,依赖中介者模式
2.手动置空,使用临时变量(用__block修饰)引用self,在block内部手动置空
依赖中介者模式,需手动释放vc
- (void)block1{
// 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();
}
3.将可能产生循环引用的对象self以block参数的形式传入,在block内部使用参数就不会产生循环引用问题
- (void)block1{
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);
}
4.NSProxy虚拟类
- OC只能单继承,由于它是运行实际值,所以可以通过NSProxy来实现伪多继承
- NSProxy 和 NSObject是童年个基点一个类,也可以说是一个虚拟类,只是实现了NSObject的协议
- NSProxy 其实是一个消息重定向封装的一个抽象类,类似一个代理人,中间件,可以通过继承它,并重写下面两个方法来实现消息转发到另一个实例
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
NSProxy的使用场景主要有两种
实现多继承功能
解决了NSTimer&CADisplayLink创建时对self强引用问题,参考YYKit的YYWeakProxy。
通过自定义的NSProxy类对象来代替self,使用消息转发 自定义一个NSProxy子类
@interface LRProxy : NSProxy
- (id)transformObjc:(NSObject *)objc;
+ (instancetype)proxyWithObjc:(id)objc;
@end
@interface LRProxy ()
@property (nonatomic, weak, readonly) NSObject *objc;
@end
@implementation LRProxy
- (id)transformObjc:(NSObject *)objc{
_objc = objc;
return self;
}
+ (instancetype)proxyWithObjc:(id)objc{
return [[self alloc] transformObjc:objc];
}
//2.有了方法签名之后就会调用方法实现
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = [invocation selector];
if ([self.objc respondsToSelector:sel]) {
[invocation invokeWithTarget:self.objc];
}
}
//1、查询该方法的方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
NSMethodSignature *signature;
if (self.objc) {
signature = [self.objc methodSignatureForSelector:sel];
}else{
signature = [super methodSignatureForSelector:sel];
}
return signature;
}
- (BOOL)respondsToSelector:(SEL)aSelector{
return [self.objc respondsToSelector:aSelector];
}
@end
自定义Cat和Dog类
//********Cat类********
@interface Cat : NSObject
@end
@implementation Cat
- (void)eat{
NSLog(@"猫吃鱼");
}
@end
//********Dog类********
@interface Dog : NSObject
@end
@implementation Dog
- (void)shut{
NSLog(@"狗叫");
}
@end
多继承示例
- (void)proxyTest{
Dog *dog = [[Dog alloc] init];
Cat *cat = [[Cat alloc] init];
LRProxy *proxy = [LRProxy alloc];
[proxy transformObjc:cat];
[proxy performSelector:@selector(eat)];
[proxy transformObjc:dog];
[proxy performSelector:@selector(shut)];
}
通过LRProxy解决定时器中对self强引用的问题,解决循环引用
self.timer = [NSTimer timerWithTimeInterval:1 target:[LRProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
- (void)dealloc{
[self.timer invalidate];
NSLog(@"dealloc 来了");
}
block循环引用面试题
# pragma mark weak面试题
- (void)blockWeak_static {
// 是同一片内存空间
__weak typeof(self) weakSelf = self;
staticSelf_ = weakSelf;
// staticSelf_ -> weakSelf -> self
}
# pragma mark weak_strong_dance
- (void)block_weak_strong {
__weak typeof(self) weakSelf = self;
self.doWork = ^{
__strong typeof(self) strongSelf = weakSelf;
weakSelf.doStudent = ^{
NSLog(@"%@", strongSelf);
};
weakSelf.doStudent();
};
self.doWork();
}
staticSelf_ -> weakSelf -> self 三个变量指向同一片内存空间,都是self指向的内存空间
由于staticSelf 是static修饰, vc返回之后还没有出作用域释放,导致self的引用计数不为0无法释放,产生了循环引用
当vc返回之后 重新进入新的vc页面调用此方法的时候,self是重新创建的 staticSelf = weakSelf 重新赋值,staticSelf不再指向原来的self,原来指向的self引用计数变为0 原来的vc释放,原来的循环引用打破, 但是重新赋值之后的staticSelf和新的vc继续产生了循环引用😏6️⃣6️⃣6️⃣😄
对于block_weak_strong 这个题
本身就会产生循环引用
解决方案一 将强弱共舞进行到底
每一层block都有自己的strongSelf
__weak typeof(self) weakSelf = self;
self.doWork = ^{
__strong typeof(self) strongSelf = weakSelf;
__weak typeof(self) weakSelf = strongSelf;
weakSelf.doStudent = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"%@", strongSelf);
};
weakSelf.doStudent();
};
self.doWork();
方案二 手动置空
__block ViewController *vc = self;
self.doWork = ^{
vc.doStudent = ^{
NSLog(@"%@", vc);
vc = nil;
};
vc.doStudent();
};
self.doWork();
方案三 传参
@property (nonatomic, copy) void(^doWork1)(ViewController *vc);
@property (nonatomic, copy) void(^doStudent1)(ViewController *vc);
self.doWork1 = ^(ViewController *vc) {
vc.doStudent1 = ^(UIViewController *vc) {
NSLog(@"%@", vc);
};
vc.doStudent1(vc);
};
self.doWork1(self);