前言 OC中的异步行为:
block
delegate
KVO
通知
target-action机制
异步行为的共同点:
何时触发并不确定,观察者需要等待其触发时的回调;
异步行为的创建和触发并不在同一块区域,往往是分开的。
异步行为的缺点:
异步行为的创建和回调相对分散,这不符合代码规范中”高内聚”的要求;
使用不当时,这些行为往往会造成循环引用等问题。
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 借鉴了RX 的思想,它处理这些问题的核心在于信号
:
针对内聚问题,RAC将异步行为的实现封装在信号的didSubscribe
block 中,从而能对外提供统一的接口。我们只需要在创建完信号后订阅信号,即可在异步行为触发时收到回调,这样创建、监听、业务逻辑聚合到了一起;
针对第二点,信号的 block 内向订阅者发送了sendNext:
事件后,会自动清理资源和引用,从而解决了循环引用问题。
下面将分别介绍它们的使用和实现原理。
block block 创建完成后在随后的某个时间点被调用,执行其内部定义的业务。RAC中block的使用如下:
#示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @property (nonatomic , strong ) id <RACSubscriber> subscriber; - (void )viewDidLoad { [super viewDidLoad]; RACSignal *s1 = [RACSignal createSignal:^RACDisposable *(id <RACSubscriber> subscriber) { _subscriber = subscriber; return [RACDisposable disposableWithBlock:^{ NSLog (@"++s1 Disposed~" ); }]; }]; [s1 subscribeNext:^(id x) { NSLog (@"+++订阅触发:%@" ,x); }]; [_subscriber sendNext:@"Hello world~" ]; }
执行[s1 subscribeNext:]订阅信号时,我们传入了一个 nextBlock,它会被保存起来以在后续被调用。subscribeNext:的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock { NSCParameterAssert (nextBlock != NULL ); RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL ]; return [self subscribe:o]; } + (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; }
信号内部自动创建了一个订阅者
对象,并将我们传入的 nextBlock 保存了起来。
随后在示例中我们调用了[_subscriber sendNext:],其实现如下:
1 2 3 4 5 6 7 - (void )sendNext:(id )value { @synchronized (self ) { void (^nextBlock)(id ) = [self .next copy ]; if (nextBlock == nil ) return ; nextBlock(value); } }
即调用 sendNext: 相当于调用了之前保存在订阅者内部的nextBlock
并向其输入了一个新值。
KVO #示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @property (nonatomic , copy ) NSString *text; - (void )viewDidLoad { [super viewDidLoad]; [[self rac_valuesAndChangesForKeyPath:@"text" options:NSKeyValueObservingOptionNew observer:self ] subscribeNext:^(RACTwoTuple<id ,NSDictionary *> *x) { NSLog (@"++self.text update:%@" ,x.first); }]; self .text = @"Text1" ; _text = @"Text2" ; }
日志:
1 ++self.text update :Text1
注意:要使用self.xxx
来赋值,下划线的方式是给成员变量直接赋值,并不会触发KVO回调~~
RAC 版的 KVO 与 OC 版的差别并不大,rac_valuesAndChangesForKeyPath:options:observer:方法是RAC在 NSObject 分类中定义的一个方法。它的返回值是一个信号,供我们订阅并自定义回调block。其内部最终是通过RACKVOTrampoline
来管理和实现KVO的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 - (instancetype )initWithTarget:(__weak NSObject *)target observer:(__weak NSObject *)observer keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions )options block:(RACKVOBlock)block { NSCParameterAssert (keyPath != nil ); NSCParameterAssert (block != nil ); NSObject *strongTarget = target; if (strongTarget == nil ) return nil ; self = [super init]; _keyPath = [keyPath copy ]; _block = [block copy ]; _weakTarget = target; _unsafeTarget = strongTarget; _observer = observer; [RACKVOProxy.sharedProxy addObserver:self forContext:(__bridge void *)self ]; [strongTarget addObserver:RACKVOProxy.sharedProxy forKeyPath:self .keyPath options:options context:(__bridge void *)self ]; [strongTarget.rac_deallocDisposable addDisposable:self ]; [self .observer.rac_deallocDisposable addDisposable:self ]; return self ; } - (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); }
RACKVOTrampoline
将我们传入的observer
和target
及回调block
保存了起来。属性变化后KVO代理中回调了 block 并传回变化的信息。
KVC KVC本身不算异步行为,因为OC中调用[self valueForKey:@”text”]后,能立刻得到text
属性的值。但是 RAC 还是对其进行了封装和扩展,使得 KVC 既能立刻获取属性的值,又能和 KVO 一样持续收到属性变化的回调。
#示例:
1 2 3 4 5 6 7 8 9 10 11 @property (nonatomic , copy ) NSString *text; - (void )viewDidLoad { [super viewDidLoad]; [[self rac_valuesForKeyPath:@"text" observer:self ] subscribeNext:^(id x) { NSLog (@"++++self.text By KVC:%@" ,x); }]; self .text = @"Text1" ; _text = @"Text2" ; }
日志:
1 2 ++++self .text By KVC:(null ) ++++self .text By KVC:Text1
调用 rac_valuesForKeyPath: 并订阅信号后,立刻收到了一次回调,返回属性的当前值null
;
调用 self.text = @”Text1” 给属性赋值后,订阅者的回调再次触发并传回当前的新值。
调用 _text = @”Text2” 给属性的成员变量赋值时,并未触发订阅者回调,这与 RAC 的 KVO 一样。
结合这些情况来看,RAC 的 KVC 似乎与其 KVO 有密切关系,那么我们来看看其方法的实现:
1 2 3 4 5 6 7 8 9 - (RACSignal *)rac_valuesForKeyPath:(NSString *)keyPath observer:(__weak NSObject *)observer { return [[[self rac_valuesAndChangesForKeyPath:keyPath options:NSKeyValueObservingOptionInitial observer:observer] map:^(RACTuple *value) { return value[0 ]; }] setNameWithFormat:@"RACObserve(%@, %@)" , RACDescription(self ), keyPath]; }
可以看到,RAC 的 KVC 内部只是调用了其 KVO 方法并返回元组的第一个值。这就不难理解上面的猜测了~
通知 #示例:
1 2 3 4 5 [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil ] subscribeNext:^(NSNotification *x) { NSLog (@"++通知:%@" ,x); }];
监听键盘通知并订阅其返回的信号,待键盘弹出或收回时即可触发订阅者的回调。其实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @implementation NSNotificationCenter (RACSupport ) - (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 ]; }@end
在RAC对 NSNotificationCenter 的扩展中,监听通知后实际上只是调用了OC原生的通知监听方法,在原生回调中向订阅者发送消息~
target-action #示例:
1 2 3 [[_mBtn1 rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIControl *x) { NSLog(@ "++clicked Btn1~" ) }]
其内部实现为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @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
RAC在UIControl
的分类中返回一个信号,通过OC原生的”addTarget:action:forControlEvents:”添加了subscriber
为target
,而其响应方法为sendNext:
。所以当按钮被点击后会触发subscriber
的sendNext:
方法,随即回调订阅者的 block。
delegate RAC中的delegate
可以通过RACSubject
来实现。官方文档中关于此类的描述如下:
A subject, represented by the RACSubject class, is a signal that can be manually controlled. Subjects can be thought of as the “mutable” variant of a signal, much like NSMutableArray is for NSArray. They are extremely useful for bridging non-RAC code into the world of signals. For example, instead of handling application logic in block callbacks, the blocks can simply send events to a shared subject instead. The subject can then be returned as a RACSignal, hiding the implementation detail of the callbacks.
RACSubject
继承自RACSignal
,是一种可以由我们控制的信号。
相比于在某个 block 回调中自己处理业务逻辑,RAC 可以让 block 向某个共享的 subject 发送事件并让其处理该业务逻辑。
#示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 - (IBAction )onAction2:(id )sender { ViewControllerII *controller = [[UIStoryboard storyboardWithName:@"Main" bundle:nil ] instantiateViewControllerWithIdentifier:@"ViewControllerII" ]; controller.delegate = [RACSubject subject]; [controller.delegate subscribeNext:^(id x) { NSLog (@"%@" ,x); }]; [self .navigationController pushViewController:controller animated:YES ]; }@interface ViewControllerII : UIViewController @property (nonatomic , strong ) RACSubject *delegate;@end @implementation ViewControllerII - (IBAction )onDismiss:(id )sender { [self .delegate sendNext:@"++ViewControllerII Closed~" ]; [self .navigationController popViewControllerAnimated:YES ]; }@end
RACSubject
是信号的子类,我们在 A 中订阅 B 中的信号,并在 B 中向 A 中订阅者发送消息,以此实现代理的功能。
小结 综上,RAC在对异步行为进行封装时所做的工作主要为:
将这些行为封装到信号的 block 中(有些只是调用原生接口);
我们只须订阅此信号就能以一种统一的方式实现业务代码的内聚;
信号向订阅者发送完事件之后随即清理资源和引用;
RAC将这些异步行为的创建、监听、业务回调集中在一片代码区域或交由RAC内部实现,这符合高内聚的设计思想,值得学习和借鉴~
相关参考:
#©RAC-Github