ReactiveCocoa 进阶学习

ReactiveCocoa 进阶学习

进入该文章地址

1.双向绑定

ReactiveCocoa 中的一对一的单向数据流 RACSignal 和一对多的单向数据流 RACMulticastConnection 。
现在我们来了解一下,一对一的双向数
据流 RACChannel 。

举个栗子:

双向绑定.png

上面图片每个UISlider改变对应的UITextField也跟着改变,反之UITextField改变对应的UISlider也跟着改变。这样两个空间就形成双向绑定。

上码:

//双向绑定 slider改变TF也改变  TF改变slider也跟着改变
-(RACSignal *) blindSlider:(UISlider *)slider textField:(UITextField *) textField{
    //TF的第一次改变马上触发合并信号 让初始化时就触发合并信号
    RACSignal *textSignal = [[textField rac_textSignal] take:1];
    
    RACChannelTerminal *sliderSignal = [slider rac_newValueChannelWithNilValue:nil];
    RACChannelTerminal *textFieldSignal = [textField rac_newTextChannel];
    [textFieldSignal subscribe:sliderSignal];
    [[sliderSignal map:^id(id value) {
        return [NSString stringWithFormat:@"%.02f",[value floatValue]];;
    }] subscribe:textFieldSignal];
    //合并两个信号  其中一个信号有改变都会触发该合并信号
    return [[[textFieldSignal merge:sliderSignal] merge:textFieldSignal] merge:textSignal];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RACSignal *redColorSignal = [self blindSlider:_redSlider textField:_redTF];
    RACSignal *blueColorSignal = [self blindSlider:_blueSlider textField:_blueTF];
    RACSignal *greenColorSignal = [self blindSlider:_greenSlider textField:_greenTF];
    //combineLatest 要三个信号都激活才响应;
    RAC(self.showColorView, backgroundColor) = [[RACSignal combineLatest:@[redColorSignal,blueColorSignal,greenColorSignal]] map:^id(RACTuple* value) {
        return [UIColor colorWithRed:[value[0] floatValue]/255.0 green:[value[2] floatValue]/255.0 blue:[value[1] floatValue]/255.0 alpha:1];
    }];
    
}

2.RAC做网络请求

//rac做网络请求
-(RACSignal *) signalFromUrl:(NSString *) urlStr{
    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
        NSURLSessionDataTask *data = [session dataTaskWithURL:[NSURL URLWithString:urlStr] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                [subscriber sendError:error];
            }else{
                NSError *e;
                NSDictionary *jsonDic = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&e];
                if (e) {
                    [subscriber sendError:e];
                }else{
                    [subscriber sendNext:jsonDic];
                    [subscriber sendCompleted];
                }
            }
        }];
        [data resume];
       return [RACDisposable disposableWithBlock:^{
           
       }];
    }];
}

使用该RACSignal请求方法

[[self signalFromUrl:@"http://www.orzer.club/test.json"] subscribeNext:^(id x) {
   NSLog(@"请求成功:%@",x);
} error:^(NSError *error) {
   NSLog(@"请求失败:%@",error.domain);
} completed:^{
   NSLog(@"请求完成");
}];

3.了解和使用RACCommand

RACCommand类用于表示事件的执行,一般来说是在UI上的某些动作来触发这些事件,比如点击一个按钮。RACCommand的实例能够决定是否可以被执行,这个特性能反应在UI上,而且它能确保在其不可用时不会被执行。通常,当一个命令可以执行时,会将它的属性allowsConcurrentExecution设置为它的默认值:NO,从而确保在这个命令已经正在执行的时候,不会同时再执行新的操作。命令执行的返回值是一个RACSignal,因此我们能对该返回值进行next:,completed或error:,这在下文会有所展示。

RACCommand的属性

`

  1. executionSignals:需要执行的block成功的时候返回的信号,他是在主线程执行的。
    1. executing:判断当前的block是否在执行,执行完之后会返回@(NO).
    2. enabled:当前命令是否enabled,默认是no,他也可以根据enableSignal来设置或者allowsConcurrentExecution设置为NO的时候(command已经开始执行)
    3. errors:执行command的时候获取的error都会通过这个信号发送
    4. allowsConcurrentExecution:是否允许并发执行command,默认是NO。
      `

使用RACCommand将ViewModel的RACCommand与按钮的rac_command绑定

-(void)setViewModel:(LMLoginViewModel *)viewModel{
    _viewModel = viewModel;
    self.button.rac_command = self.viewModel.loginCommand;
    //判断是否正在执行
    [self.button.rac_command.executing subscribeNext:^(id x) {
        if ([x boolValue]) {
            NSLog(@"View: login..");
        } else {
            NSLog(@"View: end logining");
        }
    }];
    
    //执行结果
    [self.button.rac_command.executionSignals.flatten subscribeNext:^(id x) {
        NSLog(@"View:result:%@",x);
    }];
    
    //错误处理
    [self.button.rac_command.errors subscribeNext:^(id x) {
        NSLog(@"error:%@",x);
    }];
}

