函數響應式編程及ReactiveObjC學習筆記 (-)


最近無意間看到一個視頻講的ReactiveObjC, 覺得挺好用的 但聽完后只是了解個大概.

在網上找了些文章, 有的寫的比較易懂但看完還是沒覺得自己能比較好的使用RAC, 有的甚至讓我看不下去

 

這兩天剛好公司項目交付閑下來, 想自己去啃下官方文檔

ReactiveCocoa是一個基於函數響應式編程的OC框架.

那么什么是函數式響應式編程呢?概念我就不講了 因為我講的也不一定准確, 大家可以去baidu看看大神們的解釋

下面我大概演示下響應式編程的樣子

Masonry是比較常見的一個響應式框架, 它的的用法舉例如下:

make.centerY.equalTo(self.view).offset(100);

大家注意它的用法, 點號調用一個事件或屬性后可以接着點號調用, 這里一個比較明顯的函數響應式編程的好處就是我們可以把一些要使用的連貫的或者有先后順序的調用方法和事件連在一起, 邏輯清晰明了的完成代碼.

那么要如何實現這樣的調用方式呢?

centerY.equalTo(self.view)這個能執行的話equalTo就必須是一個返回對象的block

下面試試自己來實現這個, 

建一個Person對象,  加上跑步, 走路的方法

Class: Person;  Method: run; walk;

我們拆分成幾個步驟來做, 首先實現

[[person run] walk];先跑, 跑累了再走

要實現這樣的調用的話, run就必須返回person, 為了還能繼續接着這樣調用walk也要返回person

好了, 思路就很清晰了, 我們上代碼

#import <Foundation/Foundation.h>

@interface Person : NSObject

- (Person *)run;
- (Person *)walk;

@end

 

#import "Person.h"

@implementation Person

- (Person *)run {
    
    NSLog(@"跑步");
    
    // 延時2s
    [NSThread sleepForTimeInterval:2];
    
    return self;
}
- (Person *)walk {
    
    NSLog(@"走路");
    
    // 延時2s
    [NSThread sleepForTimeInterval:2];
    
    return self;
}

@end

大家可以看到, 我們run 跟 walk方法都會返回對象本身, 為了延時我加了個延遲2s

我們調用試試

    // 創建對象
    Person *person = [[Person alloc] init];
    
    // 嘗試調用
    [[person run] walk];

結果如下:

2017-07-21 21:59:30.962 RAC[63284:11390973] 跑步
2017-07-21 21:59:33.036 RAC[63284:11390973] 走路

跟預期一致, 我們再來實現person.run().walk();

剛才說了要返回一個返回值是對象的block, 我們來實現下 代碼如下:

- (Person * (^)())runBlock {
    
    Person * (^block)() = ^() {
        
        NSLog(@"run");
        // 延時2s
        [NSThread sleepForTimeInterval:2];
        return self;
    };
    
    return block;
}

- (Person * (^)())walkBlock {
    
    Person * (^block)() = ^() {
        
        NSLog(@"walk");
        // 延時2s
        [NSThread sleepForTimeInterval:2];
        return self;
    };
    
    return block;
}

如果對block使用不熟的同學可以花點時間琢磨下block的結構

我們調用試試看

    // 調用block
    person.runBlock().walkBlock();

結果:

2017-07-22 13:58:01.306 RAC[64288:11757631] run
2017-07-22 13:58:03.381 RAC[64288:11757631] walk

好了, 這樣我們就自己實現了一個基於函數響應式的小Demo

 

常規情況下, 我們寫代碼是一般是定義很多個變量和方法,  在不同的狀態和業務流程下去改變變量的值或者調用對應的方法.

而RAC采用信號機制來獲取當前的, 同時也能直接處理將來要如何修改這些值, 通過利用鏈式響應編程來書寫結構邏輯清晰的代碼, 不用我們在不同的地方去給我們屬性值做處理, 

 

比如我們要給一個UITextField做監聽, 當值改變的時候做一些處理例如打印當前輸入的值, 常規用法下我們要讓當前控制器或者類遵循textField的代理, 然后把textField的代理指給當前類, 實現代理方法, 代碼大概會是這樣:

@interface ViewController ()<UITextFieldDelegate>

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    

    UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 100, 35)];
    
    textField.center          = self.view.center;
    textField.backgroundColor = [UIColor yellowColor];
    textField.delegate        = self;
    
    [self.view addSubview:textField];
}

#pragma mark - UITextFieldDelegate Method

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    
    
    NSLog(@"%@", textField.text);
    return YES;
}

@end

或者大家也能用KVO來實現, 當代碼比較少的時候這樣看起來還比較清晰, 如果當時一個完整的項目呢, 那么多方法要寫我們要看看某一個textField事件估計要花一些時間在代碼里面去找這個方法, 代碼就不是很直觀了.

 

那么RAC怎么幫助我們解決這個問題呢,  上面有說過RAC是通過信號來管理的, 那么什么是信號呢?

RACSignal就是這個類, 我們試試自己創建一個信號 首先我們先用Pod導入ReactiveObjC庫

pod 'ReactiveObjC', '~>3.0.0'

導入頭文件

#import <ReactiveObjC.h>

我們創建一個信號:

// 創建一個信號
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) { NSLog(@"創建一個信號"); return nil; }];

直接運行看看, 好像什么都沒有發生, 怎么回事呢? 我們點擊創建新的方法看看他做了什么

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    return [RACDynamicSignal createSignal:didSubscribe];
}

他給我們返回了一個RACDynamicSignal,  這個是什么呢? 我們點他看看

@interface RACDynamicSignal : RACSignal

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;

原來他是RACSignal的一個子類, 它也重寫了createSignal方法, 我們現在實際是調用了他的創建信號的方法. 那我們看看它這個方法都做了什么

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}

它創建了一個RACDynamicSignal實例, 然后把didSubscribe復制了一份復制給創建的實例, 然后重命名后就直接返回給我們了.

然后就結束了, 難怪我們什么效果都沒有看到

RAC里面有一個很重要的理念: 創建信號必須訂閱, 訂閱了信號才會被執行.  

沒有訂閱的信號是冷信號 不會產生任何效果, 訂閱信號就從冷信號變成熱信號, 就可以執行各種操作.

 

我們看看如何訂閱:

    // 創建一個信號
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        NSLog(@"創建一個信號");
        return nil;
    }];
    
    // 訂閱一個信號
    [signal subscribeNext:^(id  _Nullable x) {
        
        NSLog(@"訂閱一個信號");
    }];

我們運行看看

2017-07-22 15:05:58.760 RAC[65085:12004278] 創建一個信號

創建信號的block執行了,  但是訂閱的信號沒有執行, 我們看看點開subscribeNext看看為什么

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

它首先判斷我們的block不會空, 然后創建了一個RACSubscriber訂閱者, 並把我們的block給它了

再點subscriber的創建方法看看它做了什么

+ (instancetype)subscriberWithNext:(void (^)(id x))next error:(void (^)(NSError *error))error completed:(void (^)(void))completed {
    RACSubscriber *subscriber = [[self alloc] init];

    subscriber->_next = [next copy];
    subscriber->_error = [error copy];
    subscriber->_completed = [completed copy];

    return subscriber;
}

它只是創建了一個subscriber實例, 然后把我們的block拷貝給它 還是什么都沒有做

我們再看看

[self subscribe:o];

做了什么

- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCAssert(NO, @"This method must be overridden by subclasses");
    return nil;
}

它做了非空判斷, 然后說這個方法必須被子類重寫, 這里好像也啥都沒干啊 怎么創建信號的block就執行了呢?

大家想想, 我們剛才創建信號的時候, 是不是就是調用的是RACSignal的子類DynamicSignal, 所以這里實際上運行的也是這個DynamicSignal的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;
}

 

首先它也是先判斷是否為空, 然后創建了一個RACCompoundDisposable實例

接着有給我們的subscriber重新賦值, 我們看看這個RACPassthroughSubscriber

// A private subscriber that passes through all events to another subscriber
// while not disposed.
@interface RACPassthroughSubscriber : NSObject <RACSubscriber>

它是把事件從一個subscriber傳遞給另外一個subscriber, 所以這里就是它把我們原有的subscriber + 之前創建的signal + disposable加起來組成一個新的subscriber重新賦值給我們的subscriber,  相當於把我們創建的信號跟訂閱綁定到一起了

 

接着如果didsubscribe不為空的話, 及繼續執行否則直接返回disposable

我們的didsubscriber大家還記得是什么嗎? 打印創建信號那段對吧

然后我們看到它創建了一個RACDisposable實例, 但是它用的是一個RACScheduler來創建的

我們看看這個RACScheduler是個啥

/// Schedulers are used to control when and where work is performed.
@interface RACScheduler : NSObject

哦 它是一個類似Timer或者dispatch_after的東西, 控制事件在什么時候觸發

我們再看看這個subscriptionScheduler

