ReactiveCocoa函數(shù)響應(yīng)式編程-基礎(chǔ)篇

一直以來,都很想學學ReactiveCocoa這個神奇的技術(shù),但是最后都由于各種原因擱置了。這次終于也認真的研究一番,把自己學習心得整理出來留個記錄。

目錄:

一、了解函數(shù)響應(yīng)式編程
二、ReactiveCocoa簡介
三、ReactiveCocoa集成
四、理解什么是信號
五、從源碼理解RAC的信號機制
六、本篇總結(jié)

一、了解函數(shù)響應(yīng)式編程

image.png

函數(shù)式編程(Funcational Programming)
使用高階函數(shù)編程,即函數(shù)可采用多種函數(shù)作為它們的參數(shù)和返回值。

響應(yīng)式編程(Reactive Programming)
一種面向數(shù)據(jù)流和變化傳播的編程范式

函數(shù)響應(yīng)式編程(Funcational Reacitve Programming)
簡稱FRP,ReactiveCocoa就是一個典型的FRP框架,響應(yīng)式的編程思想,函數(shù)式的代碼形式。

二、ReactiveCocoa簡介

ReactiveCocoa(簡稱RAC),Reactive表示響應(yīng)式,Cocoa是蘋果整個框架的簡稱,許多蘋果框架都以Cocoa結(jié)尾。所以RAC是Github上為我們提供函數(shù)響應(yīng)式編程方法的iOS開發(fā)框架。

iOS開發(fā)中,我們需要使用按鈕點擊、代理、通知等這些方法來處理響應(yīng)事件。而RAC框架使用Category為很多基本的UIKit控件添加信號Signal,這樣我們可以通過信號來監(jiān)聽數(shù)據(jù)流與變化傳播,把將監(jiān)聽的代碼與處理代碼放在一起,從而方便我們管理。利用此特點結(jié)合MVVM架構(gòu),RAC也有十分顯著的作用。

三、ReactiveCocoa集成

RAC.5.0相對于之前版本對于自身項目結(jié)構(gòu)進行了較大調(diào)整,被拆分ReactiveCocoa、ReactiveSwift、ReactiveObjC、ReactiveObjCBridge四個庫,我們需要根據(jù)不同的情況來集成。
GitHub地址:https://github.com/ReactiveCocoa/ReactiveCocoa
通常,我們都使用Cocoapods集成RAC,需要注意的是Podfile文件中必須使用user_framework!,然后,針對于不同的代碼環(huán)境,有三種集成情況:

1.純OC工程

ReactiveObjc庫包含原RAC2的全部代碼,在純OC工程中使用

platform :ios, '8.0'
use_frameworks!     #必須添加
target 'ZSTest' do  #工程名
#pod 'ReactiveObjC' #默認導入最新的RAC版本         
end

2.純Swift工程

純Swfit工程繼續(xù)使用ReactiveCocoa,但RAC依賴ReactiveSwift,所以相當于引入兩個庫。
集成方法同上,只不過將ReactiveObjc換成ReactiveCocoa。

3.OC與Swift混編工程

混編工程需要同時引入ReactiveCocoa與ReactiveObjCBridge,但是ReactiveObjCBridge庫依賴于ReactiveObjc庫,所以相當于同時引入四個庫了。示例如下:

platform :ios, '8.0'
use_frameworks!     #必須添加
target 'ZSTest' do  #工程名
pod 'ReactiveCocoa'
pod 'ReactiveObjC'  
pod 'ReactiveObjCBridge'
end

四、ReactiveCocoa信號理解

我覺得學習RAC的第一個關(guān)口就是理解信號RACSignal了,什么是信號也許是困惑我們的第一個問題。

作為RAC中最為核心的一個類,信號可以理解為傳遞數(shù)據(jù)變化信息的工具,信號會在數(shù)據(jù)發(fā)生變化時發(fā)送事件流給它的訂閱者,然后訂閱者執(zhí)行響應(yīng)方法。信號本身不具備發(fā)送信號的能力,而是交給一個訂閱者去發(fā)出。

