ReactiveCocoa 进阶学习
进入该文章地址
1.双向绑定
ReactiveCocoa 中的一对一的单向数据流 RACSignal 和一对多的单向数据流 RACMulticastConnection 。
现在我们来了解一下,一对一的双向数
据流 RACChannel 。
举个栗子:
上面图片每个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 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的属性
`
- executionSignals:需要执行的block成功的时候返回的信号,他是在主线程执行的。
- executing:判断当前的block是否在执行,执行完之后会返回@(NO).
- enabled:当前命令是否enabled,默认是no,他也可以根据enableSignal来设置或者allowsConcurrentExecution设置为NO的时候(command已经开始执行)
- errors:执行command的时候获取的error都会通过这个信号发送
- 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 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 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 subscriber) {
//反编码获取地理信息
[self.coder reverseGeocodeLocation:location completionHandler:^(NSArray * _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 -> (
"{ name = , array = (\n 1,\n 2\n) }",
"{ name = , array = (\n 3,\n 4\n) }"
)