在ViewModel对按钮的点击事件做处理

-(void) setupSign{
    //executionSignals是RACCommand的signal,每当command开始执行时next:,其参数是由command创建的signal,
    //1.开始执行的信号
    RACSignal *startedSignal = [self.loginCommand.executionSignals map:^id(RACSignal *subscribeSignal) {
        return  NSLocalizedString(@"VM: Sending request...", nil);
    } ];
    //2.执行完成的信号处理
    //materialize会将一个signal转换为RACEvent信号(将一个signal的next:complete和error:消息转换为RACEvent实例的next:的值)。
    RACSignal *completedSignal = [self.loginCommand.executionSignals flattenMap:^RACStream *(RACSignal *subscribeSigna) {
        return [[[subscribeSigna materialize] filter:^BOOL(RACEvent *event) {
            return event.eventType == RACEventTypeCompleted;
        }] map:^id(id value) {
            return NSLocalizedString(@"VM: Thanks", nil);
        }];
    }];
    //3.executionSignals属性,这个signals不会发送了error事件,而是由errors这个属性来发送的
    RACSignal*failedsignal = [[self.loginCommand.errors subscribeOn:[RACScheduler mainThreadScheduler]] map:^id(NSError *error) {
        return NSLocalizedString(@"VM: Error :(", nil);
    }];
    
    self.passSignal = [RACSignal merge:@[completedSignal]];
    //判断是否正在执行
    [self.loginCommand.executing subscribeNext:^(id x) {
        if ([x boolValue]) {
            NSLog(@"VM: login..");
        } else {
            NSLog(@"VM: end logining");
        }
    }];
    
    //执行结果
//    [self.loginCommand.executionSignals.flatten subscribeNext:^(id x) {
//        NSLog(@"VM:result:%@",x);
//    }];
}
-(RACCommand *)loginCommand{
    if (!_loginCommand) {
        _loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                [subscriber sendNext:@(1)];
                [subscriber sendCompleted];
                return nil;
            }];
        }];
    }
    return _loginCommand;
}

4.使用RACCommand在ViewModel中做网络请求

把self.requestCommand和网络请求绑定
self.requestCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
        RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
            NSString *url =[NSString stringWithFormat:@"%@/User/AccountManage?%@",API_GATEWAY,[KADDataTrack getTrackParamsWithType:nil]];
            
            [[KADHttpManage sharedManage] getJsonAsyn:url andParams:nil andSucc:^(id data) {
                @strongify(self)
                NSInteger code = [[data objectForKey:@"Code"] integerValue];
                if (code == 0) {//成功
//                    [self.dataArray removeAllObjects];
                    self.dataArray = [KADUserUnwrapModel objectArrayWithKeyValuesArray:[data objectForKey:@"Data"]];
                    
                    [subscriber sendNext:@(YES)];
                    [subscriber sendCompleted];
                }else if(code == 1){//请登录
                    NSString *msg = [data objectForKey:@"Message"];
                    NSError *err = [NSError errorWithDomain:NSCocoaErrorDomain code:code userInfo:@{NSLocalizedDescriptionKey:msg}];
                    [subscriber sendError:err];
                }
                
            } andFail:^(NSError *error) {
                //            KADError *err = [KADError initWithCode:error.code andMessage:error.localizedDescription];
                [subscriber sendError:error];
                
            }];
            return nil;
        }];
        return signal;
    }];

在ViewController中订阅该请求信号

-(void) getData{
    
    RACSignal *signal = [self.viewModel.requestCommand execute:nil];
    @weakify(self)
    [signal subscribeNext:^(id x) {
        @strongify(self)
        [self.tableView reloadData];
    }];
    
    [signal subscribeError:^(NSError *error) {
        @strongify(self)
        [self alertShow:error.localizedDescription];
    }];
}

5.使用信号做定位

autoriedSignal方法

//用rac定位 返回信号内容是有个布尔值:用户是否授权
-(RACSignal *) autoriedSignal{
    //判断是否授权
    if ([CLLocationManager authorizationStatus] == kCLAuthorizationStatusNotDetermined) {
        //没有授权就提示用户授权
        [self.manager requestWhenInUseAuthorization];
        //创建用户授权结果的信号
        return [[self rac_signalForSelector:@selector(locationManager:didChangeAuthorizationStatus:) fromProtocol:@protocol(CLLocationManagerDelegate)] map:^id(id value) {
            return @([value[1] integerValue] == kCLAuthorizationStatusAuthorizedWhenInUse );
        }];
    }
    return [RACSignal return:@([CLLocationManager authorizationStatus] == kCLAuthorizationStatusAuthorizedWhenInUse)];
}

