RAC常见用法(一)
上次体验了一把RAC, 今天, 再介绍一下RAC的简单用法:
首先看一下打算介绍的知识点:
知识点大纲然后, 就开始One by One了:
(一) RAC的集合:
- 在RAC中, 也有一个元祖类, 叫做
RACTuple
, 它完全可以当做OC的数组来用, 比如:
- (void)demo1 {
/* 元祖 */
RACTuple *tuple = [RACTuple tupleWithObjects:@"firstObject",@"secondObject",@1, nil];
NSString *string = tuple[1];
NSLog(@"%@", string);
}
你同样可以使用以下方法来创建一个元祖:
/// Creates a new tuple out of the array. Does not convert nulls to nils.
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array;
/// Creates a new tuple out of the array. If `convert` is YES, it also converts
/// every NSNull to RACTupleNil.
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert;
/// Creates a new tuple with the given objects. Use RACTupleNil to represent
/// nils.
+ (instancetype)tupleWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;
元祖完全可以当做数组来使用, 你也可以通过下标来取出元祖中的数据.
- 在RAC中, 还有一个非常重要的集合类, 那就是
RACSequence
, 且看:
- (void)demo2 {
NSArray *array = @[@"jack", @"rose", @"james"];
[array.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"%@", [NSThread currentThread]);
NSLog(@"%@", x);
}];
}
遍历一个数组, 打印的结果是:
数组遍历打印结果
可以看到的是: 数组中的元素被一一打印了, 值得注意的是: 这个打印的过程是在子线程调用的, 因为:
+ (RACScheduler *)schedulerWithPriority:(RACSchedulerPriority)priority name:(NSString *)name {
return [[RACTargetQueueScheduler alloc] initWithName:name targetQueue:dispatch_get_global_queue(priority, 0)];
}
在这个方法的调用中, 使用了全局并发队列.
同样的, 还有字典的遍历:
- (void)demo3 {
NSDictionary *dict = @{
@"name" : @"jack",
@"age" : @18,
};
[dict.rac_sequence.signal subscribeNext:^(RACTuple * _Nullable x) {
NSLog(@"%@", x);
}];
}
可以看到打印结果是打印了两个元祖类型的对象:
打印了两个元祖类型的对象那么, 如何将这个元祖类型的对象转化成字典的键值对一一输出呢? 这里就需要用到一个功能强大的宏, 在RAC中, 有很多很强大的宏, 这个宏是这样的:
RACTupleUnpack(...)
我们将需要解析的元祖赋值给这个宏, 同时, 将要解析的key值和value值做为宏的参数, 如下面的代码所示, 我们就能拿到字典的键值对了:
- (void)demo3 {
NSDictionary *dict = @{
@"name" : @"jack",
@"age" : @18,
};
[dict.rac_sequence.signal subscribeNext:^(RACTuple * _Nullable x) {
RACTupleUnpack(NSString *key, NSString *value) = x;
NSLog(@"%@----%@",key,value);
}];
}
- 还有一点需要单独拿出来讲的就是利用RAC进行字典转模型. RAC提供了一个方法, 可以直接将字典数组映射成模型数组:
- (void)demo4 {
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:@"http://mockhttp.cn/mock/tsaievantest"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSArray *array = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:NULL];
NSArray *infoArr = [[array.rac_sequence.signal map:^id _Nullable(NSDictionary *value) {
return [KFCFood kfcFoodWithDictionary:value];
}] toArray];
NSLog(@"%@", infoArr);
}] resume];
}
当我们拿到服务器返回的响应体数据时, 利用json反序列化得到一个字典数组, 然后将数组转化成RACSequence
对象,利用其signal
属性进行映射, 再调用toArray
方法就可以拿到装满模型对象的数组了:
(二) RAC基本用法:
- 代替代理:
假设有如上的需求, 我们一般使用代理来完成. 那么现在不用了, RAC很容易就能解决这个问题:
#import "YFSmallView.h"
@implementation YFSmallView
- (IBAction)greenButtonDidClick:(UIButton *)sender {
NSLog(@"绿色按钮被点击了");
}
@end
我在蓝色View内部定义一个处理点击事件的方法, 然后在控制器中, 响应这个方法产生一个信号, 然后订阅信号, 打印结果是一个元祖:
- (void)demo1 {
SEL sel = NSSelectorFromString(@"greenButtonDidClick:");
[[_blueView rac_signalForSelector:sel] subscribeNext:^(id _Nullable x) {
NSLog(@"%@" ,x);
}];
}
代替代理
我们可以看到的是:元祖里面保存的是响应的方法的参数, 那么我们可以用解包的方法, 将参数提取出来:
- (void)demo1 {
SEL sel = NSSelectorFromString(@"greenButtonDidClick:");
[[_blueView rac_signalForSelector:sel] subscribeNext:^(id _Nullable x) {
RACTupleUnpack(UIButton *sender) = x;
self.view.backgroundColor = sender.backgroundColor;
}];
}
通过这个, 我们能够拿到参数sender
, 将sender
的背景色赋值给控制器view
:
- 代替KVO:
/****************** -------- 代替KVO -------- ******************/
- (void)demo2 {
[_blueView rac_observeKeyPath:@"frame" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
NSLog(@"%@----%@", value, change);
}];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
static int x = 50;
x++;
_blueView.frame = CGRectMake(x, 50, 100, 100);
}
每点击屏幕就改变_blueView
的值, 这时候, 就能够监听的到, 并且在block中进行逻辑处理, 参数value
就是当前的keyPath属性对应的值, 参数change
是一个字典,
不过, 有更方便的监听方法:
[[_blueView rac_valuesForKeyPath:@"frame" observer:self] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
此时, 打印的x
的值是当前属性的值.
- 监听事件:
以前, 我们监听按钮的点击事件, 我们得这么写代码:
- (void)demo3 {
[_blueButton addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonClick:(UIButton *)sender {
NSLog(@"点击了蓝色按钮+++%@", sender);
}
这样的不好的地方是, 业务逻辑被分割到了另外的地方,显得不统一, 而使用RAC的方法, 我们将逻辑直接写在block代码块里面, 这样可读性更强,使用更方便:
/****************** -------- 监听事件 -------- ******************/
- (void)demo3 {
[[_blueButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"点击了蓝色按钮---%@", x);
}];
}
- 代替通知:
/****************** -------- 代替通知 -------- ******************/
- (void)demo4 {
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
NSLog(@"%@", x);
}];
}
block中打印的就是通知本身:
通知信息- 监听文本框
/****************** -------- 监听文本框 -------- ******************/
- (void)demo5 {
[self.textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
_textLabel.text = x;
}];
}
实现效果:
监听文本框实现效果(三) 利用RAC定时器创建制作一个发送验证码的小demo
发送验证码是很常见的需求, 点击发送之后, 开始60秒倒计时, 此时按钮不能点击, 且文字变成灰色, 等到60秒时间到后, 便可以重新发送.一般情况下, 我们会用NSTimer, 但RAC为我们提供了更好的定时器方法:
+ (RACSignal *)interval:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler;
需要注意的是, 我们必须在主线程更新UI, 所以scheduler
必须是主线程的scheduler, 使用[RACScheduler mainThreadScheduler]
这个单例对象作为参数. 主要的代码如下:
self.disposable = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
_time--;
if (_time > 0) {
_sendLabel.text = [NSString stringWithFormat:@"已发送,请等待%@秒", @(_time)];
_sendLabel.textColor = [UIColor lightGrayColor];
[_sendLabel removeGestureRecognizer:tap];
}else {
_sendLabel.text = @"重新发送";
_sendLabel.textColor = [UIColor whiteColor];
[_sendLabel addGestureRecognizer:tap];
[_disposable dispose];
_time = 10;
}
}];
实现效果:
发送验证码实现效果我把涉及到的代码放到下面供大家参考: