block类型
-
__NSGlobalBlock__
:全局block,存储在全局区,没有传参也没有返回值
-
__NSMallocBlock__
:堆区block,存储在堆区,访问外部变量时,block底层会拷贝外部变量
-
__NSStackBlock__
:栈区block,存储在栈区,在外部变量没有进行拷贝前,或者是__weak
修饰
总结
- block直接存储在
全局区
- 如果
block访问了外部变量
,并进行block相应的copy- 如果
block是强引用
,则block存储在堆区,堆区block - 如果
block是弱引用,__weak
,则block存储在栈区,栈区block
- 如果
block循环引用
-
正常释放
:当A持有B时,当A调用dealloc方法时,给B发送release信号,B收到release信号,如果此时B的retainCount(引用计数)==0,则B调用dealloc -
循环引用
:A、B相互持有,导致A无法调用dealloc方法向B发送release信号,而B无法调用dealloc方法向A发送release信号
解决方案
【方式1】weak-strong-dance
通过__weak
打破self对block的强引用
,属于中介者模式
,会自动释放
- block内部没有嵌套block,直接使用
__weak
修饰,因为weakSelf
和self
指向同一片内存空间,但是__weak不会让引用计数发生变化
typedef void(^YPBlock)(void);
@property(nonatomic, copy) YPBlock ypBlock;
__weak typeof(self) weakSelf = self;
self.ypBlock = ^(void){
NSLog(@"%@",weakSelf.name);
}
- block内部嵌套block,同时使用
__weak
和__strong
,因为strongSelf
是一个作用域在ypBlock内的临时变量,当内部block执行完毕后会自动释放
__weak typeof(self) weakSelf = self;
self.ypBlock = ^(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);
});
};
【方式二】__block修饰变量
同样属于中介者模式
,手动释放
,这种方式下的block必须调用
,如果不调用,变量不会释放,依旧是循环引用
__block ViewController *vc = self;
self.ypBlock = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;//手动释放
});
};
self.ypBlock();
【方式三】对象self当做参数传入block
typedef void(^YPBlock)(ViewController *vc);
@property(nonatomic, copy) YPBlock ypBlock;
self.ypBlock = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.ypBlock(self);
【方式四】NSProxy基类
-
NSProxy
和NSObject
是同级的类,只实现了NSObject的协议的一个虚拟类
- 利用OC的运行时特性,通过
NSProxy
实现伪多继承
-
NSProxy
是一个消息重定向封装的抽象类
,类似一个代理人、中间件,可以通过继承NSProxy,并重写下面消息转发的两个方法来实现消息转发到另一个实例
- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel
- 使用场景
- 实现
多继承
- 解决
NSTimer和CADisplayLink
创建时self强引用
,参考YYKit
中的YYWeakProxy
- 实现
NSProxy子类实现
@interface YPProxy : NSProxy
- (id)transformObjc:(NSObject *)objc;
+ (instancetype)proxyWithObjc:(id)objc;
@end
@interface YPProxy ()
@property(nonatomic, weak, readonly) NSObject *objc;
@end
@implementation YPProxy
- (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
- 实现多继承
- (void)yp_proxyTest{
Teacher *teacher = [[Teacher alloc] init];
Student *student = [[Student alloc] init];
YPProxy *proxy = [YPProxy alloc];
[proxy transformObjc:teacher];
[proxy performSelector:@selector(name)];
[proxy transformObjc:student];
[proxy performSelector:@selector(age)];
}
- 解决
定时器中强引用
self.timer = [NSTimer timerWithTimeInterval:1 target:[YPProxy proxyWithObjc:self] selector:@selector(print) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
总结
循环引用的解决方法从本质上来说有两种,self-->block-->self
打破self-->block的强引用,比如使用weak修饰,但是这会导致block还没创建就被释放,所有这个方案不行
-
打破block-->self的强引用,主要就是
self的作用域
和block作用域
的通讯,通讯有代理、传值、通知、传参等几种方式,用于解决循环,常见的解决方式如下 :weak-strong-dance
-
__block
(block内对象置空,且调用block) - 将对象
self
作为block的参数
- 通过
NSProxy
的子类代替self
Block底层分析
block本质
是一个对象、函数、结构体
,由于block函数没有名称,也称为匿名函数
block底层
- 自定义一个
block.c
文件
#include "stdio.h"
int main(){
void(^block)(void) = ^{
printf("YP");
};
return 0;
}
- 通过命令
xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c
,将block.c
文件编译成block.cpp
,其中__main_block_impl_0
函数就等于block
int main(){
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("YP");
}
//******简化******
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));//构造函数
block->FuncPtr(block);//block调用执行
- 查看
__main_block_impl_0
函数,发现是一个结构体,因为block
能用%@
打印,也说明block是一个__main_block_impl_0
类型的对象
//**block代码块的结构体类型**
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//**block的结构体类型**
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
1、block为什么需要调用
在底层block是一个__main_block_impl_0
的结构体,通过同名的构造函数创建
-
函数声明
:block内部实现声明了一个函数__main_block_impl_0
-
执行具体的函数实现
:通过调用block的FuncPtr
指针,执行block
2、block如何捕获外界变量
block在捕获外界变量时,会自动生成一个同名属性进行值拷贝保存
- 定义一个block
int main(){
int a = 11;
void(^block)(void) = ^{
printf("YP - %d", a);
};
block();
return 0;
}
- 底层编译源码,在
__main_block_func_0
中的变量a是值拷贝,即a是只读的
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;//编译时就自动生成了相应的变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;//block的isa默认是stackBlock
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy 值拷贝,即 a = 10,此时的a与传入的__cself的a并不是同一个
printf("YP - %d", a);
}
int main(){
int a = 11;
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));
block)->FuncPtr(block);
return 0;
}
__block底层原理
外部变量会生成一个__Block_byref_a_0
的结构体,通过指针拷贝
来保存原始变量的指针和值
,将变量生成的结构体对象的指针地址传递到block
,在block内部就可以对外部变量进行操作
- 定义一个block
int main(){
__block int a = 11;
void(^block)(void) = ^{
a++;
printf("YP - %d", a);
};
block();
return 0;
}
- 底层编译
struct __Block_byref_a_0 {//__block修饰的外界变量的结构体
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {//block的结构体类型
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {//构造方法
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {//block内部实现
__Block_byref_a_0 *a = __cself->a; // bound by ref 指针拷贝,此时的对象a 与 __cself对象的a 指向同一片地址空间
//等同于 外界的 a++
(a->__forwarding->a)++;
printf("YP - %d", (a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
int main(){
//__Block_byref_a_0 是结构体,a 等于 结构体的赋值,即将外界变量a 封装成对象
//&a 是外界变量a的地址
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 11};
//__main_block_impl_0中的第三个参数&a,是封装的对象a的地址
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}