首先上一段代碼,演示信號的一個基本使用。
測試場景:我們要對一個用于輸入用戶名的UITextFiled進行檢測,每次輸入內(nèi)容變化的時候都打出輸入框的內(nèi)容,使用RAC來實現(xiàn)此操作的關(guān)鍵代碼如下:

[self.userNameTxtField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
        NSLog(@"測試:%@",x);
}];
控制臺打?。?2018-03-23 17:57:00.497956+0800 ZSTest[4351:263810] 測試:1
2018-03-23 17:57:00.498237+0800 ZSTest[4351:263810] 測試:12
2018-03-23 17:57:00.498375+0800 ZSTest[4351:263810] 測試:123

沒錯的,不使用代理方法,也沒有action的響應(yīng)處理,我們僅僅使用了一行方法就實現(xiàn)了對文本框輸入內(nèi)容的實時打印。由此,RAC的實用性可見一斑。

五、ReactiveCocoa信號機制

我們會對上面的代碼產(chǎn)生疑問,RAC是怎么做到上述代碼功能的呢?而且我們常說的訂閱者又在哪里呢?

其實RAC已經(jīng)使用Category的形式為我們基本的UI控件創(chuàng)建了信號(如上例中的rac_textSignal),所以這里我們才可以很方便的實現(xiàn)信號訂閱,而且訂閱者在整個過程中也是對于我們隱藏的。 現(xiàn)在我們使用自定義信號的方法,從創(chuàng)建信號到訂閱信號細致的了解一下這個過程。首先上一段創(chuàng)建信號的測試代碼如下:

//創(chuàng)建信號
RACSignal *testSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    //1.訂閱者發(fā)送信號內(nèi)容
    [subscriber sendNext:@"發(fā)送信號內(nèi)容"];
    //2.訂閱者發(fā)送信號完成的信息,不需要再發(fā)送數(shù)據(jù)時,最好發(fā)送信號完成,可以內(nèi)部調(diào)起清理信號的操作。
    [subscriber sendCompleted];
    //3.創(chuàng)建信號的Block參數(shù),需要返回一個RACDisposable對象 ,可以返回nil。
    //RACDisposable對象用于取消訂閱信號,此block在信號完成或者錯誤時調(diào)用。
    RACDisposable *racDisposable = [RACDisposable disposableWithBlock:^{
       NSLog(@"信號Error或者Complete時銷毀");
    }];
    return racDisposable;
}];
    
//訂閱信號
[testSignal subscribeNext:^(id  _Nullable x) {
    //新變化的值
    NSLog(@"訂閱信號:subscribeNext:%@",x);
} error:^(NSError * _Nullable error) {
    //信號錯誤,被取消訂閱,被移除觀察
    NSLog(@"訂閱信號:Error:%@",error.description);
} completed:^{
    //信號已經(jīng)完成,被取消訂閱,被移除觀察
    NSLog(@"訂閱信號:subscribeComplete");
}];

控制臺打印:
2018-03-23 17:57:00.497956+0800 ZSTest[4351:263810] 訂閱信號:subscribeNext:發(fā)送信號內(nèi)容
2018-03-23 17:57:00.498237+0800 ZSTest[4351:263810] 訂閱信號:subscribeComplete
2018-03-23 17:57:00.498375+0800 ZSTest[4351:263810] 信號Error或者Complete時銷毀

我們通過觀察源碼來理解整個過程:

1.創(chuàng)建信號

創(chuàng)建信號,我們需要使用RACSignal的類方法createSignal。該方法需要一個Block作為參數(shù)。查看源碼,我們就會發(fā)現(xiàn)RACSignal最終是通過調(diào)用自己子類RACDynamicSignal的createSignal方法,將這個Block設(shè)置給了自己的didSubscribe屬性的。

//RACSignal.m文件
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    return [RACDynamicSignal createSignal:didSubscribe];
}
//RACDynamicSignal.h文件
@interface RACDynamicSignal ()
// The block to invoke for each subscriber.
@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber);
@end
//RACDynamicSignal.m文件
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}

didSubscribe:這是創(chuàng)建信號時候需要傳入的一個block,它的傳入?yún)?shù)是訂閱者subscriber,而返回值是需要是一個RACDisposable對象。創(chuàng)建信號后的didSubscrib是一個等待執(zhí)行的block。

