iOS开发笔记之七十四——FRP与RAC进阶篇(数据黑白板XYReactDataBoard的介绍)

******阅读完此文,大概需要30分钟******

一、简介

XYReactDataBoard是一款已经比较成熟的基于RAC的响应式编程组件,它主要用来实现任意模块之间的数据通讯,它可以替代原生的Notification、KVO,delegate、NSUserdefault等数值传递方式。因为它除了可以实时传递数据,比起Notification、KVO等,实现相同的功能,XYReactDataBoard只需较少的代码,而且几乎无需关心Notification和KVO带来的手动释放、内存泄漏等问题。使用它,可以很好地提高我们的业务开发效率。目前已经广泛地使用在VivaVideo家族产品中,得到业务开发的充分肯定与验证。

组件化代码地址:

https://github.com/Leon0206/XYReactDataBoard

二、接入说明

XYReactDataBoard有两种数据板,分别是数据白板类XYReactWhiteBoard和数据黑板类XYReactBlackBoard,它们实现原理一致,但是用在不同的业务场景,下面分别进行介绍:

1、数据白板类:XYReactWhiteBoard

XYReactWhiteBoard是一个严格的单例,app全局只有一份,推荐在app启动初始化时进行实例化。因为是单例,所以它可以实现内存中任意模块之间的数据实时通讯,具体使用例子如下:

  • 1)数据的订阅(必须),首先你需要针对某一个key值进行订阅,代码如下:
[[[XYReactWhiteBoard shareBoard] signalForKey:@"video_edit_key"] subscribeNext:^(id x) {

NSLog(@"----------------->%@",x);

}];或者

id x = [[[XYReactWhiteBoard shareBoard] valueForKey:@"video_edit_key"]];
  • 2)数据的发送(必须),然后你需要对指定的key值进行设置:代码如下:
[[XYReactWhiteBoard shareBoard] setValue:@"hello world.." forKey:@"video_edit_key"];
  • 3) key值数据的移除(可选,但是强烈推荐), 如果你已经完成你的数据传递,建议针对key值进行remove,代码如下:
[[XYReactWhiteBoard shareBoard] removeValueForKey:@"video_edit_key"];

当然以上的订阅者是一对多的订阅,也就是说,1)中的订阅者代码,可以在多处编写,订阅者之间相互独立,它们都会被执行,类似Notification。XYReactWhiteBoard同时也提供一个api,用来实现数据的单独传递,这个订阅者代码只会执行一次,不可被覆盖,不可被多次订阅(多余的订阅者代码不会被执行),方法如下:

- (nonnull RACSignal *)singleSignalForKey:(NSString *)key;

以上即完成数据的黑板传递,详细的使用注意事项,见下面具体介绍。

2、数据黑板类:XYReactBlackBoard

XYReactBlackBoard是一个普通的工具类,也就是说,要使用它,你首先需要初始化它,它可以作为任意类对象的内部变量去使用,多个对象之间的通讯,需要持有一份whiteBoard才行,它的使用也很简单:

  • 1)初始化(必须):
XYReactBlackBoard *blackBoard = [[XYReactBlackBoard alloc] init];
  • 2)数据的订阅(同whiteBoard)(必须):
[[blackBoard signalForKey:@"video_edit_key"] subscribeNext:^(id x) {

     NSLog(@"video_edit_key:--------->%@",x);

}];
  • 3)数据的发送(同whiteBoard)(必须):
[blackBoard setValue:@"test" forKey:@"video_edit_key"];
  • 4)信号的删除操作(可选,同whiteBoard):
[blackBoard removeValueForKey:@"video_edit_key"];
  • 5) 信号的暂停与重启(可选)
[blackBoard pauseSignalForKey:@"video_edit_key"];
[blackBoard restartSignalForKey:@"video_edit_key"];

信号暂停后,针对次key值的信号将不会在接收到信号的传值;

三、实现原理

XYReactDataBoard的实现原理不是很复杂,数据黑板和数据白板的原理是一样的,只是为了适应不同的业务场景才进行区分。

1、XYReactWhiteBoard的实现

XYReactWhiteBoard内部维护了两个字典,subjects和values,分别用来存储订阅者和要传递的值对象;

  • 1)订阅者代码分析:
- (RACSignal *)signalForKey:(NSString *)key
{
    NSAssert(key, @"key should not be nil");
    if (key == nil) return [RACSignal empty];
    RACSubject *subject = [self subjectForKey:key];
    if (subject == nil) {
        subject = [RACSubject subject];
        @synchronized (self.values) {
            [self.subjects setObject:subject forKey:key];
            [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
                [subject sendCompleted];
            }]];
        }
    }
    return subject;
}

每次订阅者代码的执行,都会根据key值生成唯一的RACSubject,RACSubject是一个热信号,它可以有多个订阅者,