+ (RACScheduler *)subscriptionScheduler {
    static dispatch_once_t onceToken;
    static RACScheduler *subscriptionScheduler;
    dispatch_once(&onceToken, ^{
        subscriptionScheduler = [[RACSubscriptionScheduler alloc] init];
    });

    return subscriptionScheduler;
}

它創建了一個RACScheduler單例, 不過是用RACSubscriptionScheduler來初始化的, 我們再看看它

@interface RACSubscriptionScheduler : RACScheduler

是一個RACSchedule的子類, 它重寫的初始化和schedule , after...等等方法,  先記下一會看看是否用到了這些重寫的方法

這里我們先看看這個子類重寫的初始化方法

- (instancetype)init {
    self = [super initWithName:@"org.reactivecocoa.ReactiveObjC.RACScheduler.subscriptionScheduler"];

    _backgroundScheduler = [RACScheduler scheduler];

    return self;
}

重命名, 然后給持有的一個RACScheduler對象backgroundScheduler賦值, 我們看看RACScheduler的scheduler做了什么

+ (RACScheduler *)scheduler {
    return [self schedulerWithPriority:RACSchedulerPriorityDefault];
}

繼續點

+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority {
    return [self schedulerWithPriority:priority name:@"org.reactivecocoa.ReactiveObjC.RACScheduler.backgroundScheduler"];
}

還是看不出來, 繼續點

+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {
    return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];
}

返回了一個RACTargetQueueScheduler實例, targetQueue是一個dispatch_get_global_queue全局隊列

/// A scheduler that enqueues blocks on a private serial queue, targeting an
/// arbitrary GCD queue.
@interface RACTargetQueueScheduler : RACQueueScheduler

/// Initializes the receiver with a serial queue that will target the given
/// `targetQueue`.
///
/// name        - The name of the scheduler. If nil, a default name will be used.
/// targetQueue - The queue to target. Cannot be NULL.
///
/// Returns the initialized object.
- (instancetype)initWithName:(nullable NSString *)name targetQueue:(dispatch_queue_t)targetQueue;

一個類似隊列的東西, 看看它的初始化方法

- (instancetype)initWithName:(NSString *)name targetQueue:(dispatch_queue_t)targetQueue {
    NSCParameterAssert(targetQueue != NULL);

    if (name == nil) {
        name = [NSString stringWithFormat:@"org.reactivecocoa.ReactiveObjC.RACTargetQueueScheduler(%s)", dispatch_queue_get_label(targetQueue)];
    }

    dispatch_queue_t queue = dispatch_queue_create(name.UTF8String, DISPATCH_QUEUE_SERIAL);
    if (queue == NULL) return nil;

    dispatch_set_target_queue(queue, targetQueue);

    return [super initWithName:name queue:queue];
}

前面很清晰, 創建了一個隊列

看看super的初始化做了什么< 

- (instancetype)initWithName:(NSString *)name queue:(dispatch_queue_t)queue {
    NSCParameterAssert(queue != NULL);

    self = [super initWithName:name];

    _queue = queue;
#if !OS_OBJECT_USE_OBJC
    dispatch_retain(_queue);
#endif

    return self;
}

保存了這個隊列, 就結束了

 

還記得到哪里了嗎, 我把代碼再貼下

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

現在到這個schedule了, 我們看看它做了什么, 注意哦這個時候要去看RACScheduler的子類RACSubscriptionScheduler中的方法

- (RACDisposable *)schedule:(void (^)(void))block {
    NSCParameterAssert(block != NULL);

    if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block];

    block();
    return nil;
}

看到了嗎 block(), 是不是從來沒有覺得這對括號這么可愛的, 我們看着這么半天終於看到一個執行block的地方了

先不急, 我們看看它之前的代碼

首先判斷block非空, 然后如果RACScheduler.currentScheduler為空的話, 就讓backgroundscheduler去調用block

這個backgroundscheduler看菜我們有看是一個RACScheduler的實例, 我們先看看如果為空要怎么樣

- (RACDisposable *)schedule:(void (^)(void))block {
    NSCAssert(NO, @"%@ must be implemented by subclasses.", NSStringFromSelector(_cmd));
    return nil;
}

啥都沒干,  看起來這里我們就要期待它一定不為空了,  不然我們我們辛辛苦苦找到一個執行的地方就又白找了

那么它到底是不是空呢, 我們先看看它的定義

/// The current scheduler. This will only be valid when used from within a
/// -[RACScheduler schedule:] block or when on the main thread.
+ (nullable RACScheduler *)currentScheduler;