RACSubscriber:表示訂閱者,創(chuàng)建信號時訂閱者發(fā)送信號,這里的訂閱者是一個協(xié)議而非一個類。信號需要訂閱者幫助其發(fā)送數(shù)據(jù)。查看RACSubscriber的協(xié)議,我可以看到以下幾個方法:

//發(fā)送信息
- (void)sendNext:(nullable id)value;
//發(fā)送錯誤消息
- (void)sendError:(nullable NSError *)error;
//發(fā)送完成信息
- (void)sendCompleted;
//
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;

在創(chuàng)建一個信號的時候,訂閱者使用sendNext發(fā)送信息。而且如果我們不再發(fā)送數(shù)據(jù),最好在這里執(zhí)行一次sendCompleted方法,這樣的話,信號內(nèi)部會自動調(diào)用對應(yīng)的方法取消信號訂閱。

RACDisposable:這個類用于取消訂閱信號和清理資源,在信號出現(xiàn)錯誤或者信號完成的時候,信號會自動調(diào)起RACDisposable對象的block方法。在代碼中我們也可以看到,創(chuàng)建RACDisposable對象是使用disposableWithBlock方法設(shè)置了一個block操作,執(zhí)行block操作之后,信號就不再被訂閱了。

總結(jié):創(chuàng)建信號就是使用createSignal方法,創(chuàng)建一個信號,并為信號設(shè)置了一個didSubscribe屬性(也就是一系列訂閱者需要做的操作)。

2.訂閱信號

進入訂閱信號的源碼我們看到如下代碼:

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

在此方法中,我們可以看到訂閱信號有兩個過程:
過程1:使用subscribeNext的方法參數(shù),創(chuàng)建出一個訂閱者subscriber。
過程2:信號對象執(zhí)行了訂閱操作subscribe,方法中傳入?yún)?shù)是剛創(chuàng)建的訂閱者。

注:這也就解釋了我們常提起卻看不見的訂閱者存在哪里的問題。真實開發(fā)中我們只關(guān)心訂閱者需要發(fā)送的值就行了,而不需要關(guān)心其內(nèi)部訂閱的過程。

繼續(xù)打開信號的subscribe方法,看到源碼如下:

- (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;
}

上面的代碼中我們不難看出:除了對于訂閱者和清理對象的再次封裝外,最重要的就是創(chuàng)建信號時為信號設(shè)置Block(didSubscribe)被調(diào)用了,而且Block參數(shù)使用了我們創(chuàng)建的訂閱者。

六、本篇總結(jié)

1.自創(chuàng)建信號會傳入一個Block(didSubscribe),Block中遵循協(xié)議的訂閱者會調(diào)用sendNext方法發(fā)送消息。而在訂閱信號subscribeNext時,會在內(nèi)部創(chuàng)建一個訂閱者,并將其傳遞給原先賦值的didSubscribe,并執(zhí)行這個Block。

2.但是我們應(yīng)該注意:上述的分析只是其中信號機制的一種情況罷了。RAC對于UI組件信號的封裝可能有所不同,比如之前我們看到的輸入框信號,執(zhí)行訂閱信號subscribeNext時并不立即執(zhí)行打印,而是監(jiān)聽到輸入時打印。這其實是該信號使用了concat又做了一系列的操作。對于不同的信號我們只需要理解上述分析中提到幾個關(guān)鍵屬性,就可以結(jié)合源碼很好的理解信號機制的使用了。

本篇的重點在于對RAC的基本介紹,是為了更好的理解信號機制,這僅相當于打開一個切入口來認識RAC。關(guān)于RAC的詳細用法可以參考下一篇:ReactiveCocoa函數(shù)響應(yīng)式編程-應(yīng)用篇,這里將總結(jié)RAC關(guān)于信號的各種用法。

其他參考鏈接:
1.ReactiveCocoa入門教程:第一部分
2.最快讓你上手ReactiveCocoa之基礎(chǔ)篇
3.這樣好用的ReactiveCocoa,根本停不下來
4.函數(shù)式編程與面向?qū)ο蟮谋容^

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者。

友情鏈接更多精彩內(nèi)容