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