ReactiveCocoa 学习笔记

近期开发RN项目对函数式编程有了一点点的理解,回过头看native的函数式编程框架RAC,有点茅塞顿开的感觉,在此记录下RAC源码阅读心得。

对于函数式编程,我个人感觉最大的好处就是代码很紧凑,结构简洁而清晰。到oc语言里就是“block式”编程,举个例子,在处理输入框的时候比如UITextField,使用UIKit原生的API需要遵循它的协议,实现代理方法监听输入变化;如果一个页面有多个输入框,甚至是一个输入框的动态array,这个时候我们会想到如果回调是一个block,在创建输入框的时候就写好了回调block,不仅代码很集中,也不必做跨函数的事情区分回调来源。

引用sunnyReactive Cocoa Tutorial [0] = Overview中的一段比喻,原来的编程思想好比走迷宫,走出迷宫需要在不同时间段记住不同状态根据不同情况而做出一系列反应,继而走出迷宫;RAC的思想好比是建迷宫,在创建时就建立好联系,一个事件来了会引发哪些响应链,都按照创建好的路线传导,像一个精密的仪器,复杂但完整而自然。
最可见的变化就是代码量大量减少,没有多余的状态变量,逻辑更清晰。


RACSignal

RAC是github开源的框架,有专门社区维护,代码的水平还是很高的。对我这种初学者说收货很大。。里面有很多关于宏、block、runtime的高级用法,一些之前掌握不深的知识点都有新的理解。
首先要看的肯定是RACSignal类了,对它的使用无非就是创建signal,订阅(next、complete、error):

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
    RACDynamicSignal *signal = [[self alloc] init];
    //这里将didSubscribe block保存在信号里面,所以使用时要注意避免循环引用
    signal->_didSubscribe = [didSubscribe copy];
    return [signal setNameWithFormat:@"+createSignal:"];
}

创建就是将传入的block保存起来,看这个block的名字也就知道,didSubscribe 即订阅了信号才会执行:

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

这里创建了一个RACSubscriber对象o,并执行了[self subscribe:o]。
RACSubscriber对象保存了next、error、completed三个block:

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

下一步执行[self subscribe:o]并返回一个RACDisposable对象。这里稍微复杂一些,但从上面分析来看,这一步要做的是执行didSubscribe,并建立关联在sendNext、sendError、sendCompleted时执行订阅者对应的block。

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

好吧,一行一行看

已经了解了RACSignal和RACSubscriber,还需要知道这个RACDisposable是干嘛的。

A disposable encapsulates the work necessary to tear down and cleanup a subscription.
disposable封装了撤销、清除一个订阅所必须的一些工作。

添加一个订阅要支持可以随时取消订阅,并在取消或完成时做好清理工作,看来disposable是干这些事情的。再回到[self subscribe:o]:

1.首先创建了一个RACCompoundDisposable,它是RACDisposable的一个子类,来看一段官方绕口令:

A disposable of disposables. When it is disposed, it disposes of all its contained disposables.

一个混合的disposable,简单讲就是一个disposable的数组(内部会有一些优化,可以参照RACCompoundDisposable.m),当CompoundDisposable dispose时会dispose它包含的所有disposables。

2.创建一个subscriber,这一行看起来有点诡异:上一步创建了一个RACSubscriber对象o,现在用o再创建一个RACPassthroughSubscriber赋给o,RACPassthroughSubscriber实现了RACSubscriber协议,并且signal、subcriber、disposable一应俱全,看起来这个类会维系好三者之间的关系。实际上看并没有什么管理三者关系的代码存在,仅有一些DTrace的东西,和Instruments有关。如果把这一行注掉,会发现并没有什么影响。

3.接下来就是关键的部分了,执行didSubscribe block。这里用了一个subscriptionScheduler,是专门用来执行subscription任务的,返回一个disposable是为了能够在任务没执行前能控制取消掉它,也对应了disposable这个名字的含义。scheduler内部是gcd实现的,disposable相当于一个外部的变量控制是否执行这个任务:

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

    RACDisposable *disposable = [[RACDisposable alloc] init];

    dispatch_async(self.queue, ^{
        if (disposable.disposed) return;
        [self performAsCurrentScheduler:block];
    });

    return disposable;
}

这样看下来,忽略一些实现细节,其实这些类大概都是做了两件事:1.把block存起来;2.在合适的时机执行它。

用一张图简单梳理下从创建signal、订阅和发布接收的关系:

RAC源码里其实有很多值得研究和学习的地方,对宏、runtime、block的使用等。比如我们在写RAC宏的时候

RAC(self.submitBtn, enabled) = RACObserve(self.submitBtnModel, enabled);

