ReactiveCocoa學(xué)習(xí)筆記二--回調(diào)統(tǒng)一(9.15更新)

9.15更新

補(bǔ)充關(guān)于NSURLConnection的category方法+rac_sendAsynchronousRequest源碼解析
補(bǔ)充關(guān)于rac_signalForSelector的源碼解析

9.14更新

補(bǔ)充了關(guān)于block回調(diào)的內(nèi)容
補(bǔ)充了關(guān)于delegate回調(diào)的內(nèi)容

例行廢話

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

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

異步機(jī)制

記得在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的一個(gè)優(yōu)勢就是提供了一個(gè)單一的機(jī)制(就是信號處理)來聯(lián)合目前所有處理異步行為, 包括代理,回調(diào)block,target-action機(jī)制,通知和KVO.因?yàn)楦鞣N回調(diào)模式使用上并沒有說一個(gè)完全統(tǒng)一的規(guī)范, 沒有人能夠確定地說在某種情況下使用A模式就一定比B模式好, 因此我們?nèi)绻陧?xiàng)目中使用RAC, 不放從這里入手, 慢慢把新增代碼改用RAC的形式來寫.

下面的篇幅我打算用一個(gè)模式一個(gè)小節(jié)來看.

Notification

先看看頭文件, 只有一個(gè)方法:

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

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

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

從打印的信息來看, 和我們自己寫一個(gè)target一個(gè)selector的傳統(tǒng)形式并沒有太大差別, 所以, 我們?nèi)タ纯磳?shí)現(xiàn):

// 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個(gè)宏可以在代碼查看預(yù)處理之后的代碼:

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

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

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

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

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

KVO

KVO是出名難用的一個(gè)模式, 但是有些情況確不得不用, 另外給個(gè)小tip, 據(jù)我個(gè)人的實(shí)際使用經(jīng)驗(yàn), 觀察者與被觀察者任意一個(gè)被析構(gòu)掉, KVO如果還未解除都會(huì)發(fā)生crash, 這種情況會(huì)在memoryWarning的時(shí)候發(fā)生, 所以用到的地方多測試一下, 另外, KVOController是解決這個(gè)問題的好幫手, 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來處理了, 并且返回了一個(gè)Disposable, 意味著我們能隨時(shí)干掉這個(gè)監(jiān)聽. 為了輔助完成KVO, 新建一個(gè)測試類, 里面含有一個(gè)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的修改, 后面因?yàn)閐ispose了, 所以我們不能再繼續(xù)收到后續(xù)的KVO了. 另外observer參數(shù)傳nil也是可以的.
我們來看看實(shí)現(xiàn)吧, 代碼有點(diǎn)長, 且分了好幾個(gè)類來實(shí)現(xiàn)的, 我們就按步驟來看吧:

第一步: addObserver

我們在執(zhí)行了上面的那一段代碼之后, 內(nèi)部實(shí)現(xiàn)會(huì)創(chuàng)建RACKVOTrampoline這個(gè)類的示例來實(shí)際處理我們的監(jiān)聽, 而這個(gè)類又會(huì)把監(jiān)聽關(guān)系放在RACKVOProxy進(jìn)行管理, 先來看看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是一個(gè)NSMapTable, 之所以用NSMapTable而不是Map, 主要是因?yàn)樗鼘ey和value是弱引用. add和remove都是同步操作的. 所謂的context其實(shí)就是一個(gè)RACKVOTrampoline的實(shí)例, 所以在proxy這里, 一個(gè)實(shí)例對應(yīng)一個(gè)監(jiān)聽. 我們我們基本可以認(rèn)定, proxy會(huì)有:

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

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

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

所以, 當(dāng)name的值有變的時(shí)候, proxy會(huì)首先監(jiān)聽到:

// 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];
    }
}

然后調(diào)用RACKVOTrampoline的監(jiān)聽實(shí)現(xiàn):

// 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);
}

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

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

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

target-action

這個(gè)異步機(jī)制在目前來說用的最多的應(yīng)該就是UIButton加action的地方了吧, 因?yàn)檫@個(gè)方法繼承自UIControl, 所以我們看看對UIControl的拓展:

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

只有一個(gè)方法, 有前面的經(jīng)驗(yàn)我們基本上就知道怎么使用了:

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

內(nèi)部實(shí)現(xiàn)則是直接創(chuàng)建一個(gè)信號, 以subcriber為target, 然后設(shè)置selector為sendNext, 所以每次btn被點(diǎn)擊都會(huì)調(diào)用[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];
}

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

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

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

block

