ReactiveCocoa学习笔记七-RACSubscriber源码分析

Subscriber

到了最后一个部分了--订阅者. 其实在Signal篇我们已经接触过了一些Subscriber, 毕竟Subject也是一个Subscriber.

而与RACSignal和RACDisposable不同的是, 与Subscriber相关的其它类不是用的继承的形式, 而是组合(也就是其它类持有了一个RACSubscriber实例, 例如RACPassthroughSubscriber).

RACSubscriber

RACSubcriber是一般订阅者的类, 整体而言代码也是比较简洁的, 首先要说明的是RACSubscriber有一个protocol的声明, 也有一个类的声明, 对于一个协议来说, 它Required了以下几个方法:

- (void)sendNext:(id)value;

- (void)sendError:(NSError *)error;

- (void)sendCompleted;

- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;

前面3个基本都见过了, 最后一个在Subject也见过一次. RACSubscriber类有个类方法返回实例, 但是头文件标注为私有的, 也就是说一般我们使用的时候不要关注它:

+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed;

再看看私有变量:

@interface RACSubscriber ()

@property (nonatomic, copy) void (^next)(id value);
@property (nonatomic, copy) void (^error)(NSError *error);
@property (nonatomic, copy) void (^completed)(void);
// 因为协议中有了addDisposable, 所以理所当然要有个RACCompoundDisposable实例
@property (nonatomic, strong, readonly) RACCompoundDisposable *disposable;

@end

再看看几个重要方法:

- (id)init {
    self = [super init];
    if (self == nil) return nil;

    @unsafeify(self);

    // 首先要添加一个释放自己资源的disposable
    RACDisposable *selfDisposable = [RACDisposable disposableWithBlock:^{
        @strongify(self);

        @synchronized (self) {
            self.next = nil;
            self.error = nil;
            self.completed = nil;
        }
    }];

    _disposable = [RACCompoundDisposable compoundDisposable];
    [_disposable addDisposable:selfDisposable];

    return self;
}

- (void)dealloc {
    [self.disposable dispose];
}

值得一提的是, 虽然与Subscriber与Subject的作者都是同一人, 但是2者创建的时间却是一个1月一个9月, 所以在写的时候还是会有所区别, 特别是@unsafeify和@weakify, 之前看到一篇博文应该是王巍写的, 专门讲RAC里面宏的精要, 还是很值得一看的.

再看看3个发送事件的方法:

- (void)sendNext:(id)value {
    @synchronized (self) {
        void (^nextBlock)(id) = [self.next copy];
        if (nextBlock == nil) return;

        nextBlock(value);
    }
}

- (void)sendError:(NSError *)e {
    @synchronized (self) {
        void (^errorBlock)(NSError *) = [self.error copy];
        [self.disposable dispose];

        if (errorBlock == nil) return;
        errorBlock(e);
    }
}

- (void)sendCompleted {
    @synchronized (self) {
        void (^completedBlock)(void) = [self.completed copy];
        [self.disposable dispose];

        if (completedBlock == nil) return;
        completedBlock();
    }
}

可以看到前期作者在写的时候非常谨小慎微, 各种同步加持, 还用临时变量来保证线程安全, 到了Subject的时候就会更加"随意"一些.

同样的, 最后这个didSubscribeWithDisposable也是如此, 这里以这个方法来看看Subject里面和Subscriber里面的实现差异

// RACSubscriber.m
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)otherDisposable {
    if (otherDisposable.disposed) return;

    RACCompoundDisposable *selfDisposable = self.disposable;
    [selfDisposable addDisposable:otherDisposable];

    @unsafeify(otherDisposable);

    [otherDisposable addDisposable:[RACDisposable disposableWithBlock:^{
        @strongify(otherDisposable);
        [selfDisposable removeDisposable:otherDisposable];
    }]];
}
// RACSubject.m
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)d {
    if (d.disposed) return;
    [self.disposable addDisposable:d];

    @weakify(self, d);
    [d addDisposable:[RACDisposable disposableWithBlock:^{
        @strongify(self, d);
        [self.disposable removeDisposable:d];
    }]];
}

发现去掉临时变量之后, 二者基本上是一致的.

RACPassthroughSubscriber

在我们订阅的时候, 实际创建的实例是RACPassthroughSubscriber, 但是一般我们不直接创建它, 都是RAC本身的实现.

先看看头文件:

@interface RACPassthroughSubscriber : NSObject <RACSubscriber>

- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable;

实现了RACSubscriber协议, 并且signal, subcriber, disposable一应俱全, 看起来这个类会维系好三者之间的关系.

看实现文件时, 发现里面又来了一些 DTrace 的东西, 这个东西在 objc.io 里面有过介绍, Xcode 的Instruments就是基于此实现, 有兴趣的可以去看看. 从这个侧面也应该看的出来, RAC 团队对框架的性能是有过比较深度的评测的, 因此还是值得信赖的.

同时需要提一下的是, DTrace 的属性没有标注为weak 而是unsafe_unretained, 众所周知, weak 的属性会在指向的对象被释放后自动置为nil, 因此要实现这一点, 肯定就会在目标对象上有存储 weak 引用的列表, 据测为了保证 weak 引用, 中间产生的开销还是不小的, 所以 RAC 会只在 DTrace 中用的属性用unsafe_unretained, 以免造成不必要的开销. 当然, 使用了unsafe_unretained的属性, 就不能轻易去 get 和 set 否则很容易造成野指针 crash.

实例初始化代码:

- (instancetype)initWithSubscriber:(id<RACSubscriber>)subscriber signal:(RACSignal *)signal disposable:(RACCompoundDisposable *)disposable {
    NSCParameterAssert(subscriber != nil);

    self = [super init];
    if (self == nil) return nil;

    _innerSubscriber = subscriber;
    _signal = signal;
    _disposable = disposable;

    // 添加传入的 disposable, 在调用 dispose 的时候一并清除掉
    [self.innerSubscriber didSubscribeWithDisposable:self.disposable];
    return self;
}

RACSubscriber协议的其它方法就基本上没有太多可讲的了, 与上面的基本上是大同小异. 那么这里会引出一个问题, 为什么不直接用 RACSubscriber, 而要再引入一个 passThrough 版本呢?

根据RACPassthroughSubscriber头文件中对这个类的描述:

A private subscriber that passes through all events to another subscriber
while not disposed.

大意是说: 在未被清除的情况下, 把所有事件都传递到另一个订阅者的私有订阅者.

前提很好理解, 清除之后自然没有必要再传递, 另一个订阅者自然也就是传入进来的这个订阅者, 也就是实际上需要发送时间的 RACSubscriber. 然而这段话并没有解释出来为什么需要这么样个私有订阅者呢, 从代码角度上来看也就是加了 DTrace 的功能而已, 并没有什么特殊的管理三者关系的代码存在.

目前除了 DTrace 的探针之外, 我个人找不到很好的解释, 发送邮件给作者想要问这个问题也暂时没有得到回复, 如果有回复我会更新上来. 除此之外, 我还特地把RACDynamicSignal里面的subscribe中把普通 RACSubscriber 变为RACPassThroughSubscriber 的代码给注释掉:

// subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

发现对我的 demo 应用并没有任何干扰, 因此也算一个佐证证明RACPassThroughSubscriber的作用的猜想了.

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

推荐阅读更多精彩内容