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阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 1.ReactiveCocoa简介 ReactiveCocoa(简称为RAC),是由Github开源的一个应用于i...
    清蘂翅膀的技术阅读 1,973评论 0 1
  • RAC使用测试Demo下载:github.com/FuWees/WPRACTestDemo 1.ReactiveC...
    FuWees阅读 6,349评论 3 10
  • 1.ReactiveCocoa常见操作方法介绍。 1.1 ReactiveCocoa操作须知 所有的信号(RACS...
    萌芽的冬天阅读 1,013评论 0 5
  • 前言由于时间的问题,暂且只更新这么多了,后续还会持续更新本文《最快让你上手ReactiveCocoa之进阶篇》,目...
    Karos_凯阅读 1,724评论 0 6
  • 我们每个人都有一个心理期,就是每隔一段时间,心情就会很低落,烦躁,很难控制,这样我们就会把这些负面情绪传染给我们身...
    OU雪影阅读 206评论 0 0