從嚴(yán)格的角度上來說, block并沒有被替換掉, 畢竟Signal subscript的回調(diào)也還是block, 不過從概念上來說block回調(diào)和Signal subscript還是有本質(zhì)區(qū)別的.
既然官方文檔已經(jīng)提了, 那就講一個(gè)經(jīng)典常用的例子吧:
在NSURLConnection中有一個(gè)
+ (void)sendAsynchronousRequest:(NSURLRequest*) request queue:(NSOperationQueue*) queue completionHandler:(void (^)(NSURLResponse* __nullable response, NSData* __nullable data, NSError* __nullable connectionError)) handler
方法, 通過block來回調(diào)請求的response或者error, 在RAC的世界中, NSURLConnection也被拓展了:
+ (RACSignal *)rac_sendAsynchronousRequest:(NSURLRequest *)request
接收一個(gè)request, 返回一個(gè)Signal, 剩下的事情就是對這個(gè)信號的操作了, map, reduceEach等等都可以網(wǎng)上加了, 這里給出一個(gè)我在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];
}

那個(gè)URL是我隨便構(gòu)造的, 然后mock了HTTP請求, 返回我自己要的數(shù)據(jù).

reduceEach會(huì)遍歷所有的元素, 然后替換掉原本的, 本質(zhì)上也是調(diào)用的map, 只是map只接收一個(gè)參數(shù), 所以response和data會(huì)被包裝為RACTuple, 要一個(gè)個(gè)取出來, 用reduce可以接收多個(gè)參數(shù), 所以用reduceEach更加方便和清晰. 對reduceEach源碼感興趣的同學(xué)最后附錄會(huì)有解析. 在reduceEach里我們?nèi)绻麤]有額外的需求直接返回我們感興趣的data即可.

deliverOne到了mainThreadScheduler是因?yàn)榻酉聛砜赡芤秩綰I了, 要切換回主線程;

我構(gòu)造的數(shù)據(jù)中, data反序列化之后其實(shí)是一個(gè)map, 里面有一個(gè)list, list里面又是map, 裝著我要初始化Model的信息.

至于publish和autoconnect則是和信號有關(guān), 這里只提一下前者是把Signal變?yōu)閙ulticastConnection用的, 后者是在有人subscribe的情況下才進(jìn)行連接, 也就是激活signal.

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

源碼部分今天暫時(shí)不分析了, 后續(xù)更新在附錄上.

Delegate

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

  1. 要先使用Signal來替換delegate先要#import "RACDelegateProxy.h", 它不包含在默認(rèn)的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;

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

結(jié)語

這章涉及到了Signal, 但是因?yàn)閟ignal的話題太大, 必須新開一章節(jié)來學(xué)習(xí), 所以就先不講了.

附錄

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做為參數(shù), 再接著看里面實(shí)現(xiàn):

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

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

只是返回一個(gè)實(shí)例, 繼續(xù)看:

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

主線很明顯, 就是創(chuàng)建了一個(gè)NSInvocation對象, 然后塞入?yún)?shù), invoke, 注意這里invoke的target是自己, 返回的selector也是自己的, 往下一看就是一大堆1-15個(gè)參數(shù)的方法, 在這些方法中調(diào)用block, 傳遞參數(shù), 所以, 我們也知道了reduceEach最多支持15個(gè)參數(shù).
看一個(gè)3個(gè)參數(shù)的情況(參數(shù)沒有l(wèi)abel, 因?yàn)楫吘共皇墙o人看的)

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

源碼很簡單, 主要圍繞這個(gè)話題拓展開來, 說一下和網(wǎng)絡(luò)請求相關(guān)的注意事項(xiàng), 牽涉到Signal的內(nèi)容:

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

很清晰的主線, 創(chuàng)建一個(gè)Signal, 里面在新建隊(duì)列中發(fā)請求, 請求完成回調(diào)中根據(jù)data或error來sendNext+sendCompleted 或者 sendError.

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

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

只是創(chuàng)建了一個(gè)RACMulticastConnection對象返回而已, 注意這里publish和multicast的區(qū)別是一個(gè)publish默認(rèn)是RACSubject, multicast可以傳replaySubject, 以后再看這些區(qū)別, 繼續(xù)看:

// 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所有的實(shí)現(xiàn)代碼, connect和autoconnect的區(qū)別是, 一個(gè)是立即觸發(fā)連接, 一個(gè)則是返回信號, 被訂閱后則觸發(fā)連接. 需要注意的是, 最終的connect只能執(zhí)行一次, autoconnect會(huì)維護(hù)一個(gè)計(jì)數(shù)器, 在計(jì)數(shù)器歸0時(shí)會(huì)dispose掉connection.

核心的代碼是self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];, 我們繼續(xù)跟進(jìn)去看(有多個(gè)subscribe:方法, 目前我們的例子需要關(guān)注的是RACDynamicSignal, 因?yàn)镹SURLConnection真正返回的是這個(gè)對象):

// 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);這行代碼觸發(fā)了NSURLConnection的連接, 所以到這里, 我們弄清楚了RACMulticastConnection最終訂閱了NSURLConnection的Signal.

現(xiàn)在進(jìn)入下半部分, 多個(gè)subscriber到底訂閱了誰呢? 回到autoconnect的代碼中:

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

結(jié)合上下文我們知道, self.signal是publish中傳進(jìn)來的RACSubject, 所以這個(gè)時(shí)候要我們?nèi)ACSubject里面看看:

// 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];
        }
    }];
}

