ReactiveCocoa 解析
ReactiveCocoa 解决了什么问题ReactiveCocoa 是一个 iOS 中的函数式响应式编程框架,它改变了我们在使用 Cocoa 时的思维和方式。 它将苹果的 API 进行了一次封装改造,使其可以使用响应式进行编程。 函数式编程 (Functional Programming) [FP]
在 iOS 中,开发者一般使用的开发范例都是面向对象开发,而面向对象是一种命令式编程 (Imperative Programming)。它与函数式编程是两种开发思维,其实目的都是一样的,让计算机计算出来我们需要的结果。
总的来说,函数式编程就是一个表达式,y = x + 1, 就像数学中的函数一样。 比如需要求 1 到 n 的和,用 Swift 来写,在命令式编程中,会写成这样: func addTo(_ number: Int) -> Int {
var sum: Int = 0
for i in 1...number {
sum += i
}
return sum
}
addTo(10) // 55
addTo() 是有状态的,sum 为一个执行状态,在执行结束的时候程序达到了最终状态,目标达到,结束运行。 而在函数式编程中,代码会是这样: func addUpTo(_ number: Int) -> Int {
if number >= 1 {
return number + addUpTo(number-1)
}
return number
}
addUpTo(10) // 55
函数式编程中不使用循环,想要循环只能使用递归。在 addUpTo() 中是不保存状态的,换个方式,可以说,它是使用了函数来保存状态的。 在函数式编程中,如果仅此而已,那么其表达能力是非常弱的,函数式编程有两个本质:
当然函数式编程不可能就真的不执行 I/O,但它通过一些手段来把I/O的影响限制到最小,比如通过Continuations,Monad等技术。 Moand
Monad 提供了一个 因为对函数式和 Monad 的理解也不是非常深刻,在这里就不展开了,上面两个链接也只是从茫茫资料中挑选出来好理解的。毕竟是做程序,多敲代码才能更加理解。 在第一个知乎上面的回答中 Monad 的例子,写了一个 Swift 版本,方便理解吧: enum type {
case ret
case excp
}
// M 是一个 Monad
struct M <T> {
init(value: T,t: type) {
self.value = value
self.t = t
}
var value: T
var t: type
// 一个辅助 debug 方法
func log() {
if t == .ret {
print(value)
} else {
print("Exception")
}
}
// 将一个普通值放在一个 M 中
static func unit (value: T) -> M {
return M.init(value: value,t: .ret)
}
// 一个不可用的 M
static func raise () -> M {
return M.init(value: 0 as! T,t: .excp)
}
// >>= 方法
func selfBind (value: M,morph: ((T) -> M)) -> M {
if value.t == .excp {
return M.raise()
} else {
return morph(value.value)
}
}
// Swift 优化版 >>= 方法
func bind (morph: ((T) -> M)) -> M {
return selfBind(value: self,morph: morph)
}
}
M.unit(value: 8).bind(morph: { (v1: Int) -> M<Int> in return M.unit(value: 2).bind(morph: { (v2: Int) -> M<Int> in if v2 == 0 { return M.raise() } return M.unit(value: v1/v2) }) }).log() // 4 M.unit(value: 8).bind { (v1: Int) -> M<Int> in
return M.unit(value: 0).bind(morph: { (v2: Int) -> M<Int> in if v2 == 0 { return M.raise() } return M.unit(value: v1/v2) }) }.log() // Exception
响应式编程 (Reactive Programming)响应式编程是 ReactiveCocoa 的主要思想,Monad 是实现这种思想的方式。
这里也不展开讨论了,下面是两个讲解:
响应式编程 (Reactive Programming) 的关键点在事件流(Stream):
理解了以上两篇文章以后,对于 ReactiveCocoa 的理解就会简单很多了。 ReactiveCocoa 实现在上一个章节中了解到,ReactiveCocoa 实现了 FRP,也了解了什么是 FRP。再温习一下,FRP 的关键点在于:
再次来看一下一个 Stream 的整个订阅过程: 而对于 ReactiveCocoa 来说,其重点实现为:
看起来是比纯粹的 RP 订阅过程要复杂一些,其实重点还是 解读一下(具体代码在后面):
RACSignal先来看看 RACSignal 的继承关系: RACStream 是一个 Monad,代表了一个数据或事件流,它声明了 /// RACStream.h
///
/// A block which accepts a value from a RACStream and returns a new instance
/// of the same stream class.
///
/// Setting `stop` to `YES` will cause the bind to terminate after the returned
/// value. Returning `nil` will result in immediate termination.
typedef RACStream * (^RACStreamBindBlock)(id value,BOOL *stop);
/// An abstract class representing any stream of values.
///
/// This class represents a monad,upon which many stream-based operations can
/// be built.
///
/// When subclassing RACStream,only the methods in the main @interface body need
/// to be overridden.
@interface RACStream : NSObject
/// Lifts `value` into the stream monad.
///
/// Returns a stream containing only the given value.
+ (instancetype)return:(id)value;
/// Lazily binds a block to the values in the receiver.
///
/// This should only be used if you need to terminate the bind early,or close
/// over some state. -flattenMap: is more appropriate for all other cases.
///
/// block - A block returning a RACStreamBindBlock. This block will be invoked
/// each time the bound stream is re-evaluated. This block must not be
/// nil or return nil.
///
/// Returns a new stream which represents the combined result of all lazy
/// applications of `block`.
- (instancetype)bind:(RACStreamBindBlock (^)(void))block;
当然 RACSignal 也是一个 Monad,它的功能是通过一系列的类簇来实现的:
createSignal:创建信号的时候需要一个接收订阅者返回清理者的 block(didSubscribe),然后 RACSignal 会将这个 block 作为属性保存起来,并不执行。 // RACDynamicSignal.m
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe {
RACDynamicSignal *signal = [[self alloc] init];
// didSubscribe 是一个 block,在创建 Signal 的时候实现的,主要作用是指定信号源,确定数据或事件流
signal->_didSubscribe = [didSubscribe copy];
return [signal setNameWithFormat:@"+createSignal:"];
}
// 使用:创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSError *error;
// 做一些事情后
[subscriber sendNext:@1];
// 任务完成后
[subscriber sendCompleted];
// 如果发生错误
[subscriber sendError:error];
return [RACDisposable disposableWithBlock:^{
// 清理数据
}];
}];
subscribeNext:error:complete:// RACSignal.m
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock {
NSCParameterAssert(nextBlock != NULL);
NSCParameterAssert(errorBlock != NULL);
NSCParameterAssert(completedBlock != NULL);
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:errorBlock completed:completedBlock]; // RACDynamicSignal
return [self subscribe:o];
}
// RACDynamicSignal.m
// RACDynamicSignal 只实现了两个方法,第一个是 createSignal,第二个就是 subscribe
- (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber {
NSCParameterAssert(subscriber != nil);
// 一组 Disposable,可以理解为一个 Disposable 数组,当被 dispose,他会 dispose 所有它包含的 Disposable
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable];
if (self.didSubscribe != NULL) {
RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{
// 执行创建 signal 的时候保存的 block
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
[disposable addDisposable:innerDisposable];
}];
[disposable addDisposable:schedulingDisposable];
}
return disposable;
}
@protocol RACSubscriberRACSubscriber 是一个协议,其他的类可以遵循这个协议并且实现其方法,这样都可以做为一个订阅者。RACSubject 也是遵循了这个协议才可以即作为订阅者又作为信号的。这个协议声明了四个方法: // RACSubscriber.h
@protocol RACSubscriber <NSObject>
@required
/// Sends the next value to subscribers.
///
/// value - The value to send. This can be `nil`.
- (void)sendNext:(id)value;
/// Sends the error to subscribers.
///
/// error - The error to send. This can be `nil`.
///
/// This terminates the subscription,and invalidates the subscriber (such that
/// it cannot subscribe to anything else in the future).
- (void)sendError:(NSError *)error;
/// Sends completed to subscribers.
///
/// This terminates the subscription,and invalidates the subscriber (such that
/// it cannot subscribe to anything else in the future).
- (void)sendCompleted;
/// Sends the subscriber a disposable that represents one of its subscriptions.
///
/// A subscriber may receive multiple disposables if it gets subscribed to
/// multiple signals; however,any error or completed events must terminate _all_
/// subscriptions.
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable;
@end
前三个方法就不用多说了,是订阅中的最关键的方法。 最后一个方法是添加 Disposable 的,一个订阅者可以订阅多个 Signal,因此它也会收到多个 Disposable,因此是添加方法。注释中也写了,如果触发了 在 ReactiveCocoa 中,实现了 RACSubscriber 协议的类有:
RACSubscriber// RACSubscriber.m
// 注意,RACSubscriber 类是在 RACSubscriber+Pirvate.h 中声明的,而实现放在了 RACSubscriber.m 中
+ (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;
}
可以看到的是,RACSubscriber 声明了三个属性来存储对应的 block,当然还有 disposable: // RACSubscriber.m
@property (nonatomic,copy) void (^next)(id value);
@property (nonatomic,copy) void (^error)(NSError *error);
@property (nonatomic,copy) void (^completed)(void);
@property (nonatomic,strong,readonly) RACCompoundDisposable *disposable;
disposable 是在 init 的时候创建的,当执行 dispose 方法的时候将会把三个 block 置空。 最重要的,RACSubscriber 类实现了 RACSubscriber 协议的方法: // RACSubscriber.m
- (void)sendNext:(id)value {
@synchronized (self) {
void (^nextBlock)(id) = [self.next copy];
if (nextBlock == nil) return;
nextBlock(value);
}
}
- (void)sendError:(NSError *)e {
@synchronized (self) {
void (^errorBlock)(NSError *) = [self.error copy];
[self.disposable dispose];
if (errorBlock == nil) return;
errorBlock(e);
}
}
- (void)sendCompleted {
@synchronized (self) {
void (^completedBlock)(void) = [self.completed copy];
[self.disposable dispose];
if (completedBlock == nil) return;
completedBlock();
}
}
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)otherDisposable {
if (otherDisposable.disposed) return;
RACCompoundDisposable *selfDisposable = self.disposable;
[selfDisposable addDisposable:otherDisposable];
@unsafeify(otherDisposable);
// If this subscription terminates,purge its disposable to avoid unbounded
// memory growth.
[otherDisposable addDisposable:[RACDisposable disposableWithBlock:^{
@strongify(otherDisposable);
[selfDisposable removeDisposable:otherDisposable];
}]];
}
都是最基础的实现,调用对应的 block,添加 disposable。 RACPassthroughSubscriber上面在说 RACSubscriber 协议的时候提到:一个订阅者可以订阅多个 Signal。这个类就是完成这个任务的,它有三个属性: // RACPassthroughSubscriber.m
// The subscriber to which events should be forwarded.
@property (nonatomic,readonly) id<RACSubscriber> innerSubscriber;
// The signal sending events to this subscriber.
//
// This property isn't `weak` because it's only used for DTrace probes,so
// a zeroing weak reference would incur an unnecessary performance penalty in
// normal usage.
@property (nonatomic,unsafe_unretained,readonly) RACSignal *signal;
// A disposable representing the subscription. When disposed,no further events
// should be sent to the `innerSubscriber`.
@property (nonatomic,readonly) RACCompoundDisposable *disposable;
重点在于 RACSubscriber 协议的实现(删除了部分代码,只保留了重点代码): - (void)sendNext:(id)value {
if (self.disposable.disposed) return;
[self.innerSubscriber sendNext:value];
}
- (void)sendError:(NSError *)error {
if (self.disposable.disposed) return;
[self.innerSubscriber sendError:error];
}
- (void)sendCompleted {
if (self.disposable.disposed) return;
[self.innerSubscriber sendCompleted];
}
- (void)didSubscribeWithDisposable:(RACCompoundDisposable *)disposable {
if (disposable != self.disposable) {
[self.disposable addDisposable:disposable];
}
}
在这里,先判定了对应的 disposable 是否被销毁过,然后再确定是否触发对应的方法。 RACDisposableRACDisposable 是 ReactiveCocoa 中的清理者,它在一次订阅结束的时候清理相关数据和任务。 RACDisposable 的继承关系如下:
RACSchedulerRACScheduler 在 ReactiveCocoa 中起了调度的作用,其本质是封装了 GCD 的串行队列,以保证所有相关任务能有序进行。上面有说到,在 RACDynamicSignal subscribe 的时候,会使用 RACScheduler 将创建数据或事件流的 block 放到线程中执行。 RACScheduler 的继承关系如下:
RACSequenceRACSequence 也是 ReactiveCocoa 中一个比较重要的内容,它继承自 RACStream,但它并不能被当做一个信号被订阅,但它提供一个方法来转换成 signal。 RACSequence 代表的是一个序列,里面的值是有序存放的,这些值不可被改变,像 Objective-C 里面的 collection 类型一样,它不能保存空值。 RACSequence 中最重要的概念是 head 和 tail,就是头和尾巴,头指的是第一个值,尾巴指的是剩下的值,而一个尾巴也是一个 RACSequence,它也有自己的头和尾巴,一个最小的 RACSequence 单元就是尾巴只有一个值。 /// The first object in the sequence,or nil if the sequence is empty.
///
/// Subclasses must provide an implementation of this method.
@property (nonatomic,readonly) id head;
/// All but the first object in the sequence,readonly) RACSequence *tail;
RACSequence 存在的目的是为了简化 Objective-C 里面的集合操作,类似 map、filter 等功能,Swift 中的集合是有这样的方法的,但是 Objective-C 没有。
总结以上就是 ReactiveCocoa 的来由和基本架构及相关实现了。不过对于函数式和 Monad,还有很多操作,我们的 bind 还没有发挥作用呢,有了 bind 功能,可以实现很多更加自由的东西,比如 map,flattenMap 等非常强大的功能。ReactiveCocoa 还对 cocoa 的一些接口进行了封装,将一些数据和事件做成了事件流来方便我们使用。 在这里有一个 OC 里面的思路可以学习,就是使用类簇来实现相关功能,类似 ReactiveCocoa 相关的内容在研究的时候也参考了不少文章,在这里有一篇写的非常好:
ReactiveCocoa 进阶bind最开始在了解 Monad 的时候用 Swift 写的那个 Monad 中,就有一个 bind 方法: // >>= 方法
func selfBind (value: M,morph: ((T) -> M)) -> M {
if value.t == .excp {
return M.raise()
} else {
return morph(value.value)
}
}
// Swift 优化版 >>= 方法
func bind (morph: ((T) -> M)) -> M {
return selfBind(value: self,morph: morph)
}
// 使用
M.unit(value: 2).bind(morph: { (value: Int) -> M<Int> in if value == 0 { return M.raise() } return M.unit(value: 100/value) })
可以见得,bind 方法的作用是将 Monad 与一个 block 绑定,这个 block 可以获取到 Monad 中的值,然后对其进行处理,并将处理后的值作为一个新的 Monad 返回。 RACSignal 在实现 bind 方法的时候动作并不多,但是理解起来会很绕,绑定的 block 返回的是一个返回 signal 的 RACStreamBindBlock,bind 方法会返回一个新建的 bindSignal,在新建的 bindSignal 的 createSignalBlock 里面,订阅原信号(self),并在原信号发送值的时候使用 RACStreamBindBlock 处理值并返回一个持有新值的 newSignal,并且在这个时候订阅这个 newSignal,当这个 newSignal 发送值的时候 bindSignal 也会将这个新值发送出去。在使用 bind 的时候订阅的信号其实是 bindSignal。 // 正式看 bind 之前先看看 RACStreamBindBlock 的声明
// RACStream.h
typedef RACStream * (^RACStreamBindBlock)(id value,BOOL *stop);
// RACSignal.m
// bind 方法是必须要返回一个新的 Monad (RACSignal) 的。
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
NSCParameterAssert(block != NULL);
/* * -bind: should: * (-bind: 方法的实现:) * * 1. Subscribe to the original signal of values. * (1. 订阅原信号的值。) * 2. Any time the original signal sends a value,transform it using the binding block. * (2. 在原信号发送数据的时候,使用绑定的 block 处理这个数据。) * 3. If the binding block returns a signal,subscribe to it,and pass all of its values through to the subscriber as they're received. *( 3. 如果绑定的 block 返回的是一个信号(RACSignal),将订阅这个信号,然后通过其订阅者将获取到的值发送出去。) * 4. If the binding block asks the bind to terminate,complete the _original_ signal. * (4. 如果绑定的 block 要求绑定结束(使用 RACStreamBindBlock 的 stop 判定),那么完成原信号。) * 5. When _all_ signals complete,send completed to the subscriber. * (5. 当所有绑定的信号都完成了,那么给订阅者发送信号完成。) * * If any signal sends an error at any point,send that to the subscriber. * (任何绑定的信号在任何时候发送了 error,也将会被发送给订阅者。) */
// bind 方法返回的一定是一个新的 RACSignal
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
// 传递进来的参数是一个返回 RACStreamBindBlock 的 block,这里需要的是 RACStreamBindBlock
RACStreamBindBlock bindingBlock = block();
// 可能绑定的信号不止一个,用这个数组维护所有的绑定信号
// 每当绑定一个信号,将会将被绑定的信号存入数组
// 每当有一个信号 complete,那么就在数组中移除这个信号
// 如果所有的信号都 complete,这个数据也将为空,原信号也将被 complete
NSMutableArray *signals = [NSMutableArray arrayWithObject:self];
// 与上面的 signals 一样的道理,用来维护所有的信号的 Disposable
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
// 完成一个被绑定的信号代码块,将在后面被调用
void (^completeSignal)(RACSignal *,RACDisposable *) = ^(RACSignal *signal,RACDisposable *finishedDisposable) {
BOOL removeDisposable = NO;
@synchronized (signals) {
[signals removeObject:signal];
if (signals.count == 0) {
[subscriber sendCompleted];
[compoundDisposable dispose];
} else {
removeDisposable = YES;
}
}
if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable];
};
// 添加一个绑定的信号代码块,将在后面被调用
void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
@synchronized (signals) {
[signals addObject:signal];
}
// 被添加的信号将被订阅
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
// 订阅将原信号发送的值处理后生成的新 signal
// 当处理后的 signal 发送信号的时候绑定后生成的新 signal 也将调用对应的方法
RACDisposable *disposable = [signal subscribeNext:^(id x) {
[subscriber sendNext:x];
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(signal,selfDisposable);
}
}];
selfDisposable.disposable = disposable;
};
// 真正执行的代码
@autoreleasepool {
// 下面会订阅原信号,这个 Disposable 是用来保存当次订阅原信号的 Disposable
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
// 订阅原信号
RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
// 手动检测原信号的 Disposable 是否 dispose 过,避免同步错误
// Manually check disposal to handle synchronous errors.
if (compoundDisposable.disposed) return;
BOOL stop = NO;
// 执行绑定的 block,对原信号的 value 进行处理
id signal = bindingBlock(x,&stop);
@autoreleasepool {
// 添加信号
if (signal != nil) addSignal(signal);
// 完成信号
if (signal == nil || stop) {
[selfDisposable dispose];
completeSignal(self,selfDisposable);
}
}
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(self,selfDisposable);
}
}];
selfDisposable.disposable = bindingDisposable;
}
return compoundDisposable;
}] setNameWithFormat:@"[%@] -bind:",self.name];
}
flattenMapflattenMap 方法允许你提供一个处理原信号发送的值并返回一个任意的 Signal 的 block 作为参数,然后调用 bind 方法,返回一个新的 signal。 // RACStream.m
- (instancetype)flattenMap:(RACStream * (^)(id value))block {
Class class = self.class;
return [[self bind:^{
return ^(id value,BOOL *stop) {
id stream = block(value) ?: [class empty];
NSCAssert([stream isKindOfClass:RACStream.class],@"Value returned from -flattenMap: is not a stream: %@",stream);
return stream;
};
}] setNameWithFormat:@"[%@] -flattenMap:",self.name];
}
类比 bind 实现中的参数 block: ^{
return ^(id value,BOOL *stop) {
id stream = block(value) ?: [class empty];
NSCAssert([stream isKindOfClass:RACStream.class],stream);
return stream;
};
}
类比 bind 实现中的 ^(id value,BOOL *stop) {
id stream = block(value) ?: [class empty];
NSCAssert([stream isKindOfClass:RACStream.class],stream);
return stream;
};
这里类比一下 bind 实现中的参数是为了更加理解 bind,也只有使用了这个方法才能更加理解这个方法是干什么的。 mapmap 方法允许你提供一个处理原信号发送的值并返回新值的 block 作为参数,然后使用 RACSignal 的 - (instancetype)map:(id (^)(id value))block {
NSCParameterAssert(block != nil);
Class class = self.class;
return [[self flattenMap:^(id value) {
return [class return:block(value)];
}] setNameWithFormat:@"[%@] -map:",self.name];
}
类比 bind 实现中 return [class return:block(value)];
在这里,创建这个 signal 的时候并没有调用
还有很多其他方法都是使用 bind 实现的,例如: // 合并多个信号,当其中一个信号发送值的时候,返回的新信号都可以接收到信号,并且发送这个值
// 主要用途在 RACSgianl 的 merge 方法中
- (instancetype)flatten;
// 直接替换原信号发送的值,调用的是 map 方法
- (instancetype)mapReplace:(id)object;
// 筛选原信号发送的值
// block 处理值并返回一个 BOOL 值
// YES 将返回一个持有原值的新 signal
// NO 将返回一个 RACEmptySignal
// 调用了 flattenMap 方法
- (instancetype)filter:(BOOL (^)(id value))block;
// 忽略原信号发送的某个值
// 如果原信号发送的值与 value 相同,则直接忽略
// 调用了 filter 方法
- (instancetype)ignore:(id)value;
在这里就不展开分析了,只要了解了 bind 和每个方法的目的,就会很容易理解其操作与使用。 ReaciveCocoa 对 Cocoa 的封装ReactiveCocoa 对 Cocoa API 进行了一次封装,在使用的时候,用处比较大的有:
UIControlEvents类似 首先 - (void)addLoginButton {
UIButton *loginButton = [UIButton buttonWithType:UIButtonTypeCustom];
[loginButton addTarget:self action:@selector(login:) forControlEvents:(UIControlEventTouchUpInside)];
}
- (void)login:(id)sender {
// login
}
而经过 ReactiveCocoa 封装过后的使用方式如下: // UIControlEvents
UIButton *loginButton = [UIButton buttonWithType:UIButtonTypeCustom];
[[loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
// login
}];
封装过后的使用方式更加简便,初始化和事件触发都在一个地方。 ReactiveCocoa 给 UIControl 添加了分类,在这个分类中添加了 // UIControl+RACSignalSupport.h
- (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];
}
实现过程非常简单:新建一个 RACSignal,依然调用 Delegate 处理换句话说,协议的处理,或者说,某个方法的调用,我们都可以将其设定成为一个 signal。这个东西在 cocoa 中用处其实一般,但是其实现方法值得学习。 比如我们需要监听 UIAlertView 的 [[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple *args) {
UIAlertView *alert = args.first;
NSNumber *index = args.second;
}];
这个方法是定义在 // NSObject+RACSelectorSignal.h
// 将方法转换为 signal
- (RACSignal *)rac_signalForSelector:(SEL)selector; // 将协议的方法转换为 signal - (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol;
他们的实现是这样的: // NSObject+RACSelectorSignal.m
- (RACSignal *)rac_signalForSelector:(SEL)selector { NSCParameterAssert(selector != NULL); return NSObjectRACSignalForSelector(self,selector,NULL); } - (RACSignal *)rac_signalForSelector:(SEL)selector fromProtocol:(Protocol *)protocol { NSCParameterAssert(selector != NULL); NSCParameterAssert(protocol != NULL); return NSObjectRACSignalForSelector(self,protocol); }
所以,重点是 NSObjectRACSignalForSelector(selector,protocol) 这个方法的实现,它将一个方法转换成为了 signal。以下为实现,比较复杂: // NSObject+RACSelectorSignal.m
static RACSignal *NSObjectRACSignalForSelector(NSObject *self,SEL selector,Protocol *protocol) {
// 一个别名方法名
SEL aliasSelector = RACAliasForSelector(selector);
@synchronized (self) {
// 后面会将方法转换为一个 RACSubject,并且使用 runtime 保存为一个属性,key 为别名方法名
RACSubject *subject = objc_getAssociatedObject(self,aliasSelector);
// 如果这个方法的对应信号已经创建,那么将不再创建
if (subject != nil) return subject;
// 重点方法,主要是利用 MethodSwizzle 技术
// 使用 runtime 新建了一个实现放到了消息转发方法
// forwardInvocation: 中,在新的实现中:
// 每次调用都将会把对应的参数使用元组(RACTuple)的方式封装
// 并且使用 subject 的 sendNext: 方法发送
// 给一个类添加消息转发方法的时候还必须实现 methodSignatureForSelector: 方法
// 因此也为这个方法新建了实现
// 因为也考虑到 selector 方法可能是协议方法,本类中本来并没有实现
// 为 responseToSelector 方法也创建了一个新的实现
// 返回一个类型,后面的获取方法等操作都是在这个类下操作的
Class class = RACSwizzleClass(self);
NSCAssert(class != nil,@"Could not swizzle class of %@",self);
// 新建 RACSubject 信号,并存储为属性
subject = [[RACSubject subject] setNameWithFormat:@"%@ -rac_signalForSelector: %s",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) {
// 方法为空,则表示这个方法是 protocol 方法
// 用来获取方法的参数及返回值类型
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,NO,YES);
// 检测这个协议是否包含对应的方法
if (methodDescription.name == NULL) {
// Then fall back to looking for a required instance
// method.
methodDescription = protocol_getMethodDescription(protocol,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,_objc_msgForward,typeEncoding)) {
// 创建不成功的话信号将返回错误
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedString(@"A race condition occurred implementing %@ on class %@",nil),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) {
// 本类包含的方法,但是不能是消息转发方法
// Make a method alias for the existing method implementation.
const char *typeEncoding = method_getTypeEncoding(targetMethod);
RACCheckTypeEncoding(typeEncoding);
// 添加别名方法,其实现为原方法的实现
BOOL addedAlias __attribute__((unused)) = class_addMethod(class,method_getImplementation(targetMethod),typeEncoding);
NSCAssert(addedAlias,@"Original implementation for %@ is already copied to %@ on %@",NSStringFromSelector(aliasSelector),class);
// 将原方法的实现替换为消息转发方法的实现
// Redefine the selector to call -forwardInvocation:.
class_replaceMethod(class,method_getTypeEncoding(targetMethod));
}
return subject;
}
}
如果不关心 // NSObject+RACSelectorSignal.m
static Class RACSwizzleClass(NSObject *self) {
// 获取本类的类型,这里用两种方式获取
// 是因为如果使用类簇开发的,这两个获取的结果不一样
// statedClass 获取的是调用的类
// baseClass 获取的是真正创建的类
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()) {
// 在实际实现的类上 swizzle 对应的方法
if (![swizzledClasses() containsObject:className]) {
RACSwizzleForwardInvocation(baseClass);
RACSwizzleRespondsToSelector(baseClass);
RACSwizzleGetClass(baseClass,statedClass);
RACSwizzleGetClass(object_getClass(baseClass),statedClass);
RACSwizzleMethodSignatureForSelector(baseClass);
[swizzledClasses() addObject:className];
}
}
return baseClass;
}
// 如果这个类就是实现的类
// 那么将新建一个这个类的子类
// 这个子类的名字是原类名字后面添加 _RACSelectorSignal
// 然后 swizzle 这个子类的对应方法
// 获取子类名字
const char *subclassName = [className stringByAppendingString:RACSubclassSuffix].UTF8String;
// 获取子类
Class subclass = objc_getClass(subclassName);
// 如果子类没有创建
if (subclass == nil) {
// 创建子类
subclass = [RACObjCRuntime createClass:subclassName inheritingFromClass:baseClass];
if (subclass == nil) return nil;
// swizzle 对应方法
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;
}
其实看看也会发现有很多细节,以及 ReactiveCocoa 在实现功能的时候的安全性以及特殊性考虑是非常细致的。这个非常考验开发者对 Cocoa 底层实现细节的理解程度,看这段代码的时候我也是研究了很久才搞明白它在干什么,关于
上面一段中,最重要的方法是 // NSObject+RACSelectorSignal.m
static void RACSwizzleForwardInvocation(Class class) {
// 获取原消息转发方法
SEL forwardInvocationSEL = @selector(forwardInvocation:);
Method forwardInvocationMethod = class_getInstanceMethod(class,forwardInvocationSEL);
// 暂存原消息转发方法
// Preserve any existing implementation of -forwardInvocation:.
void (*originalForwardInvocation)(id,SEL,NSInvocation *) = NULL;
if (forwardInvocationMethod != NULL) {
originalForwardInvocation = (__typeof__(originalForwardInvocation))method_getImplementation(forwardInvocationMethod);
}
// 新建一个消息转发方法的实现
// Set up a new version of -forwardInvocation:.
//
// If the selector has been passed to -rac_signalForSelector:,invoke
// the aliased method,and forward the arguments to any attached signals.
//
// If the selector has not been passed to -rac_signalForSelector:,
// invoke any existing implementation of -forwardInvocation:. If there
// was no existing implementation,throw an unrecognized selector
// exception.
id newForwardInvocation = ^(id self,NSInvocation *invocation) {
// 发送消息
BOOL matched = RACForwardInvocation(self,invocation);
// 如果发送消息成功,则不再有任何处理
if (matched) return;
// 发送消息失败
if (originalForwardInvocation == NULL) {
// 如果原方法不存在,报错,表示这个方法不存在
[self doesNotRecognizeSelector:invocation.selector];
} else {
// 如果原方法存在,则调用原方法
originalForwardInvocation(self,forwardInvocationSEL,invocation);
}
};
// 将原消息转发方法的实现替换为新建的实现
class_replaceMethod(class,imp_implementationWithBlock(newForwardInvocation),"v@:@");
}
新建的消息转发方法的实现中,最重要的一段是 // NSObject+RACSelectorSignal.m
static BOOL RACForwardInvocation(id self,NSInvocation *invocation) {
// 在转换最开始的时候,对于将要被转换为 signal 的 selector
// ReactiveCocoa 新建了一个别名方法
// 并且将转换的 signal 使用这个方法的名字当做 key 保存成了属性
// 取出来这个属性
SEL aliasSelector = RACAliasForSelector(invocation.selector);
RACSubject *subject = objc_getAssociatedObject(self,aliasSelector);
// 先获取消息转发的目标类
Class class = object_getClass(invocation.target);
// 如果这个类有这个别名方法
BOOL respondsToAlias = [class instancesRespondToSelector:aliasSelector];
if (respondsToAlias) {
// 设定方法名
invocation.selector = aliasSelector;
// 调用这个方法
[invocation invoke];
}
// 没有信号,则说明这个方法并没有被转换为 signal
// 则返回这个类是否存在这个别名方法
if (subject == nil) return respondsToAlias;
// 将这个 invocation 传递进去的参数封装成 RACTuple 发送出去
[subject sendNext:invocation.rac_argumentsTuple];
return YES;
}
这样就结束了,关于 RACTuple,它代表了一个元组,是 ReactiveCocoa 自己创建的一个元组类,用来管理元组中的元素。而 在这里说的是 ReactiveCocoa 如何实现将现有的协议转化为信号的。 不使用协议,自己创建 RACSignal 来实现代理的功能就不多讲了,很容易的 RACSignal 创建和订阅行为,主要是要思考到 dispose 的时候需要释放的内容以及一些特殊情况的处理。 代替 KVO 的处理Cocoa 提供的 KVO 使用方式如下: // 添加监听
[self addObserver:self forKeyPath:keyPath options:options context:nil];
// 添加回调方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
}
// 移除监听
[self removeObserver:self forKeyPath: keyPath];
其实也并不复杂,不过 ReactiveCocoa 将它更加简化了: [RACObserve(self,keyPath) subscribeNext:^(id x) { }];
将 KVO 变成一个信号,其实 KVO 的整个过程很像一个信号,我们监听信号,处理信号传来的值,再完成监听。 ReactiveCocoa 的实现其实也并不复杂,依旧是封装了 Cocoa 提供的 KVO 使用方法。其关键类如下:
RACKVOTrampoline : RACDisposable仅仅对外暴露了一个初始化方法: // RACKVOTrampoline.h
//
// Initializes the receiver with the given parameters.
//
// target - The object whose key path should be observed. Cannot be nil.
// observer - The object that gets notified when the value at the key path
// changes. Can be nil.
// keyPath - The key path on `target` to observe. Cannot be nil.
// options - Any key value observing options to use in the observation.
// block - The block to call when the value at the observed key path changes.
// Cannot be nil.
//
// Returns the initialized object.
- (id)initWithTarget:(__weak NSObject *)target observer:(__weak NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(RACKVOBlock)block;
其实现中仅四个方法:
RACKVOProxy真正的监听者,维护 RACKVOTrampoline 和监听的 context 到一个 NSMapTable 表中。提供了如下几个方法: // 将 RACKVOTrampoline 和 context 绑定
- (void)addObserver:(__weak NSObject *)observer forContext:(void *)context;
// 解除 RACKVOTrampoline 和 context 的绑定
- (void)removeObserver:(NSObject *)observer forContext:(void *)context;
// 接收到属性变化的监听,使用 conext 在 NSMapTable 表中取出 RACKVOTrampoline
// 然后调用其监听方法
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
在我看来,使用 KVO 代理进行监听,是为了尽量不影响到使用者的原代码,这也是一个三方框架的自我修养。 NSObject+RACKVOWrapper一个 NSObject 的分类,提供了一个方法来调用 RACKVOTrampoline 。 // NSObject+RACKVOWrapper.h
//
// Adds the given block as the callbacks for when the key path changes.
//
// Unlike direct KVO observation,this handles deallocation of `weak` properties
// by generating an appropriate notification. This will only occur if there is
// an `@property` declaration visible in the observed class,with the `weak`
// memory management attribute.
//
// The observation does not need to be explicitly removed. It will be removed
// when the observer or the receiver deallocate.
//
// keyPath - The key path to observe. Must not be nil.
// options - The KVO observation options.
// observer - The object that requested the observation. May be nil.
// block - The block called when the value at the key path changes. It is
// passed the current value of the key path and the extended KVO
// change dictionary including RAC-specific keys and values. Must not
// be nil.
//
// Returns a disposable that can be used to stop the observation.
- (RACDisposable *)rac_observeKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer block:(void (^)(id value,NSDictionary *change,BOOL causedByDealloc,BOOL affectedOnlyLastComponent))block;
在这里实现中,有两点考虑:
关于第二个问题,可以查看下面两个 ReactiveCocoa 的 issue:
NSObject+RACPropertySubscribing因为前面已经处理了很多东西,这里的实现比较简单,创建一个 Signal,然后调用 它提供了两个方法来添加 KVO: // NSObject+RACPropertySubscribing.h
//
/// Creates a signal to observe the value at the given key path.
///
/// The initial value is sent on subscription,the subsequent values are sent
/// from whichever thread the change occured on,even if it doesn't have a valid
/// scheduler.
///
/// Returns a signal that immediately sends the receiver's current value at the
/// given keypath,then any changes thereafter.
- (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer;
/// Creates a signal to observe the changes of the given key path.
///
/// The initial value is sent on subscription,even if it doesn't have a valid
/// scheduler.
///
/// Returns a signal that sends tuples containing the current value at the key
/// path and the change dictionary for each KVO callback.
- (RACSignal *)rac_valuesAndChangesForKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options observer:(__weak NSObject *)observer;
但是并没有使用,而是使用了宏 #define RACObserve(TARGET,KEYPATH) ({ _Pragma("clang diagnostic push") _Pragma("clang diagnostic ignored "-Wreceiver-is-weak"") __weak id target_ = (TARGET); [target_ rac_valuesForKeyPath:@keypath(TARGET,KEYPATH) observer:self]; _Pragma("clang diagnostic pop") })
这里的重点是 keypath 宏,它的作用如下: NSString *UTF8StringPath = @keypath(str.lowercaseString.UTF8String);
// => @"lowercaseString.UTF8String"
NSString *versionPath = @keypath(NSObject,version);
// => @"version"
NSString *lowercaseStringPath = @keypath(NSString.new,lowercaseString);
// => @"lowercaseString"
详细了解这些宏都做了什么,可以查看这里。 总结到这里该结束了,写了非常非常多,还有很多东西都没写。ReactiveCocoa 这个框架实在太厉害,上面有好几个点都不在一开始计划中,但是因为觉得写的很好,而且细节略过去对于理解它是一件坏事,所以浩浩荡荡写了很多。 每个框架的学习都将带给你新的知识,很多时候不光要去了解他们是怎么实现的,还要去思考为什么这样做。至少在 ReactiveCocoa 中,值得学习的地方很多。很多地方都使用到了 ReactiveCocoa 还有很多东西,后续不会再有分析了,但是依然会继续研究。有一点需要提醒,RAC 是开源的,在 github 就可以查看,最重要的是广大程序员在这里提交的 issue 和 pull request,都是比较有用的,也可以看看别人是如何发现这些问题,是如何处理的。 授人以鱼不如授人以渔,在学习的时候,我一直坚持学习学习方法,提高自己发现问题和解决问题的能力,这点是非常重要的,没有人 24 小时做你的问题解决机,大部分问题还是要自己解决的。 共勉。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |