iOS Block原理

block类型

  • __NSGlobalBlock__:全局block,存储在全局区,没有传参也没有返回值

    __NSGlobalBlock__

  • __NSMallocBlock__:堆区block,存储在堆区,访问外部变量时,block底层会拷贝外部变量

    __NSMallocBlock__

  • __NSStackBlock__:栈区block,存储在栈区,在外部变量没有进行拷贝前,或者是__weak修饰

    __NSStackBlock__

总结

  • 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
    修饰,因为weakSelfself指向同一片内存空间,但是__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基类
  • NSProxyNSObject是同级的类,只实现了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;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容