ReactiveCocoa基础类学习笔记

前言

最近第一次接触了一个采用MVVM架构的项目。有很多人学习RAC应该是为了适应MVVM。关于MVVM推荐一篇文章->iOS 关于MVVM With ReactiveCocoa设计模式的那些事。说得挺好,但完全不会函数响应式编程的我,看到后面部分就止步了。 MVVM这种概念性的东西,如果不实操,看完都学不到什么。于是先学习RAC,发现网上教程大多都看不懂???在某文章翻到这样一句hhhhhhhh。

为了加深理解,故从初学者的角度写下这篇笔记,显得啰嗦。希望能带给小白们something helpful

本文先不管RAC有什么用,入门学习,下一篇文章笔者再尽能力展示RAC的应用。

ReactiveCocoa入门学习

笔者整理了一份笔记。开发时可以对着想用什么,熟了以后就不用看了。

RACSignal

RAC中常用的类是RACSignal,据说会用就能开发了。所以这里探讨其实现。

大多教程都是说三步。

    // RACSignal使用步骤:

    // 1.创建信号 + (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe

    // 2.订阅信号,才会激活信号. - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock

    // 3.发送信号 - (void)sendNext:(id)value

复制代码

然后给出代码

// 1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        // block调用时刻:每当有订阅者订阅信号,就会调用block。
        
        NSLog(@"信号被订阅");
        // 2.发送信号
        [subscriber sendNext:@1];
        
        // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
        [subscriber sendCompleted];
 
        return [RACDisposable disposableWithBlock:^{
            
            // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
            
            // 执行完Block后,当前信号就不在被订阅了。
            
            NSLog(@"信号被取消订阅");
            
        }];
    }];
    
    // 3.订阅信号,才会激活信号.
    [signal subscribeNext:^(id x) {
        // block调用时刻:每当有信号发出数据,就会调用block.
        NSLog(@"接收到数据:%@",x);
    }];
复制代码

看上去觉得很好用,但仔细想想这执行顺序是怎么回事?


笔者整理了一张图,可以本地打开这图,对照着下文理解。

只要能理解这图,RAC的实现就不会显得迷茫。

  1. 创建信号

我们先从+ (RACSignal *)createSignal:(RACDisposable * (^)(id subscriber))didSubscribe探讨

注意到block的参数中RACSubscriber是个协议,后面也有一个相同的类名。

该方法将blockdidSubscribe传进了另一个类RACDynamicSignal,其是RACSignal的子类,最直接的区别是其有一个block成员变量。

//  RACDynamicSignal.h
@interface RACDynamicSignal : RACSignal

+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;

@end

//  RACDynamicSignal.m
@interface RACDynamicSignal ()

// The block to invoke for each subscriber.
@property (nonatomic, copy, readonly) RACDisposable * (^didSubscribe)(id<RACSubscriber> subscriber);

@end
复制代码

原来生成了一个RACDynamicSignal的对象,并把didiSubscribe赋给了成员变量,最终返回该对象。这个block将在第二步中被调用。

所以,运行时signal实际上属于RACDynamicSignal

  1. 订阅信号

在生活中,信号只有被订阅。订阅说起来就像某个订阅者调用了订阅信号方法一样 [subscriber subscribe:signal];但订阅方法事实上是信号对象本身找到订阅者[singnal subscribe:subscriber]。。。

所以笔者认为这里理解为激活信号更妥当。

上面提到signal实际上属于RACDynamicSignal,所以查看方法时不要跳转错了。

可以看到生成了一个RACSubscriber的对象o,并把代码块赋给了成员变量。然后o作为参数被调用[signal subscribe:o]

⚠️⚠️⚠️在[signal subscribe]中。发现o居然从RACSubscriber类变成了RACPassthroughSubscriber类,其有个成员变量_innerSubscriber保存着原本的RACSubscriber

然后,第一步中signal里保存的成员变量didSubscribe,在这会被执行 self.didSubscribe(subscriber)

  1. 发送信号

执行didSubscribe时,若块有这么一句[subscriber sendNext:@1]就会发送信号。

转到````self.innerSubscriber sendNext:value],其之前保存的块next``在这被调用。

最后再总结一下

  • 只要订阅RACDynamicSignal,就会执行didSubscribe
  • 只要订阅者调用sendNext,就会执行nextBlock

到这我们已经基本理清这错综复杂的顺序。


RACDisposable

上面didSubscribe返回类型为RACDisposable,现在开始研究这个类。

  • 作用:用于取消订阅或者清理资源。当信号发送完成或者发送错误的时候,就会触发它。

还是看回以上的代码

    // 1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        // block调用时刻:每当有订阅者订阅信号,就会调用block。
        
        NSLog(@"信号被订阅");
        // 2.发送信号
        [subscriber sendNext:@1];
        
        // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
        [subscriber sendCompleted];
 
        return [RACDisposable disposableWithBlock:^{
            
            // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
            
            // 执行完Block后,当前信号就不在被订阅了。
            
            NSLog(@"信号被取消订阅");
            
        }];
    }];
    
    // 3.订阅信号,才会激活信号.
    [signal subscribeNext:^(id x) {
        // block调用时刻:每当有信号发出数据,就会调用block.
        NSLog(@"接收到数据:%@",x);
    }];
复制代码

运行结果告诉我们,有输出"信号被取消订阅"。原因是默认一个信号发送完毕后,订阅者会主动取消订阅,即订阅者引用数-1。

那要怎么才能不自动取消呢?只要在控制器中声明一个属性,引用数+1。

@property (nonatomic, strong) id<RACSubscriber> subscriber;


RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        
        // block调用时刻:每当有订阅者订阅信号,就会调用block。
        
        _subscriber = subscriber;
        
        NSLog(@"信号被订阅");
        // 2.发送信号
        [subscriber sendNext:@1];
        
        // 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
        [subscriber sendCompleted];
 
        return [RACDisposable disposableWithBlock:^{
            
            // block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
            
            // 执行完Block后,当前信号就不在被订阅了。
            
            NSLog(@"信号被取消订阅");
            
        }];
    }];
复制代码

这时就要手动取消订阅,然后会调用“信号被取消订阅”的block。

    RACDisposable *disposable = [signal subscribeNext:^(id x) {
        // block调用时刻:每当有信号发出数据,就会调用block.
        NSLog(@"接收到数据:%@",x);
    }];
    
    [disposable dispose];
复制代码

看完不知道有什么用也没关系,笔者将在下一篇应用中再次提起。


RACSubject

  • 作用:自己可以充当信号,又能发送信号。
    // 1.创建信号
    RACSubject *subject = [RACSubject subject];

    // 2.订阅信号
    [subject subscribeNext:^(id x) {
        // block调用时刻:当信号发出新值,就会调用.
        NSLog(@"第一个订阅者%@",x);
    }];
    [subject subscribeNext:^(id x) {
        // block调用时刻:当信号发出新值,就会调用.
        NSLog(@"第二个订阅者%@",x);
    }];

    // 3.发送信号
    [subject sendNext:@"1"];
复制代码
  • 和RACSignal相同点

    1. 都是三步。
    2. 订阅者都是把next存起来。
  • 和RACSignal的区别

    1. RACSubject没有didSubscribe
    2. RACSubject对象中有一个数组,用来存订阅者。订阅者被创建后将被添加进该数组。
    3. RACSubject发送信号后,会遍历数组,执行next
  • 其还有个子类RACReplaySubject,区别是RACReplaySubject可以先发送后订阅,而RACSubject不能。

这么看来,感觉RACSubject更容易理解hhh。而且其能做到RACSignal不能做到的,想不懂为什么各资源都是先学RACSignal。有人能解释下为什么最常用类反而不是最轻松的呢?是性能问题吗?

  • 应用场景:通常用来代替代理

笔者偷懒,就复制别人的代码了。感觉看懂上面后,下面这段代码没什么难度。

// 需求:
    // 1.给当前控制器添加一个按钮,modal到另一个控制器界面
    // 2.另一个控制器view中有个按钮,点击按钮,通知当前控制器
    
步骤一:在第二个控制器.h,添加一个RACSubject代替代理。
@interface TwoViewController : UIViewController

@property (nonatomic, strong) RACSubject *delegateSignal;

@end

步骤二:监听第二个控制器按钮点击
@implementation TwoViewController
- (IBAction)notice:(id)sender {
    // 通知第一个控制器,告诉它,按钮被点了
    
     // 通知代理
     // 判断代理信号是否有值
    if (self.delegateSignal) {
        // 有值,才需要通知
        [self.delegateSignal sendNext:nil];
    }
}
@end

步骤三:在第一个控制器中,监听跳转按钮,给第二个控制器的代理信号赋值,并且监听.
@implementation OneViewController 
- (IBAction)btnClick:(id)sender {
    
    // 创建第二个控制器
    TwoViewController *twoVc = [[TwoViewController alloc] init];
    
    // 设置代理信号
    twoVc.delegateSignal = [RACSubject subject];
    
    // 订阅代理信号
    [twoVc.delegateSignal subscribeNext:^(id x) {
       
        NSLog(@"点击了通知按钮");
    }];
    
    // 跳转到第二个控制器
    [self presentViewController:twoVc animated:YES completion:nil];
    
}
@end

作者:袁峥
链接:https://www.jianshu.com/p/87ef6720a096
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
复制代码

留个任务给大家:为什么这里不用RACSignal

提示:RACSubject自己可以充当信号,又能发送信号。


RACTuple

  • 作用:元组类,相当于NSArray。
 // 元组
    RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@"1",@"2",@1]];
    NSString *str1 = tuple[0];
    NSString *str2 = tuple[1];
    NSNumber *number = tuple[2];
复制代码

RACSequence

  • 作用:用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典。开发中遇到NSArray和NSDictionary都用其代替。
    // 遍历数组
    NSArray *arr = @[@"213",@"321",@1];
    
    [arr.rac_sequence.signal subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    链式函数分开来如下
    // RAC集合
    //    RACSequence *sequence = arr.rac_sequence;
    //
    //    // 把集合转换成信号
    //    RACSignal *signal = sequence.signal;
    //
    //    // 订阅集合信号,内部会自动遍历所有的元素发出来
    //    [signal subscribeNext:^(id x) {
    //        NSLog(@"%@",x);
    //    }];

复制代码

遍历字典有一个强大的宏RACTupleUnpack

注意这里x的类型为RACTuple。笔者在这里直接把id x改成RACTuple *x

    // 遍历字典
    
    NSDictionary *dict = @{@"account":@"123",@"name":@"hsu",@"age":@18};
    
    // RAC集合
    [dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
        //       NSString *key = x[0];
        //        NSString *value = x[1];
        //        NSLog(@"%@ %@",key,value);
        
        // RACTupleUnpack:用来解析元组
        // 宏里面的参数,传需要解析出来的变量名
        // = 右边,放需要解析的元组
        RACTupleUnpack(NSString *key,NSString *value) = x;
        
        NSLog(@"%@ %@",key,value);
    }];

复制代码
    // 字典数组转模型
    
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
    NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
    
    // 方法一
//    NSMutableArray *arr = [NSMutableArray array];
//    [dictArr.rac_sequence.signal subscribeNext:^(NSDictionary *x) {
//        Flag *flag = [Flag flagWithDict:x];
//        [arr addObject:flag];
//    }];
    
    // 方法二
    // 高级用法
    // 会把集合中所有元素都映射成一个新的对象
   NSArray *arr = [[dictArr.rac_sequence map:^id(NSDictionary *value) {
        // value:集合中元素
        // id:返回对象就是映射的值
        return [Flag flagWithDict:value];
    }] array];

复制代码

RACMulticastConnection

  • 作用:不管信号被订阅多少次,只调用一次blockdidSubscribe

试想一下这种场景,如果didSubscribe中,请求了一个网络数据。每次订阅不想再去网络获取。

则要达到的效果是:只请求一次,但订阅者的blocknext都要调用。

要调用next,就要先调用blockdidSubscribedidSubscribe要在RACMulticastConnection对象调用方法- (void)connect;时调用。

    // 1.创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        // didSubscribe什么时候调用:连接类连接的时候
        // 网络请求
        NSData *data;
        [subscriber sendNext:data];
        
        return nil;
    }];
    
    // 2.把信号转换成连接类
    // 方法一
//    RACMulticastConnection *connection = [signal publish];
    // 方法二 用RACReplaySubject,就可以先发送后订阅
    RACMulticastConnection *connection = [signal multicast:[RACReplaySubject subject]];
    
    // 3.订阅连接类信号
    [connection.signal subscribeNext:^(id x) {
        NSLog(@"订阅者1:%@",x);
    }];
    
    [connection.signal subscribeNext:^(id x) {
        NSLog(@"订阅者2:%@",x);
    }];
    
    // 4.连接
    [connection connect];

复制代码

RACCommand

  • 作用:监听事件 。

注意blockdidSubscribe中,发送完成后调用了[subscriber sendCompleted]

// 方法一
    // RACCommand:处理事件
    // RACCommand:不能返回一个空的信号
    // 1.创建命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        // input:执行[command excute:]传入的参数
        // Block调用:执行[command excute:]的时候就会调用
        
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
            // 发送数据
            [subscriber sendNext:@"执行命令产生的数据"];
            
            // 发送完成
            [subscriber sendCompleted];
            
            return nil;
        }];
    }];
    
    // 2.执行命令
    RACSignal *signal = [command execute:@1];
    
    // 3.订阅信号
    [signal subscribeNext:^(id x) {
        NSLog(@"%@",x);// x = @"执行命令产生的数据"
    }];
复制代码

留意方法二中的command.executionSignals!!!方法三中也有用到。

// 方法二
    // 1.创建命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        // input:执行[command excute:]传入的参数
        // Block调用:执行[command excute:]的时候就会调用
        
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
            // 发送数据
            [subscriber sendNext:@"执行命令产生的数据"];
            
            // 发送完成
            [subscriber sendCompleted];
            
            return nil;
        }];
    }];
    
    // 订阅信号
    // 注意:必须要在执行命令前,订阅
    // executionSignals:信号源,信号中信号,signalOfSignals。信号:发送数据就是信号
    [command.executionSignals subscribeNext:^(RACSignal *x) {
    
            [x subscribeNext:^(id x) {
                NSLog(@"%@",x);
            }];
    
        }];
        
    // 2.执行命令
    [command execute:@1];
    
复制代码
// 方法三

    // 1.创建命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        // input:执行[command excute:]传入的参数
        // Block调用:执行[command excute:]的时候就会调用
        
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
            // 发送数据
            [subscriber sendNext:@"执行命令产生的数据"];
            
            // 发送完成
            [subscriber sendCompleted];
            
            return nil;
        }];
    }];
    
     // switchToLatest获取最新发送的信号,只能用于信号中信号
    [command.executionSignals.switchToLatest subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
    
    // 2.执行命令
    [command execute:@1];
复制代码

说了这么多创建执行方法,那RACCommand怎么做到监听事件呢?我们试想一下,当某个“确定按钮”点击时,网络不太好,这时候请求还未完成。那么再次点击时,期望不要发起另一次请求。这时就要用到````command.executing``,看事件的状态。

// 监听事件

    // 当前命令内部发送数据完成,一定要主动发送完成
    // 1.创建命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        // input:执行[command excute:]传入的参数
        // Block调用:执行[command excute:]的时候就会调用
        
        return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            
            // 发送数据
            [subscriber sendNext:@"执行命令产生的数据"];
            
            // 发送完成
            [subscriber sendCompleted];
            
            return nil;
        }];
    }];
    
    // 监听事件有没有完成
    [command.executing subscribeNext:^(id x) {
        if ([x boolValue] == YES) { // 当前正在执行
            NSLog(@"当前正在执行");
        }else{
            // 执行完成/没有执行
            NSLog(@"执行完成/没有执行");
        }
    }];
 
    
    // 2.执行命令
    [command execute:@1];
复制代码

RAC基本应用

虽然表面上没有出现上面提到的类,但是实质上有创建、订阅、发送信号。

  1. 代替代理

上面提到RACSubject能代替代理,现在再介绍多一种更简洁的方法,其缺点是不能传值。在testView中点击了按钮后,就会发送信号,执行blocknext

个人觉得,其实也就是监听着某个方法有没有被调用,不一定局限于代替代理。

//  ViewController.m

@interface ViewController ()
@property (weak, nonatomic) TestView *testView;
@end

@implementation ViewController

- (void)delegate
{
    // 只要传值,就必须使用RACSubject
    [[self.testView rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
        NSLog(@"控制器知道按钮被点击");
    }];

}

@end

复制代码
  1. 代替KVO

跟原生的KVO比起来,简直nice得不行。

用KVO监听多个值的话,要做一堆的判断。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) {
        
    }
    
    if ([keyPath isEqualToString:@"title"]) {
        
    }
}
复制代码

然而用RAC,因为函数式编程的原因,爽歪歪。同样的代码只要这样写。只要值改变就会发送信号,调用blocknext


    [[self.testView rac_valuesForKeyPath:@"name" observer:nil] subscribeNext:^(id x) {
      // x:修改的值
        NSLog(@"%@",x);
    }];
    
    [[self.testView rac_valuesForKeyPath:@"title" observer:nil] subscribeNext:^(id x) {
      // x:修改的值
        NSLog(@"%@",x);
    }];
复制代码
  1. 监听按钮点击事件
    // [_btn addTarget:self action:@selector(btnClick) forControlEvents:UIControlEventTouchUpInside];
    
    [[_btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
        NSLog(@"按钮点击了");
    }];
复制代码
  1. 代替通知

就拿原生的键盘弹出事件来说,也能看出函数式编程的简洁。如果用原生的,监听和调用的方法分隔开又不好管理。

    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
        
        NSLog(@"%@",x);
    }];

复制代码
  1. 监听文本框

只要文本框一输入文字,就会发送信号。该订阅者并不会取消订阅,所以每次输入都会调用blocknext

    [self.textField.rac_textSignal subscribeNext:^(id x) {
       
        NSLog(@"%@",x);
    }];
复制代码
  1. 综合信号结果

和GCD中的队列组相似,都是等多个任务完成后,最终执行某个任务。

用到的方法,需要注意的是

  • 数组里所有信号都发送过后才会调用Selector
  • selector的方法里,参数 依次对应着 数组信号的发送值。
- (RACSignal *)rac_liftSelector:(SEL)selector withSignalsFromArray:(NSArray *)signals;
复制代码

例如:从两个url加载图片,最终显示在某个view上。


- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    
    RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        // 网络请求 根据url下载图片1
        // 成功后 发送信号
        NSString *firstData;
        [subscriber sendNext:firstData];
        
        
        return nil;
    }];
    
    
    RACSignal *signal2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        
        // 网络请求 根据url下载图片2
        // 成功后 发送信号
        NSString *secondData;
        [subscriber sendNext:secondData];
        
        return nil;
    }];
    
    // 方法的参数 依次对应着 数组信号的发送值
    [self rac_liftSelector:@selector(updateWithFirstData:secondData:) withSignalsFromArray:@[signal1, signal2]];
    
}

- (void)updateWithFirstData:(NSString *)firstData secondData:(NSString *)secondData {
    // 根据firstData 和 secondData 作出调整
}


复制代码

RAC基本应用学起来应该不难,接下来再了解一下常用宏。


RAC常用宏

  1. RAC(<#TARGET, ...#>)

还记得上面第五点的监听文本框吗?文本框输入的值,显示到label上。

// 监听文本框内容
    //    [_textField.rac_textSignal subscribeNext:^(id x) {
    //
    //        _label.text = x;
    //    }];

复制代码

其实能替换成一个强大的宏。

    RAC(_label,text) = _textField.rac_textSignal;

复制代码
  1. RACObserve(<#TARGET#>, <#KEYPATH#>)

为了方便上面第二点代替KVO写法。

    [[self.testView rac_valuesForKeyPath:@"name" observer:nil] subscribeNext:^(id x) {
      // x:修改的值
        NSLog(@"%@",x);
    }];
复制代码

换成

    [RACObserve(self.testView, name) subscribeNext:^(id x) {
        NSLog(@"%@",x);
    }];
复制代码
  1. @weakify()@strongify()
  • 作用:方便防止block循环引用。
    __weak typeof(self) weakSelf = self;

    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    
         __strong typeof(weakSelf) strongSelf = weakSelf;
    
        NSLog("%@",strongSelf);
        reutrn nil;
    }
    _signal = signal;

复制代码

以上代码只需要换成两个方便的宏。

    @weakify(self);
    RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    
        @strongify(self);
    
        NSLog("%@",self);
        reutrn nil;
    }
    _signal = signal;
复制代码
  1. RACTuplePack(...)RACTupleUnpack(...)
  • 作用:包装成元组。解析元组。
    // 包装元组
    RACTuple *tuple = RACTuplePack(@"1",@"2");
    
    // 解析元组
    // 原来的做法
    // NSString *str1 = tuple[0];
    // NSString *str2 = tuple[1];
    // NSLog("%@ %@", str1, str2);
    
    // 利用宏
    // 宏里面的参数,传需要解析出来的变量名。
    RACTupleUnpack(NSString *str1, NSString *str2) = tuple;
    NSLog("%@ %@", str1, str2);// 此时已经有变量str1 和 str2!
复制代码

ReactiveCocoa的基础类就介绍到这,下一篇将进入开发场景。


参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值