所以每个key值可以有多个订阅者,详见:

《FRP与RAC介绍(一)》:https://blog.csdn.net/lizitao/article/details/78721650

而与此同时singleSignalForKey:的实现方式,区别是它强制往subjects字典里存储了XYReactDataBoardSubject对象。

- (nonnull RACSignal *)singleSignalForKey:(nonnull NSString *)key
{
    NSAssert(key, @"key should not be nil");
    if (key == nil) return [RACSignal empty];
    RACSubject *subject = [self subjectForKey:key];
    if (subject == nil || ![subject isKindOfClass:[XYReactDataBoardSubject class]]) {
        subject = [XYReactDataBoardSubject subject];
        @synchronized (self) {
            [self.subjects setObject:subject forKey:key];
            [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
                [subject sendCompleted];
            }]];
        }
    }
    return subject;
}

XYReactDataBoardSubject类是RACSubject的子类,它重写了订阅方法的代码,如下:

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber
{
     if (!self.subscriber) {
        self.subscriber = subscriber;
        return [super subscribe:subscriber];
     }
    return nil;
}

它只保存了首次进来的订阅者,后面来的直接丢弃。

综上可以看出,XYReactDataBoardSubject类本身是被重写的RACSubject,它产生了一种特殊的热信号,这种热信号有且仅有一个订阅者。

  • 2)信号发送者代码分析:

这个就比较简单了,如下:

- (void)setValue:(id)value forKey:(NSString *)key
{
    NSAssert(key, @"key should not be nil");
    if (key.length <=0) return;
    
    if (value) {
        @synchronized (self) {
            [self.values setObject:value forKey:key];
        }
    } else {
        @synchronized (self) {
            [self.values removeObjectForKey:key];
        }
    }
    [[self subjectForKey:key] sendNext:value];
}

当执行信号的发送代码时,首先它会将对应key值的value存储到self.values中,然后根据key去取对应的RACSubject,执行sendNext:操作,根据《FRP与RAC介绍(一)》文中介绍,这一步是在执行订阅者代码,并将value传递过去。

2、XYReactBlackBoard的实现

  • 1)XYReactBlackBoard的内部实现原理和XYReactWhiteBoard一样的,只不过XYReactBlackBoard中封装了更多的功能,更加灵活。XYReactBlackBoard的生命周期和持有它的对象一样,无需手动释放。
  • 2)信号的暂停与重启

数据黑板类中加增加了一个flag字典,如下:

@property (nonatomic, strong) NSMutableDictionary <NSString*, NSNumber*>*flags;

它会针对key值进行标识位的设置,如果信号一旦被设置为暂停,就会根据此标示进行判断是否要执行订阅这代码,如下:

- (void)setValue:(id)value forKey:(NSString *)key
{
    if (key.length <= 0) return;
    @synchronized (self) {
        if (value) {
            [self.values setObject:value forKey:key];
            if (![self.flags valueForKey:key]) {
                [self.flags setObject:@(1) forKey:key];
            }
        } else {
            [self.values removeObjectForKey:key];
        }
    }
    if ([[self.flags valueForKey:key] integerValue]) {
         [[self subjectForKey:key] sendNext:value];
    }
}

四、注意事项

  • 1、能用XYReactBlackBoard的时候,尽量优先使用XYReactBlackBoard,简单的业务场景,通讯数据的比较小,可以用XYReactWhiteBoard,如果业务场景较复杂,推荐XYReactBlackBoard。
  • 2、XYReactWhiteBoard的使用,不要忘记最终的remove操作,虽然它不会带来像notificaiton和kvo等内存泄露问题,但是由于XYReactWhiteBoard是单例,内部字典的不断增加,最终可能仍会带来内存膨胀的问题。
  • 3、XYReactWhiteBoard是全局的单例,也就意味着它的block回调会持有变量,所以如果订阅者回调里持有self,别忘记__weak __typeof(self) weakSelf = self;
  • 4、XYReactBoard是采取的观察者设计模式开发的,内部维护了热信号的数组,所以,请首先保证订阅者代码得到执行,否则将不会收到任何数据。
  • 5、数据通讯的完全依赖key值,十分灵活,这就意味着key容易重复,因此可能会带来各种问题,所以使用前,请确保你key值的唯一性。
  • 6、XYReactWhiteBoard适用于普通的业务代码数据通讯,跨模块,跨组件等;XYReactBlackBoard适合作为一个复杂对象(例如,可重复压栈的VC)的属性变量来使用,复杂的业务类建议自带了一份XYReactBlackBoard,便于内部模块间的数据通讯。
  • 7、文档描述会有滞后,一切以源码逻辑为准;

猜你喜欢

转载自blog.csdn.net/lizitao/article/details/81841171
今日推荐