美文网首页iOS接下来要研究的知识点从0到1学习RAC
iOS ReactiveCocoa学习笔记(1):基本使用

iOS ReactiveCocoa学习笔记(1):基本使用

作者: 一粒咸瓜子 | 来源:发表于2019-05-29 16:17 被阅读31次

本文知识点:RACSignal使用、combine、@weakify@strongify、Model与UI双向绑定。

1. 介绍

ReactiveCocoa 接管了苹果的事件机制,asyncDisplayKit接管了苹果的UIKit。

  • 运用的是Hook(钩子)思想,Hook是一种用于改变API(应用程序编程接口:方法)执行结果的技术。
  • Hook用处:截获API调用的技术。
  • Hook原理:在每次调用一个API返回结果之前,先执行你自己的方法,改变结果的输出。

1.1 响应式

使用RAC的原因:实现响应式编程。

  • 什么是响应式编程:
b = 2; c = 3;
a = b + c;  //a = 5;
b = 100;  //此时对修改 b 的修改并不会使 a 发生改变。

响应式:当修改b或c的时候,a同时发生变化。

  • iOS开发中实现响应式
    • 方法一:使用KVO监听对象的属性值达到这一效果。但缺点是KVO会统一调用同一个方法,如果监听属性过多,方法非常难以维护。
    • 方法二:ReactiveCocoa是目前实现响应式编程的唯一解决方案。

1.2 难点

  • 学习曲线陡峭
  • 在团队开发的时候要特别谨慎
  • 需要不断的代码评审,保证团队的代码风格一致
  • 开发的时候调用堆栈深不见底,提高debug成本

1.3 框架导入

Cocoapods导入ReactiveCocoa5.0以上版本需注意:

  • Swift 项目:使用 ReactiveCocoa
    但是 RAC 依赖于 ReactiveSwift,等于你引入了两个库。
pod 'ReactiveCocoa'
  • OC 项目:使用 ReactiveObjC
    这个库里面包含原来 RAC 2 的全部代码。
pod 'ReactiveObjC'
pod 'ReactiveObjC'
pod 'ReactiveCocoa'
pod 'ReactiveObjCBridge'

2. 使用

2.1 RACSignal

1> 信号的创建

RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"123"];
    [subscriber sendNext:@"456"];
    NSLog(@"%@",subscriber);
    // 如果不再发送数据,最后发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
    [subscriber sendCompleted];
    [subscriber sendError:[NSError errorWithDomain:@"send error" code:0 userInfo:@{}]];
    return [RACDisposable disposableWithBlock:^{
        // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
        // 执行完Block后,当前信号就不再被订阅了。
        // 信号销毁的时候 会执行这个闭包
        // 用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
        // 使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。
        NSLog(@"dispose");
    }];
}];
//信号被订阅。订阅者不是信号本身 而是这段代码所处的那个objcect 如在vc中,订阅者就是vc
[signal subscribeNext:^(id  _Nullable x) {
    NSLog(@"订阅next :%@",x);
}];
[signal subscribeError:^(NSError * _Nullable error) {
    NSLog(@"订阅error :%@",x);
}];
[signal subscribeCompleted:^ {
    NSLog(@"订阅completed");
}];


Output:
订阅next :123  //订阅的next信号
订阅next :456
<RACPassthroughSubscriber: 0x60400043aa80>  
dispose  
//订阅的error信号(因为代码顺序),但是此时已无效,因为subscriber发出了completed信号
<RACPassthroughSubscriber: 0x60400043aa80>
dispose
订阅completed
<RACPassthroughSubscriber: 0x60400043aa80>
dispose
  • RACSignal是冷信号,只有被订阅了才可以工作。
  • RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。
  • RACDisposable:用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
    使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。

2> 控件的监听

UI类RAC自动封装了一些方法,使用的时候先去框架源文件中找。以 button 为例:

[[button rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
    NSLog(@"%@", x);
} error:^(NSError * _Nullable error) {
    NSLog(@"%@",error);
} completed:^{
    NSLog(@"completed");
}];

3> 信号的合并

类似元组类型,可以一次订阅多个信号。

//信号合并
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals;
//reduce中可以通过接受的参数进行计算,并且返回需要的数值
+ (RACSignal *)combineLatest:(id<NSFastEnumeration>)signals reduce:(RACGenericReduceBlock)reduceBlock;


Ex1:                                                                                                         //元组类型
[[RACSignal combineLatest:@[[name_textField rac_textSignal], [pwd_textField rac_textSignal]]] subscribeNext:^(RACTuple * _Nullable x) {
    NSLog(@"%@ %@", x.first, x.second);
}];
Ex2:
//例如订阅username、password,reduce里面判断只有用户名和密码同时存在才允许登录
RACSignal *signal_username = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"abc"];
    return nil;
}];
    
RACSignal *signal_pwd = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
    [subscriber sendNext:@"123"];
    return nil;
}];
                                                                       //注意 参数需要自己添加上去
[[RACSignal combineLatest:@[signal_username, signal_pwd] reduce:^id _Nonnull (NSString *username, NSString *password){
    return @(username.length > 0 && password.length > 0);
}] subscribeNext:^(id  _Nullable x) {
    //x是上面的@(bool)
    NSLog(@"%@", x);  
}];


Output: 1

4> RAC中的循环引用

因为系统提供的信号是始终存在的,因此在RAC中所有的block中,如果出现self._成员变量,几乎百分之百会循环引用。
解决办法:weak-strong dance(RAC提供了宏 @weakify & @strongify)

2.2 RAC双向绑定

需要用到两个重要宏: RAC()、RACObserve(),源码如下:

#define RAC(TARGET, ...) \
    metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
        (RAC_(TARGET, __VA_ARGS__, nil)) \
        (RAC_(TARGET, __VA_ARGS__))

#define _RACObserve(TARGET, KEYPATH) \
({ \
    __weak id target_ = (TARGET); \
    [target_ rac_valuesForKeyPath:@keypath(TARGET, KEYPATH) observer:self]; \
})
#if __clang__ && (__clang_major__ >= 8)
#define RACObserve(TARGET, KEYPATH) _RACObserve(TARGET, KEYPATH)
#else
#define RACObserve(TARGET, KEYPATH) \
({ \
    _Pragma("clang diagnostic push") \
    _Pragma("clang diagnostic ignored \"-Wreceiver-is-weak\"") \
    _RACObserve(TARGET, KEYPATH) \
    _Pragma("clang diagnostic pop") \
})
#endif

1> Model -> UI (利用KVO宏)

RAC(TARGET, ...) = RACObserve(TARGET, KEYPATH);

如果使用基本数据类型绑定UI内容,需要使用map函数,通过block对value的数值进行转换后才能够绑定。

RAC(name_textField, text) = RACObserve(_person, name);
//rac中传递的数据都是id类型,如果是基本类型,需要使用map函数,通过block对value的数值进行转换后才能够绑定。
RAC(age_textField, text) = [RACObserve(_person, age) map:^id _Nullable(id  _Nullable value) {
    return [value description];
}];

2> UI -> Model (订阅控件发出的signal)

@weakify(self);
[[RACSignal combineLatest:@[[name_tf rac_textSignal], [age_tf rac_textSignal]]] subscribeNext:^(RACTuple * _Nullable x) {
    @strongify(self);
    self.person.name = x.first;
    self.person.age = [x.second integerValue];
}];

// 或
RAC(_person, name) = name_tf.rac_textSignal;
RAC(_person, age) = age_tf.rac_textSignal;

3. MVVM+RAC

MVVM+RAC

相关文章

网友评论

    本文标题:iOS ReactiveCocoa学习笔记(1):基本使用

    本文链接:https://www.haomeiwen.com/subject/rkyytctx.html