在写完逗号敲property时会发现编译器给出了正确的代码提示,很神奇。具体可以看下这篇博客Reactive Cocoa Tutorial [1] = 神奇的Macros


NSObject (RACSelectorSignal)

RAC相当于统一规范了异步事件的处理。那么如何将一个异步事件的处理封装成RACSignal的形式呢。

  • 如果是block回调的API,相对比较简单,只需要创建signal并在回调block中sendNext、sendComplete、sendError即可。
  • 如果是target-action,比如UIControl的事件,则只需要将target设置成相应的subscriber,比如UIControl (RACSignalSupport)分类中就只有这一个方法:
@implementation UIControl (RACSignalSupport)

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

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

            [self addTarget:subscriber action:@selector(sendNext:) forControlEvents:controlEvents];

            RACDisposable *disposable = [RACDisposable disposableWithBlock:^{
                [subscriber sendCompleted];
            }];
            [self.rac_deallocDisposable addDisposable:disposable];

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

@end
  • 稍微复杂一点的是delegate模式,不巧的是UIKit很多控件都是用的代理模式实现。把一个代理模式的API封装成signal,对外不需要实现协议只使用signal - block,那就需要内部自己管理一个代理。RAC为这种场景写了一个类RACDelegateProxy:
// A private delegate object suitable for using
// -rac_signalForSelector:fromProtocol: upon.
@interface RACDelegateProxy : NSObject

// The delegate to which messages should be forwarded if not handled by
// any -signalForSelector: applications.
@property (nonatomic, unsafe_unretained) id rac_proxiedDelegate;

// Creates a delegate proxy capable of responding to selectors from `protocol`.
- (instancetype)initWithProtocol:(Protocol *)protocol;

// Calls -rac_signalForSelector:fromProtocol: using the `protocol` specified
// during initialization.
- (RACSignal *)signalForSelector:(SEL)selector;

@end

从注释看,它的使用很简单,初始化方法传入要代理的协议Protocol ; rac_proxiedDelegate是原代理,signalForSelector用于生成对应协议方法的signal。
所以RACDelegateProxy的关键在signalForSelector的实现:

- (RACSignal *)signalForSelector:(SEL)selector {
    return [self rac_signalForSelector:selector fromProtocol:_protocol];
}

这里的 rac_signalForSelector : fromProtocol 在NSObject (RACSelectorSignal)分类中:

    - (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol {
        NSCParameterAssert(selector != NULL);
        NSCParameterAssert(protocol != NULL);
    
        return NSObjectRACSignalForSelector(self, selector, protocol);
    }
    
    - (RACSignal *)rac_signalForSelector:(SEL)selector {
        NSCParameterAssert(selector != NULL);
    
        return NSObjectRACSignalForSelector(self, selector, NULL);
    }

有协议和无协议的都会调用NSObjectRACSignalForSelector,这是这个分类的核心方法,它里面包含的代码比较长,我分解了几块来看,首先是RACSwizzleClass:
从它的命名猜测,应该是swizzle了类里面的一些方法,返回一个Class
(这部分源码略蛋疼,我后面画有一张图)

static Class RACSwizzleClass(NSObject *self) {
    Class statedClass = self.class;
    Class baseClass = object_getClass(self);

    // The "known dynamic subclass" is the subclass generated by RAC.
    // It's stored as an associated object on every instance that's already
    // been swizzled, so that even if something else swizzles the class of
    // this instance, we can still access the RAC generated subclass.
    Class knownDynamicSubclass = objc_getAssociatedObject(self, RACSubclassAssociationKey);
    if (knownDynamicSubclass != Nil) return knownDynamicSubclass;

    NSString *className = NSStringFromClass(baseClass);

    if (statedClass != baseClass) {
        // If the class is already lying about what it is, it's probably a KVO
        // dynamic subclass or something else that we shouldn't subclass
        // ourselves.
        //
        // Just swizzle -forwardInvocation: in-place. Since the object's class
        // was almost certainly dynamically changed, we shouldn't see another of
        // these classes in the hierarchy.
        //
        // Additionally, swizzle -respondsToSelector: because the default
        // implementation may be ignorant of methods added to this class.
        @synchronized (swizzledClasses()) {
            if (![swizzledClasses() containsObject:className]) {
                RACSwizzleForwardInvocation(baseClass);
                RACSwizzleRespondsToSelector(baseClass);
                RACSwizzleGetClass(baseClass, statedClass);
                RACSwizzleGetClass(object_getClass(baseClass), statedClass);
                RACSwizzleMethodSignatureForSelector(baseClass);
                [swizzledClasses() addObject:className];
            }
        }

        return baseClass;
    }

    const char *subclassName = [className stringByAppendingString:RACSubclassSuffix].UTF8String;
    Class subclass = objc_getClass(subclassName);

    if (subclass == nil) {
        subclass = objc_allocateClassPair(baseClass, subclassName, 0);
        if (subclass == nil) return nil;

        RACSwizzleForwardInvocation(subclass);
        RACSwizzleRespondsToSelector(subclass);

        RACSwizzleGetClass(subclass, statedClass);
        RACSwizzleGetClass(object_getClass(subclass), statedClass);

        RACSwizzleMethodSignatureForSelector(subclass);

        objc_registerClassPair(subclass);
    }

    object_setClass(self, subclass);
    objc_setAssociatedObject(self, RACSubclassAssociationKey, subclass, OBJC_ASSOCIATION_ASSIGN);
    return subclass;
}

替换了这个类的forwardInvocation、respondsToSelector、class、methodSignatureForSelector这几个方法,就像它注释所说,这里的实现应该和KVO类似,KVO实现时会创建一个KVO前缀的类,如果这里还创建一个子类的话会影响到KVO的实现。但如果class没被修改的话为什么就要创建一个子类,我也没太想明白。。

这几个swizzle方法,关键在forwardInvocation,其他基本上是为它服务的:

static BOOL RACForwardInvocation(id self, NSInvocation *invocation) {
    //**取到aliasSelector,以及以它为key关联的subject对象
    SEL aliasSelector = RACAliasForSelector(invocation.selector);
    RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);

    //**如果有aliasSelector要执行(原始的selector,逻辑在下面NSObjectRACSignalForSelector方法)
    Class class = object_getClass(invocation.target);
    BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector];
    if (respondsToAlias) {
        invocation.selector = aliasSelector;
        [invocation invoke];
    }

    if (subject == nil) return respondsToAlias;
  
    //**sendNext将selector的参数发出去
    [subject sendNext:invocation.rac_argumentsTuple];
    return YES;
}

然后就到NSObjectRACSignalForSelector这个方法:

static RACSignal *NSObjectRACSignalForSelector(NSObject *self, SEL selector, Protocol *protocol) {
    //**RACAliasForSelector里面就一句话,获取一个别名为rac_alias_前缀的selector
    SEL aliasSelector = RACAliasForSelector(selector);

    @synchronized (self) {
        //**第一次进来是没有的,生成之后会关联到self上,往下看
        RACSubject *subject = objc_getAssociatedObject(self, aliasSelector);
        if (subject != nil) return subject;
        //**参见上面
        Class class = RACSwizzleClass(self);
        NSCAssert(class != nil, @"Could not swizzle class of %@", self);
        //**生成subject并关联到self上
        subject = [[RACSubject subject] setNameWithFormat:@"%@ -rac_signalForSelector: %s", RACDescription(self), sel_getName(selector)];
        objc_setAssociatedObject(self, aliasSelector, subject, OBJC_ASSOCIATION_RETAIN);

        [self.rac_deallocDisposable addDisposable:[RACDisposable disposableWithBlock:^{
            [subject sendCompleted];
        }]];
        
        Method targetMethod = class_getInstanceMethod(class, selector);
        //**如果没有这个方法
        if (targetMethod == NULL) {
            const char *typeEncoding;
            if (protocol == NULL) {
                typeEncoding = RACSignatureForUndefinedSelector(selector);
            } else {
                // Look for the selector as an optional instance method.
                struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);

                if (methodDescription.name == NULL) {
                    // Then fall back to looking for a required instance
                    // method.
                    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);
            // 添加这个方法
            // Define the selector to call -forwardInvocation:.
            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]];
            }
        } else if (method_getImplementation(targetMethod) != _objc_msgForward) {
             // 如果这个方法存在,创建一个带前缀的备份添加到class,这也是上面forwardInvocation要执行aliasSelector的原因
            // Make a method alias for the existing method implementation.
            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);
            // 让原方法走forwardInvocation,而forwardInvocation已经被我们swizzle过了
            // Redefine the selector to call -forwardInvocation:.
            class_replaceMethod(class, selector, _objc_msgForward, method_getTypeEncoding(targetMethod));
        }

        return subject;
    }
}

简单来说,思路就是首先创建一个关联对象subject(subject是信号的一种,热信号和冷信号不罗列了,参考文献有详细的解读),这个subject关联的key是我们要监听的selector(alias过的),让真正的selector走forwardInvocation转发,然后在forwardInvocation中执行aliasSelector(带前缀的备份,因为如果本来实现了这个方法的话还要给人家执行),执行subject的sendNext将参数发出去。
只是我的一些理解,细节地方也不太明白为什么那样去做,代码也只看了冰山一角,以后会继续更新。


参考文章

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容