RACSignal与内存管理

简介

相信不用过多介绍都知道 ReactiveObjC 是一套基于Cocoa 的响应式编程框架。它提供了一套简介的 API 处理各种复杂消息传递问题,在使用 ReactiveObjC 开发的时候, 我们很容易用它来实现,View-Model 的绑定、用户交互事件绑定、状态依赖等问题。

RACStream

在整个 ReactiveObjC 中 RACStream 是一个抽象类,一般不直接使用。它有两个比较重要的子类 RACSignal、RACSequence。今天我们主要关注 RACSignal。RACSignal 可以说是 ReactiveObjC 中的核心类,也是 RAC 最重要的概念,整个框架围绕着 RACSignal 的概念进行组织,RACSignal 简单粗暴的理解就是它表示一连串的状态。当状态改变时,它的订阅者就会收到相应的消息。

RACStream.png

信号的创建

RACSignal 使用类簇的设计模式,我们可以使用 RACSignal 相关类族,这些类族都继承自RACSignal,具体如下:RACDynamicSignal、RACEmptySignal、RACErrorSignal、RACReturnSignal :

RACSignal.png

在创建信号的时候一般使用 + (RACSignal<ValueType> *)createSignal:(RACDisposable * _Nullable (^)(id<RACSubscriber> subscriber))didSubscribe 方法。

RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@1];
    [subscriber sendNext:@2];
    [subscriber sendCompleted];
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"%@", @"dispose");
    }];
}];

信号的订阅

ReactiveObjC 中信号的订阅与信息的发送的类主要是 RACSubscriber。在订阅信号的时候,我们可以选择我们关心的信号,如:next、error、completed。我们主要是通过 - (RACDisposable *)subscribeNext:error:completed:来订阅信号。

@weakify(self);
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
   
   @strongify(self)
   self.subscriber = subscriber;
   
   [subscriber sendNext:@"123"];
   
   return [RACDisposable disposableWithBlock:^{
       NSLog(@"%@", @"我被释放了");
    }];
}];

[signal subscribeNext:^(id  _Nullable x) {
    NSLog(@"%@", x);
}];

信息的发送

我们可以根据自己关注的消息进行发送,在 ReactiveObjC 中只要实现了RACSubscriber 协议的就可以发送消息。根据 RACSubscriber 协议,我们可以选择性的发送以下三种消息:

@protocol RACSubscriber <NSObject>
- (void)sendNext:(nullable id)value;
- (void)sendError:(nullable NSError *)error;
- (void)sendCompleted;
@end

订阅的回收

使用 + (RACSignal<ValueType> *)createSignal:(RACDisposable * _Nullable (^)(id<RACSubscriber> subscriber))didSubscribe 创建信号的时候,我们传入了 didSubscribe block,当订阅信号的时候,这个 block 便会执行并返回 RACDisposable 对象,用于取消正在执行的任务,以及订阅结束时的清理工作。

RACDisposable.png

内存管理

之所以叫 RACSignal 与内存管理,是因为这篇文章不是入门文章,也不是源码阅读的文章,文章中只是简单介绍了 RACSignal 的创建与订阅过程,没从源码角度分析。不过既然涉及到 RACSignal 内存管理,因此,下面会从源码角度分析。

在使用 RACSignal 的时候,一般会考虑相关内存管理的问题,比如,1、RACSignal 什么时候释放;2、RACSubscriber 什么时候释放;3、RACDisposable 什么时候销毁;4、RACSignal 会不会导致循环引用。 以下是信号创建的相关引用关系。接下来一步步看相关内存管理。

RACSignal.png

1、创建信号。我们使用 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe 创建信号。用 RACSignal 创建信号的时候会调用 RACDynamicSignal 来创建信号。以下是源码内容:

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    return [RACDynamicSignal createSignal:didSubscribe];
}

RACDynamicSignal 创建信号的时候则 copy 了 didSubscribe。

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}

2、订阅信号。我们使用 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock 订阅信号,这时候会创建 RACSubscriber 对象,并对 nextBlock 进行 copy。源码如下:

- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    NSCParameterAssert(nextBlock != NULL);
    
    RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL];
    return [self subscribe:o];
}

创建信号后,会调用 [self subscribe:o];方法,这里的 self 是 之前创建的 RACDynamicSignal。在 - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber 中会生成 RACPassthroughSubscriber 对象,该对象持有 RACDynamicSignal 和 RACCompoundDisposable 对象。在生成 RACPassthroughSubscriber 对象后,将其传入 RACDynamicSignal 持有的 didSubscribe Block 中去执行信号的发送工作。

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    if (self.didSubscribe != NULL) {
        RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
            RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            [disposable addDisposable:innerDisposable];
        }];

        [disposable addDisposable:schedulingDisposable];
    }
    
    return disposable;
}
  • 初步印象:

整个过程很像 RACDynamicSignal 持有 _didSubscribe Block,而 _didSubscribe Block 则持有 RACPassthroughSubscriber 对象,RACPassthroughSubscriber 的 _signal 属性又持有 RACDynamicSignal,因此,整个过程好像是循环引用。其实并不是,一方面 RACPassthroughSubscriber 的 _signal 属性并不是强引用,它的定义如下:

@property (nonatomic, unsafe_unretained, readonly) RACSignal *signal;

因此,整个消息创建与订阅的过程,并不会构成循环引用;另一方面,使用 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe 方法创建信号时,虽然 copy 了 didSubscribe Block, 但是 RACDynamicSignal 并不会持有 id<RACSubscriber> subscriber 对象,因为, id<RACSubscriber> subscriber 只是参数,不是外部变量,因此Block copy 的时候并不会强引用 id<RACSubscriber> subscriber 。

  • 案例:

1、如果我们不持有 RACDynamicSignal 和 RACPassthroughSubscriber ,则对象出了作用域之后会自动销毁。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    
       [subscriber sendNext:@"123"];
       
       return [RACDisposable disposableWithBlock:^{
           NSLog(@"%@", @"我被释放了");
        }];
    }];

    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
}

输出如下:

RAC[5280:524304] 123
RAC[5280:524304] -[RACPassthroughSubscriber dealloc]
RAC[5280:524304] -[RACSubscriber dealloc]
RAC[5280:524304] 我被释放了
RAC[5280:524304] -[RACCompoundDisposable dealloc]
RAC[5280:524304] -[RACCompoundDisposable dealloc]
RAC[5280:524304] -[RACDynamicSignal dealloc]

2、如果我们持有 RACDynamicSignal,但是 RACPassthroughSubscriber 并不会被强引用,因此,RACPassthroughSubscriber 会被释放。我们持有的 RACDynamicSignal 不会被释放。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
//    @weakify(self);
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        [subscriber sendNext:@"123"];
        
        self.subscriber = nil;
       
        return [RACDisposable disposableWithBlock:^{
           NSLog(@"%@", @"我被释放了");
        }];
    }];

    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];

    self.signal = signal;
    
}

输出如下:

RAC[5312:526033] 123
RAC[5312:526033] -[RACPassthroughSubscriber dealloc]
RAC[5312:526033] -[RACSubscriber dealloc]
RAC[5312:526033] 我被释放了
RAC[5312:526033] -[RACCompoundDisposable dealloc]
RAC[5312:526033] -[RACCompoundDisposable dealloc]

3、如果我们持有 RACPassthroughSubscriber,由于 RACPassthroughSubscriber 的属性 _signal 被 unsafe_unretained 修饰,因此,RACDynamicSignal 会被释放,RACCompoundDisposable 由于被 RACPassthroughSubscriber 持有,不会被释放。由于信号发送与接收只依赖 RACPassthroughSubscriber,因此,可以发送与接收信号。但是不能订阅信号(RACDynamicSignal被释放)。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    @weakify(self);
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        
        [subscriber sendNext:@"123"];
        
        @strongify(self);
        self.subscriber = subscriber;
       
        return [RACDisposable disposableWithBlock:^{
           NSLog(@"%@", @"我被释放了");
        }];
    }];

    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
}

输出如下:

RAC[5360:529305] 123
RAC[5360:529305] -[RACDynamicSignal dealloc]

4、类似上面的第3条,但是我们最后 dispose 信号,这时我们 didSubscribe 中返回的 RACDisposable 被 dispose,RACPassthroughSubscriber 不会释放,要使让它释放,不持有它便可。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    @weakify(self);
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        
        [subscriber sendNext:@"123"];
        
        @strongify(self);
        self.subscriber = subscriber;
       
        return [RACDisposable disposableWithBlock:^{
           NSLog(@"%@", @"我被释放了");
        }];
    }];

    RACDisposable *disposable = [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    [disposable dispose];
}

输出结果为:

RAC[5637:543032] 123
RAC[5637:543032] 我被释放了
RAC[5637:543032] -[RACDynamicSignal dealloc]

5、循环引用的情况,1、如果你的对象持有 id<RACSubscriber> subscriber,而 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock 的 nextBlock 又持有你的对象;2、如果你的对象持有 RACSignal,而 + (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe 的 didSubscribe 又持有你的对象。

情景1:

- (void)dealloc
{
    NSLog(@"%s", __func__);
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    @weakify(self);
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        [subscriber sendNext:@"123"];
        
        @strongify(self);
        self.subscriber = subscriber;
       
        return [RACDisposable disposableWithBlock:^{
           NSLog(@"%@", @"我被释放了");
        }];
    }];

    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
         self.name = x;
    }];
    
    // 事件
    [[_loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        UIViewController *vc = [[UIViewController alloc] init];
        [[UIApplication sharedApplication] keyWindow].rootViewController = vc;
    }];
    
}

情景2:

- (void)dealloc
{
    NSLog(@"%s", __func__);
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        [subscriber sendNext:@"123"];
        
        self.name = @"123";
        
        return [RACDisposable disposableWithBlock:^{
           NSLog(@"%@", @"我被释放了");
        }];
    }];

    [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"%@", x);
    }];
    
    self.signal = signal;
    
    // 事件
    [[_loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        UIViewController *vc = [[UIViewController alloc] init];
        [[UIApplication sharedApplication] keyWindow].rootViewController = vc;
    }];
    
}

总结

对于 RACSignal 的内存管理还是比较简单,其它复杂的订阅也是基于 RACSignal 的,因此,理解 RACSignal 的内存管理,对熟悉其它复杂订阅方式的内存管理很有帮助。

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

推荐阅读更多精彩内容