維護(hù)了一個(gè)self.subscribers, 里面存了RACPassthroughSubscriber, 其實(shí)passthroughSubscriber可以不用管, 所有的subscriber和Signal的event以及Disposables都是通過它的, 為了減少復(fù)雜度, 我們可以任務(wù)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];
    }];
}
...

所以事實(shí)就很清楚了, 每次sendXXX的時(shí)候, 都會(huì)遍歷一遍, 每個(gè)都send一次, 保證每個(gè)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只是為了演示的確是只發(fā)了一次請求用的, 沒有特別的含義. 如果沒有這種監(jiān)聽多個(gè)網(wǎng)絡(luò)請求的情況, 可以直接RAC(...) = RACObserve(...), 省去中間的singla命名.

里面東西確實(shí)有點(diǎn)多, 因?yàn)檫€沒有對Signal進(jìn)行分析, 這里只是拋出網(wǎng)絡(luò)請求這個(gè)常用且一般用法會(huì)出錯(cuò)的情況進(jìn)行討論. 個(gè)人感覺講的還不足夠清晰, 有時(shí)間我會(huì)再整理一把, 爭取把整個(gè)流程都梳理清晰.

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

調(diào)用了一個(gè)C函數(shù)來返回, 整個(gè)C函數(shù)有好幾十行, 直接注釋在源碼里面看好了:

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

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

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

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

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

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

        // 獲取目標(biāo)方法
        Method targetMethod = class_getInstanceMethod(class, selector);
        // 如果目標(biāo)方法未實(shí)現(xiàn)
        if (targetMethod == NULL) {
            // 先獲取typeEncoding 后面動(dòng)態(tài)添加方法時(shí)需要
            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);

            // 動(dòng)態(tài)添加一個(gè)方法
            if (!class_addMethod(class, selector, _objc_msgForward, typeEncoding)) {
                // 添加失敗是因?yàn)橐呀?jīng)有一個(gè)這樣名字的方法了
                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]];
            }
            // 目標(biāo)方法不等于runtime轉(zhuǎn)發(fā)方法
        } else if (method_getImplementation(targetMethod) != _objc_msgForward) {
            // 已有實(shí)現(xiàn)了 用已有的實(shí)現(xiàn)來取一個(gè)別名 事實(shí)證明 注釋掉這段代碼也沒什么問題 所以沒想明白這里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);

            // 因?yàn)橐呀?jīng)有了實(shí)現(xiàn)了, 所以用runtime轉(zhuǎn)發(fā)來替換掉原來的實(shí)現(xiàn) 這樣就會(huì)轉(zhuǎn)發(fā)到別名的方法上面去了
            class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));
        }

        return subject;
    }
}

里面還有一些細(xì)節(jié)沒有講解, 因?yàn)槭巧婕暗絩untime的東西, 和主題不太掛鉤, 所以也就沒細(xì)講了, 感興趣的可以去看看, 還是能學(xué)到不少runtime的知識的.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子颁糟,更是在濱河造成了極大的恐慌摘能,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芍阎,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)矛渴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來惫搏,“玉大人具温,你說我怎么就攤上這事皂岔⊥峤瘢” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評論 0 354
  • 文/不壞的土叔 我叫張陵喊熟,是天一觀的道長茴丰。 經(jīng)常有香客問我达皿,道長天吓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,562評論 1 293
  • 正文 為了忘掉前任峦椰,我火速辦了婚禮失仁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘们何。我一直安慰自己萄焦,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評論 6 392
  • 文/花漫 我一把揭開白布冤竹。 她就那樣靜靜地躺著拂封,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鹦蠕。 梳的紋絲不亂的頭發(fā)上冒签,一...
    開封第一講書人閱讀 51,482評論 1 302
  • 那天,我揣著相機(jī)與錄音钟病,去河邊找鬼萧恕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肠阱,可吹牛的內(nèi)容都是我干的票唆。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼屹徘,長吁一口氣:“原來是場噩夢啊……” “哼走趋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起噪伊,我...
    開封第一講書人閱讀 39,166評論 0 276
  • 序言:老撾萬榮一對情侶失蹤簿煌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后鉴吹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體姨伟,經(jīng)...
    沈念sama閱讀 45,608評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評論 3 336
  • 正文 我和宋清朗相戀三年豆励,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了夺荒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡肆糕,死狀恐怖般堆,靈堂內(nèi)的尸體忽然破棺而出在孝,到底是詐尸還是另有隱情诚啃,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評論 5 346
  • 正文 年R本政府宣布私沮,位于F島的核電站始赎,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜造垛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評論 3 329
  • 文/蒙蒙 一魔招、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧五辽,春花似錦办斑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至罪郊,卻和暖如春蠕蚜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背悔橄。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評論 1 269
  • 我被黑心中介騙來泰國打工靶累, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人癣疟。 一個(gè)月前我還...
    沈念sama閱讀 48,063評論 3 370
  • 正文 我出身青樓挣柬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親睛挚。 傳聞我的和親對象是個(gè)殘疾皇子凛忿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評論 2 354

推薦閱讀更多精彩內(nèi)容