說是只要在主線程, 或者調用過[RACScheduler schedule]方法就不為空

那么我們現在是在那個線程呢? 還記得嗎 我們剛才創建了一個全局隊列, 那么有沒有切換隊列呢

好像沒有, 對吧, 沒關系我們看看這個currentScheduler的setter方法

+ (RACScheduler *)currentScheduler {
    RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey];
    if (scheduler != nil) return scheduler;
    if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;

    return nil;
}

看到沒, 它首先創建了一個RACScheduler實例, 用的NSThread的threadDIctonary去找RACScheduleCurrentScheduleKey

如果找到了就在返回這個隊列, 否則如果是主線程就返回主線程隊列,

我們當前並沒有切換隊里, 這里應該是給其他情況下使用的

好了, 我們再看看執行的是什么block

RACDisposable *innerDisposable = self.didSubscribe(subscriber);
            [disposable addDisposable:innerDisposable];

我們注意第一句話, 這里就執行了didSubscribe並把返回值賦給了一個RACDisposable

記得didSubscribe里面有什么嗎? 對就是打印創建信號的那個block

到這里我們就看到為什么前面創建信號的時候沒有調用那里的block, 原來是訂閱的這個地方調用的.

所以創建信號的block要訂閱的時候才會去執行

 

不過好像到這里都沒有去執行我們訂閱的block, 只是把信號跟訂閱捆綁到了一起.

那么要怎么執行訂閱的block呢? 不知道大家注意到沒, 我們訂閱用的是subscribeNext, 看字面意思是訂閱然后做去執行

看字面理解我們現在只是完成了訂閱的操作, 但沒有觸發next

要怎么觸發next呢?

我們可以想想這個觸發要什么時候做呢? 目前只有創建和訂閱 那么肯定在創建的時候觸發對不對

我們看看創建信號的block里面的一個id格式的參數subscriber, 不過它有實現一個協議RACSubscriber

我們看看里面有什么

@protocol RACSubscriber <NSObject>
@required

/// Sends the next value to subscribers.
///
/// value - The value to send. This can be `nil`.
- (void)sendNext:(nullable id)value;

/// Sends the error to subscribers.
///
/// error - The error to send. This can be `nil`.
///
/// This terminates the subscription, and invalidates the subscriber (such that
/// it cannot subscribe to anything else in the future).
- (void)sendError:(nullable NSError *)error;

/// Sends completed to subscribers.
///
/// This terminates the subscription, and invalidates the subscriber (such that
/// it cannot subscribe to anything else in the future).
- (void)sendCompleted;

/// Sends the subscriber a disposable that represents one of its subscriptions.
///
/// A subscriber may receive multiple disposables if it gets subscribed to
/// multiple signals; however, any error or completed events must terminate _all_
/// subscriptions.
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;

@end

很清楚對不對, 原來在這里,

那我們試試看發送一個sendNext

    // 創建一個信號
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        NSLog(@"創建一個信號");
        
        // 發送信號
        [subscriber sendNext:@"發送一個信號"];
        
        return nil;
    }];
    
    // 訂閱一個信號
    [signal subscribeNext:^(id  _Nullable x) {
        
        NSLog(@"訂閱一個信號");
    }];

運行看看:

2017-07-22 17:35:38.937 RAC[66240:12626323] 創建一個信號
2017-07-22 17:35:38.937 RAC[66240:12626323] 訂閱一個信號

訂閱的block執行了對不對, 那么還有個問題我們發送信號到哪里去了呢

我們把x打印看看

    // 創建一個信號
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        NSLog(@"創建一個信號");
        
        // 發送信號
        [subscriber sendNext:@"發送一個信號"];
        
        return nil;
    }];
    
    // 訂閱一個信號
    [signal subscribeNext:^(id  _Nullable x) {
        
        NSLog(@"訂閱一個信號");
        NSLog(@"訂閱到的: %@", x);
    }];

我們把訂閱信號block里面的參數打印看看

2017-07-22 17:37:09.294 RAC[66282:12633516] 創建一個信號
2017-07-22 17:37:09.295 RAC[66282:12633516] 訂閱一個信號
2017-07-22 17:37:09.295 RAC[66282:12633516] 訂閱到的: 發送一個信號

打印出來了, 原來是這樣傳遞的 

sendNext就把內容傳遞到了Next的block中了

 

好了, 先寫到這里 希望有幫助大家理解創建信號 訂閱信號的到底有在做什么.

 

下次我們再來看具體的使用方法

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM