1. 程式人生 > >iOS開發筆記之七十四——FRP與RAC進階篇(資料黑白板XYReactDataBoard的介紹)

iOS開發筆記之七十四——FRP與RAC進階篇(資料黑白板XYReactDataBoard的介紹)

******閱讀完此文,大概需要30分鐘******

一、簡介

XYReactDataBoard是一款已經比較成熟的基於RAC的響應式程式設計元件,它主要用來實現任意模組之間的資料通訊,它可以替代原生的Notification、KVO,delegate、NSUserdefault等數值傳遞方式。因為它除了可以實時傳遞資料,比起Notification、KVO等,實現相同的功能,XYReactDataBoard只需較少的程式碼,而且幾乎無需關心Notification和KVO帶來的手動釋放、記憶體洩漏等問題。使用它,可以很好地提高我們的業務開發效率。目前已經廣泛地使用在VivaVideo家族產品中,得到業務開發的充分肯定與驗證。

元件化程式碼地址:

二、接入說明

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值可以有多個訂閱者,詳見:

而與此同時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、文件描述會有滯後,一切以原始碼邏輯為準;