ReactiveCocoa學習筆記二--回調統一(9.15更新)

9.15更新

補充關于NSURLConnection的category方法+rac_sendAsynchronousRequest源碼解析
補充關于rac_signalForSelector的源碼解析

9.14更新

補充了關于block回調的內容
補充了關于delegate回調的內容

例行廢話

原本打算寫剩下的一些集合類的方法的, 后面發現里面的東西基本上都差不多, 如果理解了整個NSArray在ReactiveCocoa下的本質工作, 那么剩余的都可以類推, 或者本身就是掛靠在NSArray上的, 例如: NSDictionary的3個拓展方法都和NSArray有關. 這個看看頭文件, 寫幾行測試代碼即可.

這一回主要看看輔助方法, 也就是一些在實際代碼編寫中能夠初步用到的, 當然, NSArray和其它集合類是平時有需要的話也是可以用到的, 只是大多數人可能不愿意為了這么點特性來引入一個這么復雜的框架.

異步機制

記得在RAC的github里, README有這么介紹RAC的一段話:

One of the major advantages of RAC is that it provides a single, unified approach to dealing with asynchronous behaviors, including delegate methods, callback blocks, target-action mechanisms, notifications, and KVO.

上面說RAC的一個優勢就是提供了一個單一的機制(就是信號處理)來聯合目前所有處理異步行為, 包括代理,回調block,target-action機制,通知和KVO.因為各種回調模式使用上并沒有說一個完全統一的規范, 沒有人能夠確定地說在某種情況下使用A模式就一定比B模式好, 因此我們如果要在項目中使用RAC, 不放從這里入手, 慢慢把新增代碼改用RAC的形式來寫.

下面的篇幅我打算用一個模式一個小節來看.

Notification

先看看頭文件, 只有一個方法:

// NSNotificationCenter+RACSupport.h
- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object;

很容易看出來就是為指定的通知名和攜帶的object創建一個信號, 我們先用一下看看實際情況如何:

[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"NotificationRAC" object:nil] subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter]  postNotificationName:@"NotificationRAC" object:@"RAC" userInfo:@{@"A":@1}];
});

// 打印出:
NSConcreteNotification 0x7f9b5ab0a720 {name = NotificationRAC; object = RAC; userInfo = {
    A = 1;
}}

從打印的信息來看, 和我們自己寫一個target一個selector的傳統形式并沒有太大差別, 所以, 我們去看看實現:

// NSNotificationCenter+RACSupport.m
- (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object {
    @unsafeify(object);
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        @strongify(object);
        id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) {
            [subscriber sendNext:note];
        }];

        return [RACDisposable disposableWithBlock:^{
            [self removeObserver:observer];
        }];
    }] setNameWithFormat:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object];
}

中間2個宏可以在代碼查看預處理之后的代碼:

@unsafeify(object) ==> @autoreleasepool {} __attribute__((objc_ownership(none))) __typeof__(object) object_weak_ = (object);;

@strongify(object) ==> @autoreleasepool {} __attribute__((objc_ownership(strong))) __typeof__(object) object = object_weak_;

本質上就是__unsafe_unretained和__strong的宏定義, 除此之外, 核心代碼就是中間的:

[self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) {
    ...
}];

所以, RAC只是借用了蘋果提供的API進行了封裝, 只是RAC幫我們管理起來了這個通知的生命周期, 不需要我們手動去remove掉了.

KVO

KVO是出名難用的一個模式, 但是有些情況確不得不用, 另外給個小tip, 據我個人的實際使用經驗, 觀察者與被觀察者任意一個被析構掉, KVO如果還未解除都會發生crash, 這種情況會在memoryWarning的時候發生, 所以用到的地方多測試一下, 另外, KVOController是解決這個問題的好幫手, Facebook出品.
回歸正題, 我們來看看在RAC里面, KVO是什么樣子:

- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer block:(void (^)(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent))block;

直接用Block來處理了, 并且返回了一個Disposable, 意味著我們能隨時干掉這個監聽. 為了輔助完成KVO, 新建一個測試類, 里面含有一個name的NSString屬性:

DRCallbackTest *test = [DRCallbackTest new];
    
    RACDisposable *dis = [test rac_observeKeyPath:@"name" options:NSKeyValueObservingOptionNew observer:test block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
        NSLog(@"change:%@", change);
    }];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        test.name = @"ABC";
    });
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [dis dispose];
        test.name = @"EFG";
    });

// 打印出:
change:{
    kind = 1;
    new = ABC;
}

如上面所看到的, 我們只能收到第一次對name的修改, 后面因為dispose了, 所以我們不能再繼續收到后續的KVO了. 另外observer參數傳nil也是可以的.
我們來看看實現吧, 代碼有點長, 且分了好幾個類來實現的, 我們就按步驟來看吧:

第一步: addObserver

我們在執行了上面的那一段代碼之后, 內部實現會創建RACKVOTrampoline這個類的示例來實際處理我們的監聽, 而這個類又會把監聽關系放在RACKVOProxy進行管理, 先來看看proxy的addObserver方法:

// RACKVOProxy.m
- (void)addObserver:(__weak NSObject *)observer forContext:(void *)context {
    NSValue *valueContext = [NSValue valueWithPointer:context];

    dispatch_sync(self.queue, ^{
        [self.trampolines setObject:observer forKey:valueContext];
    });
}

比較簡單, self.trampolines是一個NSMapTable, 之所以用NSMapTable而不是Map, 主要是因為它對key和value是弱引用. add和remove都是同步操作的. 所謂的context其實就是一個RACKVOTrampoline的實例, 所以在proxy這里, 一個實例對應一個監聽. 我們我們基本可以認定, proxy會有:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

這個惡心的方法. 所以在RACKVOTrampoline中, 有這么一行:

[strongTarget addObserver:RACKVOProxy.sharedProxy forKeyPath:self.keyPath options:options context:(__bridge void *)self];
第二步:獲得監聽

所以, 當name的值有變的時候, proxy會首先監聽到:

// RACKVOProxy.m
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    NSValue *valueContext = [NSValue valueWithPointer:context];
    __block NSObject *trueObserver;

    dispatch_sync(self.queue, ^{
        trueObserver = [self.trampolines objectForKey:valueContext];
    });

    if (trueObserver != nil) {
        [trueObserver observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}

然后調用RACKVOTrampoline的監聽實現:

// RACKVOTrampoline.m
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    if (context != (__bridge void *)self) {
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
        return;
    }

    RACKVOBlock block;
    id observer;
    id target;

    @synchronized (self) {
        block = self.block;
        observer = self.observer;
        target = self.weakTarget;
    }

    if (block == nil || target == nil) return;

    block(target, observer, change);
}

所以, 從始至終就沒有我們之前傳入那個observer什么事, 因此它為空也是可以收到的.
最后一步就是remove了, 里面涉及到很多生命周期的管理, 比較復雜, 一句話粗略概括的話, 就是如果傳入的監聽者和target被dealloc掉了, 那就要調用相對應的dispose, 這個做法是貫穿了整個RAC框架的, 因為比較復雜, 所以要獨立成章節來看, 在這個主題下先不細看了, 以免丟失主題.
另外, 因為KVO在響應式里面占了重頭戲, 所以RAC針對這塊也有相應的宏來簡寫這塊實現:

[RACObserve(test, name) subscribeNext:^(id x) {
        NSLog(@"name = %@", x);
    }];

上面的測試代碼換成這樣也是可以的.

target-action

這個異步機制在目前來說用的最多的應該就是UIButton加action的地方了吧, 因為這個方法繼承自UIControl, 所以我們看看對UIControl的拓展:

// UIControl+RACSignalSupport.h
- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents;

只有一個方法, 有前面的經驗我們基本上就知道怎么使用了:

... // new一個button, 加在self.view上
[[self.btn
        rac_signalForControlEvents:UIControlEventTouchUpInside]
        subscribeNext:^(id x) {
         NSLog(@"clicked %@", x);
     }];