setLocation授权判断和获取反地理编码

-(void)setLocation{
    //autoriedSignal判断是否授权
    [[[[self autoriedSignal] filter:^BOOL(id value) {
        //过滤 如果授权就往下
        return [value boolValue];
    }] flattenMap:^RACStream *(id value) {
        //授权过的 跟踪定位信息
        return [[[[[[[self rac_signalForSelector:@selector(locationManager:didUpdateLocations:) fromProtocol:@protocol(CLLocationManagerDelegate)] map:^id(id value) {
            //取代理方法的第二个值
            return value[1];
        }] merge:[[self rac_signalForSelector:@selector(locationManager:didFailWithError:) fromProtocol:@protocol(CLLocationManagerDelegate)] map:^id(id value) {
            //定位失败返回错误信息
            return [RACSignal error:value[1]];
        }]] take:1] initially:^{
            //信号开始前做什么
            [self.manager startUpdatingLocation];
        }] finally:^{
            //信号结束后做什么
            [self.manager stopUpdatingLocation];
        }] flattenMap:^RACStream *(id value) {
            CLLocation *location = [value firstObject];
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
                //反编码获取地理信息
                [self.coder reverseGeocodeLocation:location completionHandler:^(NSArray<CLPlacemark *> * _Nullable placemarks, NSError * _Nullable error) {
                    if (error) {
                        [subscriber sendError:error];
                    }else{
                        [subscriber sendNext:[placemarks firstObject]];
                        [subscriber sendCompleted];
                    }
                }];
               return [RACDisposable disposableWithBlock:^{
                   
               }];
            }];
        }];
    }] subscribeNext:^(id x) {
        self.placeLabel.text = [x addressDictionary][@"Name"];
    } error:^(NSError *error) {
        self.placeLabel.text = [NSString stringWithFormat:@"%@",error.userInfo];
    }] ;
    
}

在viewDidLoad直接使用[self setLocation];

6.使用RAC通知要注意事项

通知的坑:不释放通知,会导致每次进来会叠加 所以添加takeUntil方法 在VC释放时就不订阅该信号

@weakify(self)
[[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] map:^id(NSNotification *value) {
   return value.userInfo;
}] takeUntil:self.rac_willDeallocSignal]subscribeNext:^(NSDictionary *userInfo) {
   @strongify(self)
   double time = [[userInfo valueForKey:UIKeyboardAnimationDurationUserInfoKey] doubleValue];
   CGRect keyboardRect = [[userInfo valueForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
   [UIView animateWithDuration:time animations:^{
       [self.importBar mas_updateConstraints:^(MASConstraintMaker *make) {
           make.bottom.mas_equalTo(-keyboardRect.size.height);
       }];
       [self.view layoutIfNeeded];
   }];
}];

7.NSObject+RACLifting.h

有时我们希望满足一定条件时,自动触发某个方法,有了这个category就可以这么办

[self rac_liftSelector:@selector(doSomething) withSignals:signalA, signalB, nil];

8.RACSequence使用和注意

//初始化这个队列时流初始化时,内容就已经处理好
RACSequence *seq = [sigal sequence];
//RACSequence 与 RACSignal 可以互相转换
RACSignal *signal = [seq signal];

map不会整平RACSequence,但使用flattenMap是对信号处理会整平RACSequence

RACSequence *sequence1 = [@[@(1),@(2)] rac_sequence];
RACSequence *sequence2 = [@[@(3),@(4)] rac_sequence];
RACSequence *sequence3 = [@[sequence1,sequence2] rac_sequence];

NSArray *array = [[sequence3 flattenMap:^RACStream *(RACSequence *value) {
    return value;
}] array];
    
NSArray *mapArr = [[sequence3 map:^id(id value) {
   return value;
}] array];
    
NSLog(@"flattenMap-> %@",array);
NSLog(@"map -> %@",mapArr);

输出

 flattenMap-> (
    1,
    2,
    3,
    4
)
 map -> (
    "<RACArraySequence: 0x60800002fa60>{ name = , array = (\n    1,\n    2\n) }",
    "<RACArraySequence: 0x60800002faa0>{ name = , array = (\n    3,\n    4\n) }"
)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,923评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,154评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,775评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,960评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,976评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,972评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,893评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,709评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,159评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,400评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,552评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,265评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,876评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,528评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,701评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,552评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,451评论 2 352

推荐阅读更多精彩内容