近期开发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 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)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 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将参数发出去。
只是我的一些理解,细节地方也不太明白为什么那样去做,代码也只看了冰山一角,以后会继续更新。
参考文章
- ReactiveCocoa Tutorial – The Definitive Introduction 比较基础的介绍文档,有翻译版
- ReactiveCocoa Tutorial – The Definitive Introduction: Part 1/2
- ReactiveCocoa Tutorial – The Definitive Introduction: Part 2/2
- 翻译ReactiveCocoa入门教程:第一部分
- 翻译ReactiveCocoa入门教程--第二部分
- sunnyxx的技术博客 Reactive Cocoa Tutorial 对编程思想、宏的使用、底层设计讲的比较深入
- Reactive Cocoa Tutorial 0 = Overview
- Reactive Cocoa Tutorial 1 = 神奇的Macros
- Reactive Cocoa Tutorial 2 = 百变RACStream
- Reactive Cocoa Tutorial 3 = RACSignal的巧克力工厂
- Reactive Cocoa Tutorial 4 = 只取所需的Filters
- 美团点评团队博客 对冷信号与热信号的解读、实用案例
- RACSignal的Subscription深入分析
- 细说ReactiveCocoa的冷信号与热信号(一)
- 细说ReactiveCocoa的冷信号与热信号(二):为什么要区分冷热信号
- 细说ReactiveCocoa的冷信号与热信号(三):怎么处理冷信号与热信号
- ReactiveCocoa中潜在的内存泄漏及解决方案
- ReactiveCocoa核心元素与信号流
- ReactiveCocoa 讨论会 业界使用者的评价和讨论总结