// 每次點擊打印出:
clicked <UIButton: 0x7fc15a016270; frame = (100 100; 100 100); opaque = NO; layer = <CALayer: 0x7fc15a00d110>>

內部實現則是直接創建一個信號, 以subcriber為target, 然后設置selector為sendNext, 所以每次btn被點擊都會調用[subcriber sendNext:]:

- (RACSignal *)rac_signalForControlEvents:(UIControlEvents)controlEvents {
    @weakify(self);

    return [[RACSignal
        createSignal:^(id<RACSubscriber> subscriber) {
            @strongify(self);

            [self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents];
            [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
                [subscriber sendCompleted];
            }]];

            return [RACDisposable disposableWithBlock:^{
                @strongify(self);
                [self removeTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents];
            }];
        }]
        setNameWithFormat:@"%@ -rac_signalForControlEvents: %lx", self.rac_description, (unsigned long)controlEvents];
}

代碼實現比較簡單, 只是里面涉及到一些生命周期管理的東西, 差不多就是在Button要被dealloc的時候執行一次sendCompleted:, sendCompleted會調用disposable的dispose, 所以會removeTarget:action:forControlEvents:

// RACSubscriber.m
- (void)sendCompleted {
    @synchronized (self) {
        void (^completedBlock)(void) = [self.completed copy];
        [self.disposable dispose];

        if (completedBlock == nil) return;
        completedBlock();
    }
}

block

從嚴格的角度上來說, block并沒有被替換掉, 畢竟Signal subscript的回調也還是block, 不過從概念上來說block回調和Signal subscript還是有本質區別的.
既然官方文檔已經提了, 那就講一個經典常用的例子吧:
在NSURLConnection中有一個
+ (void)sendAsynchronousRequest:(NSURLRequest*) request queue:(NSOperationQueue*) queue completionHandler:(void (^)(NSURLResponse* __nullable response, NSData* __nullable data, NSError* __nullable connectionError)) handler
方法, 通過block來回調請求的response或者error, 在RAC的世界中, NSURLConnection也被拓展了:
+ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request
接收一個request, 返回一個Signal, 剩下的事情就是對這個信號的操作了, map, reduceEach等等都可以網上加了, 這里給出一個我在demo中寫的下載圖片完整例子:

- (RACSignal *)fetchInfos
{
    @weakify(self);
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://ryan.com/getList"]];
    return [[[[[[NSURLConnection rac_sendAsynchronousRequest:request]
                reduceEach:^id(NSURLResponse *response, NSData *data){
                    return data;
                }]
                deliverOn:[RACScheduler mainThreadScheduler]]
                map:^id(NSData*data){
                    id results =
                    [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                    return [[[results[@"list"] rac_sequence]
                             map:^id(NSDictionary *info){
                                 @strongify(self);
                                 DRModel *model = [[DRModel alloc] initWithDictionary:info];
                                 [self downloadImageForModel:model];
                                 return model;
                             }]
                             array];
                }]
                publish]
                autoconnect];
}

那個URL是我隨便構造的, 然后mock了HTTP請求, 返回我自己要的數據.

reduceEach會遍歷所有的元素, 然后替換掉原本的, 本質上也是調用的map, 只是map只接收一個參數, 所以response和data會被包裝為RACTuple, 要一個個取出來, 用reduce可以接收多個參數, 所以用reduceEach更加方便和清晰. 對reduceEach源碼感興趣的同學最后附錄會有解析. 在reduceEach里我們如果沒有額外的需求直接返回我們感興趣的data即可.

deliverOne到了mainThreadScheduler是因為接下來可能要渲染UI了, 要切換回主線程;

我構造的數據中, data反序列化之后其實是一個map, 里面有一個list, list里面又是map, 裝著我要初始化Model的信息.

至于publish和autoconnect則是和信號有關, 這里只提一下前者是把Signal變為multicastConnection用的, 后者是在有人subscribe的情況下才進行連接, 也就是激活signal.

這里就完整地把NSURLConnection的completionBlock給替換掉了. 雖然看起來做了更多的活, 但實際上我們把所有相關的代碼都集中處理了, 不需要各個方法, block跟來跟去看執行情況.

源碼部分今天暫時不分析了, 后續更新在附錄上.

Delegate

delegate其實個人感覺比較蛋疼, 為了少寫一個方法, 然后去弄這么個東西出來, 個人感覺不是很好用, 我覺得RAC的作者自己也覺得用的人不多, 所以默認都不包含這個東西的頭文件. 而且最主要的是, 它只能替換掉返回void的方法, 作用十分有限, 我們以替換tableViewDelegate中的tableView:didSelectRowAtIndexPath:為例:

  1. 要先使用Signal來替換delegate先要#import "RACDelegateProxy.h", 它不包含在默認的ReactiveCocoa.h里.

  2. viewDidLoad里寫上:

[[self rac_signalForSelector:@selector(tableView:didSelectRowAtIndexPath:)
                  fromProtocol:@protocol(UITableViewDelegate)]
                  subscribeNext:^(RACTuple *x) {
                      @strongify(self);
                      UITableView *tableView = x.first;
                      NSIndexPath *indexPath = x.second;
                      [tableView deselectRowAtIndexPath:indexPath animated:YES];
                      DRPageViewController *pageVC = [[DRPageViewController alloc] initWithPhotoModels:self.datas currentPhotoIndex:indexPath.row];
                      pageVC.delegate = self;
                      [self.navigationController pushViewController:pageVC animated:YES];
     }];
  1. 最后別忘了
    self.tableView.delegate = self;

源碼也比較簡單, 下次更新再補上分析吧.

結語

這章涉及到了Signal, 但是因為signal的話題太大, 必須新開一章節來學習, 所以就先不講了.

附錄

reduceEach

reduceEach是RACStream的方法:

// RACStream.m
- (instancetype)reduceEach:(id (^)())reduceBlock {
    NSCParameterAssert(reduceBlock != nil);

    __weak RACStream *stream __attribute__((unused)) = self;
    return [[self map:^(RACTuple *t) {
        NSCAssert([t isKindOfClass:RACTuple.class], @"Value from stream %@ is not a tuple: %@", stream, t);
        return [RACBlockTrampoline invokeBlock:reduceBlock withArguments:t];
    }] setNameWithFormat:@"[%@] -reduceEach:", self.name];
}

map中返回RACBlockTrampoline invoke了block, tuple做為參數, 再接著看里面實現:

+ (id)invokeBlock:(id)block withArguments:(RACTuple *)arguments {
    NSCParameterAssert(block != NULL);

    RACBlockTrampoline *trampoline = [[self alloc] initWithBlock:block];
    return [trampoline invokeWithArguments:arguments];
}

只是返回一個實例, 繼續看:

- (id)invokeWithArguments:(RACTuple *)arguments {
    SEL selector = [self selectorForArgumentCount:arguments.count];
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[self methodSignatureForSelector:selector]];
    invocation.selector = selector;
    invocation.target = self;

    for (NSUInteger i = 0; i < arguments.count; i++) {
        id arg = arguments[i];
        NSInteger argIndex = (NSInteger)(i + 2);
        [invocation setArgument:&arg atIndex:argIndex];
    }

    [invocation invoke];
    
    __unsafe_unretained id returnVal;
    [invocation getReturnValue:&returnVal];
    return returnVal;
}

主線很明顯, 就是創建了一個NSInvocation對象, 然后塞入參數, invoke, 注意這里invoke的target是自己, 返回的selector也是自己的, 往下一看就是一大堆1-15個參數的方法, 在這些方法中調用block, 傳遞參數, 所以, 我們也知道了reduceEach最多支持15個參數.
看一個3個參數的情況(參數沒有label, 因為畢竟不是給人看的)

- (SEL)selectorForArgumentCount:(NSUInteger)count {
    switch (count) {
        case 0: return NULL;
        case 1: return @selector(performWith:);
        case 2: return @selector(performWith::);
        case 3: return @selector(performWith:::);
    .....
}
- (id)performWith:(id)obj1 :(id)obj2 :(id)obj3 {
    id (^block)(id, id, id) = self.block;
    return block(obj1, obj2, obj3);
}

rac_sendAsynchronousRequest

源碼很簡單, 主要圍繞這個話題拓展開來, 說一下和網絡請求相關的注意事項, 牽涉到Signal的內容:

+ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request {
    NSCParameterAssert(request != nil);

    return [[RACSignal
        createSignal:^(id<RACSubscriber> subscriber) {
            NSOperationQueue *queue = [[NSOperationQueue alloc] init];
            queue.name = @"org.reactivecocoa.ReactiveCocoa.NSURLConnectionRACSupport";

            [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
                if (response == nil || data == nil) {
                    [subscriber sendError:error];
                } else {
                    [subscriber sendNext:RACTuplePack(response, data)];
                    [subscriber sendCompleted];
                }
            }];

            return [RACDisposable disposableWithBlock:^{
                queue.suspended = YES;
                [queue cancelAllOperations];
            }];
        }]
        setNameWithFormat:@"+rac_sendAsynchronousRequest: %@", request];
}

很清晰的主線, 創建一個Signal, 里面在新建隊列中發請求, 請求完成回調中根據data或error來sendNext+sendCompleted 或者 sendError.

RACSignal創建出來是冷信號(關于冷熱信號區別和冷信號的特征請看第三篇框架總覽. 冷信號在每次訂閱時都會觸發一次Signal的block, 那么這就有問題, 如果有多個人訂閱這個信號, 那豈不是每次都會重新發一次請求, 這明顯不合理. 所以, 在上面的例子中, 我們會用publish來轉換, publish轉換為RACMulticastConnection之后, 會把所有的訂閱都轉向另一個目標--RACSubject, 我們知道RACSubject是熱信號, 它的代碼只會被執行一次, 因此RACSubject會負責訂閱最初始的NSURLConnection的Signal. 結合代碼來看的話是這樣的:

// RACSignal+Operations.m
- (RACMulticastConnection *)publish {
    RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];
    RACMulticastConnection *connection = [self multicast:subject];
    return connection;
}

- (RACMulticastConnection *)multicast:(RACSubject *)subject {
    [subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];
    RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];
    return connection;
}

只是創建了一個RACMulticastConnection對象返回而已, 注意這里publish和multicast的區別是一個publish默認是RACSubject, multicast可以傳replaySubject, 以后再看這些區別, 繼續看:

// RACMulticastConnection.m
- (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject {
    NSCParameterAssert(source != nil);
    NSCParameterAssert(subject != nil);

    self = [super init];
    if (self == nil) return nil;

    _sourceSignal = source;
    _serialDisposable = [[RACSerialDisposable alloc] init];
    _signal = subject;
    
    return self;
}

#pragma mark Connecting

- (RACDisposable *)connect {
    BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);

    if (shouldConnect) {
        self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];
    }

    return self.serialDisposable;
}

- (RACSignal *)autoconnect {
    __block volatile int32_t subscriberCount = 0;

    return [[RACSignal
        createSignal:^(id<RACSubscriber> subscriber) {
            OSAtomicIncrement32Barrier(&subscriberCount);

            RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];
            RACDisposable *connectionDisposable = [self connect];

            return [RACDisposable disposableWithBlock:^{
                [subscriptionDisposable dispose];

                if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {
                    [connectionDisposable dispose];
                }
            }];
        }]
        setNameWithFormat:@"[%@] -autoconnect", self.signal.name];
}

這是RACMulticastConnection所有的實現代碼, connect和autoconnect的區別是, 一個是立即觸發連接, 一個則是返回信號, 被訂閱后則觸發連接. 需要注意的是, 最終的connect只能執行一次, autoconnect會維護一個計數器, 在計數器歸0時會dispose掉connection.

核心的代碼是self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];, 我們繼續跟進去看(有多個subscribe:方法, 目前我們的例子需要關注的是RACDynamicSignal, 因為NSURLConnection真正返回的是這個對象):

// RACDynamicSignal.m
- (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;
}

RACDisposable *innerDisposable = self.didSubscribe(subscriber);這行代碼觸發了NSURLConnection的連接, 所以到這里, 我們弄清楚了RACMulticastConnection最終訂閱了NSURLConnection的Signal.

現在進入下半部分, 多個subscriber到底訂閱了誰呢? 回到autoconnect的代碼中:

RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];

結合上下文我們知道, self.signal是publish中傳進來的RACSubject, 所以這個時候要我們去RACSubject里面看看:

// RACSubject.m
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
    NSCParameterAssert(subscriber != nil);

    RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
    subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];

    NSMutableArray *subscribers = self.subscribers;
    @synchronized (subscribers) {
        [subscribers addObject:subscriber];
    }
    
    return [RACDisposable disposableWithBlock:^{
        @synchronized (subscribers) {
            NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) {
                return obj == subscriber;
            }];

            if (index != NSNotFound) [subscribers removeObjectAtIndex:index];
        }
    }];
}

維護了一個self.subscribers, 里面存了RACPassthroughSubscriber, 其實passthroughSubscriber可以不用管, 所有的subscriber和Signal的event以及Disposables都是通過它的, 為了減少復雜度, 我們可以任務self.subscribers里面添加的就是我們的subscriber, , 然后再看看下面的一系列方法:

- (void)enumerateSubscribersUsingBlock:(void (^)(id<RACSubscriber> subscriber))block {
    NSArray *subscribers;
    @synchronized (self.subscribers) {
        subscribers = [self.subscribers copy];
    }

    for (id<RACSubscriber> subscriber in subscribers) {
        block(subscriber);
    }
}

- (void)sendNext:(id)value {
    [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) {
        [subscriber sendNext:value];
    }];
}
...

所以事實就很清楚了, 每次sendXXX的時候, 都會遍歷一遍, 每個都send一次, 保證每個Subscriber(要在send之前subscribe)都能收到.

下面是我demo里的代碼:

RACSignal *signal = [self fetchInfos];
RAC(self, datas) = [[[signal doCompleted:^{
    @strongify(self);
    [self.tableView reloadData];
}] logError]catchTo:[RACSignal empty]];

RAC(self, data2) = [[[signal doCompleted:^{
                             NSLog(@"second");
                             }]
                             logError]
                             catchTo:[RACSignal empty]];

data2只是為了演示的確是只發了一次請求用的, 沒有特別的含義. 如果沒有這種監聽多個網絡請求的情況, 可以直接RAC(...) = RACObserve(...), 省去中間的singla命名.

里面東西確實有點多, 因為還沒有對Signal進行分析, 這里只是拋出網絡請求這個常用且一般用法會出錯的情況進行討論. 個人感覺講的還不足夠清晰, 有時間我會再整理一把, 爭取把整個流程都梳理清晰.

delegateProxy

-rac_signalForSelector:fromProtocol:是對NSObject的拓展, 所以任何對象都可以使用, 我們先直接看看代碼:

// NSObject+RACSelectorSignal.m
- (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol {
    NSCParameterAssert(selector != NULL);
    NSCParameterAssert(protocol != NULL);

    return NSObjectRACSignalForSelector(self, selector, protocol);
}

調用了一個C函數來返回, 整個C函數有好幾十行, 直接注釋在源碼里面看好了:

static RACSignal *NSObjectRACSignalForSelector(NSObject *self, SEL selector, Protocol *protocol) {
    // 為了避免重名, 所以給方法加前綴
    SEL aliasSelector = RACAliasForSelector(selector);

    // 保證線程安全
    @synchronized (self) {

        // 如果已經建立了subject 就直接返回 讓調用者訂閱即可
        RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);
        if (subject != nil) return subject;

        // 獲取類名 里面的邏輯很復雜 replace了forwardInvocation和responseToSelector等等方法 有興趣的可以深入探究一下
        Class class = RACSwizzleClass(self);
        NSCAssert(class != nil, @"Could not swizzle class of %@", self);

        // 新建subject 并與對象關聯
        subject = [[RACSubject subject] setNameWithFormat:@"%@ -rac_signalForSelector: %s", self.rac_description, sel_getName(selector)];
        objc_setAssociatedObject(self, aliasSelector, subject, OBJC_ASSOCIATION_RETAIN);

        // 對象被釋放時發送completed
        [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
            [subject sendCompleted];
        }]];

        // 獲取目標方法
        Method targetMethod = class_getInstanceMethod(class, selector);
        // 如果目標方法未實現
        if (targetMethod == NULL) {
            // 先獲取typeEncoding 后面動態添加方法時需要
            const char *typeEncoding;
            if (protocol == NULL) {
                typeEncoding = RACSignatureForUndefinedSelector(selector);
            } else {
                // 獲取一下方法的描述 也是后面新增method
                struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
                if (methodDescription.name == NULL) {
                    methodDescription = protocol_getMethodDescription(protocol, selector, YES, YES);
                    NSCAssert(methodDescription.name != NULL, @"Selector %@ does not exist in <%s>", NSStringFromSelector(selector), protocol_getName(protocol));
                }

                typeEncoding = methodDescription.types;
            }

            RACCheckTypeEncoding(typeEncoding);

            // 動態添加一個方法
            if (!class_addMethod(class, selector, _objc_msgForward, typeEncoding)) {
                // 添加失敗是因為已經有一個這樣名字的方法了
                NSDictionary *userInfo = @{
                    NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"A race condition occurred implementing %@ on class %@", nil), NSStringFromSelector(selector), class],
                    NSLocalizedRecoverySuggestionErrorKey: NSLocalizedString(@"Invoke -rac_signalForSelector: again to override the implementation.", nil)
                };

                return [RACSignal error:[NSError errorWithDomain:RACSelectorSignalErrorDomain code:RACSelectorSignalErrorMethodSwizzlingRace userInfo:userInfo]];
            }
            // 目標方法不等于runtime轉發方法
        } else if (method_getImplementation(targetMethod) != _objc_msgForward) {
            // 已有實現了 用已有的實現來取一個別名 事實證明 注釋掉這段代碼也沒什么問題 所以沒想明白這里addMethod的意義在哪里 求解釋~~
            const char *typeEncoding = method_getTypeEncoding(targetMethod);

            RACCheckTypeEncoding(typeEncoding);

            BOOL addedAlias __attribute__((unused)) = class_addMethod(class, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
            NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), class);

            // 因為已經有了實現了, 所以用runtime轉發來替換掉原來的實現 這樣就會轉發到別名的方法上面去了
            class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));
        }

        return subject;
    }
}

里面還有一些細節沒有講解, 因為是涉及到runtime的東西, 和主題不太掛鉤, 所以也就沒細講了, 感興趣的可以去看看, 還是能學到不少runtime的知識的.

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容

  • 1.ReactiveCocoa簡介 ReactiveCocoa(簡稱為RAC),是由Github開源的一個應用于i...
    清蘂翅膀的技術閱讀 1,998評論 0 1
  • RAC使用測試Demo下載:github.com/FuWees/WPRACTestDemo 1.ReactiveC...
    FuWees閱讀 6,430評論 3 10
  • 1.ReactiveCocoa常見操作方法介紹。 1.1 ReactiveCocoa操作須知 所有的信號(RACS...
    萌芽的冬天閱讀 1,032評論 0 5
  • 前言由于時間的問題,暫且只更新這么多了,后續還會持續更新本文《最快讓你上手ReactiveCocoa之進階篇》,目...
    Karos_凱閱讀 1,749評論 0 6
  • 我們每個人都有一個心理期,就是每隔一段時間,心情就會很低落,煩躁,很難控制,這樣我們就會把這些負面情緒傳染給我們身...
    OU雪影閱讀 219評論 0 0