这篇文章主要通过举例说明Reactive Cocoa的使用方法,所举的例子都比较典型和实用,在实际的项目中都会有所涉及,也希望大家可以举一反三。
一.输入框输入11位合法手机号,获取验证码按钮才可用,号码不合法,按钮不可用,点击按钮,倒计时60s后,才可以再次可用,在等待期间,无论输入框输入的是否再次合法,获取验证码按钮都是不可用的。
RACSignal *validPhone = [self.phoneTextField.rac_textSignal map:^id(NSString *text) {
return @([RegFun checkPhoneLegal:text]);
}];
self.sendCodeBtn.rac_command = [[RACCommand alloc] initWithEnabled: validPhone signalBlock:^RACSignal *(id input) {
return [self sendCodeSingal];
}]; // validPhone控制点击的block是否可以执行,同时也控制了按钮的状态,当点击后,只要block返回的signal还没有sendCompleted,这时候你无论怎么输入字符还是做什么操作,按钮都是不可用的,这样就解决了,正在请求接口,再输入字符,按钮又变成可用的问题。
-(RACSignal *)sendCodeSingal{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[AuthorizeURL sharedInstance] startWithM:URL_M_User andWithA:@"sendSmsCode" andOtherDic:dic andIsNeedWaitingView:YES andIsNeedPopMessageWhenSuccess:YES andIsNeedCallBlockWhenNoNetwork:YES andSuccessBlock:^(id responseObj, BOOL isSuccess) {
if (isSuccess) {
self.leftTime = 60;
RACSignal *sendCodeEnableSignal = [[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]] take:self.leftTime]; //每1s执行一次
[sendCodeEnableSignal subscribeNext:^(id x) {
self.leftTime --;
[self.sendCodeBtn setTitle:[NSString stringWithFormat:@"等待(%d秒)",self.leftTime] forState:UIControlStateDisabled];
if (self.leftTime == 0) { //时间为0时,才发送信号,让按钮可用。
[self.sendCodeBtn setTitle:@"发送验证码" forState:UIControlStateDisabled]; //字符归位,不然下次就会显示等待0.
[subscriber sendNext:@(isSuccess)];
[subscriber sendCompleted];
}
}];
}else{
[subscriber sendNext:@(isSuccess)];
[subscriber sendCompleted];
}
}];
return nil;
}];
}
二.通过代码直接textView.text = @”this is a example”,也要达到rac_textSignal一样的信号效果。
//直接给值的话,rac_textSignal是不调用的,必须通过观察,然后,输入的时候,观察是不调用的,两者合并,有一者触发即可。
RACSignal *validUserName = [[RACSignal merge:@[self.userNameTextField.rac_textSignal, RACObserve(self.userNameTextField, text)]] map:^id(NSString *text) {
return @(text.length > 0);
}];
三.非常解耦的控制底部tabbar小红点和各个子小红点的显示和隐藏。
RACSignal *myMessageSignal = RACObserve(self, myMessageCircleNum);
RACSignal *groupMessageSignal = RACObserve(self, groupMessageNum);
RACSignal *mySysMessageSignal = RACObserve(self, mySysMessageCircleNum);
//这三个数值控制三个子小红点的显示隐藏,监听他们值的改变,有改变,就发通知,去刷页面,去控制子小红点的显示和隐藏。
[groupMessageSignal subscribeNext:^(NSNumber *x) {
[[NSNotificationCenter defaultCenter] postNotificationName:KNOTIFICATION_CircleType object:@(8)]; //发送通知,其他页面只要有监听,就可以刷新页面,和控制子其对应的子小红点的显示和隐藏。
[self saveToLocalWithType:8]; //存本地
}];
RACSignal *helloCircleSignal = [RACSignal combineLatest:@[myMessageSignal,mySysMessageSignal,groupMessageSignal] reduce:^id(NSNumber *myMessage,NSNumber *mySysMessage,NSNumber *groupMessage){
return @(myMessage.intValue == 0 && mySysMessage.intValue == 0 && groupMessage.intValue == 0); //当三者都为0,证明他们对应的子小红点都隐藏了,那么底部的小红点也才消失。
}];
[helloCircleSignal subscribeNext:^(NSNumber *x) {
XAppDelegate.homeVC.helloVCRedCircle.hidden = x.boolValue;
}]; //三者只要有一者的值改变,就会触发这个合并的信号,就可以刷新底部小红点的显示。
四.监听登陆状态的改变,从登陆到登出,从登出到登陆,状态的改变需要刷新页面和处理数据
//在登陆和登出的地方,会发出对应的通知,各个页面只要监听即可。
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:KNOTIFICATION_USERLOGINCHANGE object:nil] subscribeNext:^(NSNotification *notification) {
BOOL isinLogin = [notification.object boolValue];
if (isinLogin) { //从未登录到登录。
[self handleWhenLoginIn];
}else{ //从登录到未登录。
[self handleWhenLogout];
}
}];
五.替代各种delegate,让代码更集中,更易读
UIActionSheet* sheet = [[UIActionSheet alloc]initWithTitle:nil delegate:nil cancelButtonTitle:@"取消" destructiveButtonTitle:nil otherButtonTitles:@"拍照",@"从相册中选取", nil];
[[sheet rac_buttonClickedSignal] subscribeNext:^(NSNumber* number) { // UIActionSheet的delegate
UIImagePickerController* controller = nil;
int num = number.intValue;
if(num == 0) { //拍照
controller = [CameraAndPhoto getCameraPickerControllerAndIsFront:YES];
}
if(num == 1) { //相册
controller = [CameraAndPhoto getPhotoLibarayPickerController];
}
if ((num == 0 || num == 1) && controller) {
[self presentViewController:controller animated:YES completion:nil];
[[controller rac_imageSelectedSignal] subscribeNext:^(NSDictionary *info) {
// UIImagePickerController点击确定后调用
[controller dismissViewControllerAnimated:YES completion:^{
UIImage *portraitImg = [info objectForKey:@"UIImagePickerControllerOriginalImage"];
VPImageCropperViewController *imgCropperVC = [[VPImageCropperViewController alloc] initWithImage:portraitImg cropFrame:CGRectMake(0, (kScreen_Height - kScreen_Width)/2, kScreen_Width,kScreen_Width) limitScaleRatio:4.0 andIsNeedCircle:YES];
imgCropperVC.delegate = self; //delegate还是要赋值的。
[[self rac_signalForSelector:@selector(imageCropper:didFinished:) fromProtocol:@protocol(VPImageCropperDelegate)] subscribeNext:^(RACTuple *tuple) { //图片裁切VC的delegate,本来是要散落在self页面,现在集成到这里,图片裁切确定后的回调
[imgCropperVC dismissViewControllerAnimated:YES completion:^{
UIImage *editedImage = tuple.second;
}];
}];
[[self rac_signalForSelector:@selector(imageCropperDidCancel:) fromProtocol:@protocol(VPImageCropperDelegate)] subscribeNext:^(RACTuple *tuple){ //图片裁切VC的delegate,图片裁切取消后的回调
[imgCropperVC dismissViewControllerAnimated:YES completion:^{
}];
}];
[self presentViewController:imgCropperVC animated:YES completion:nil];
}];
} completed:^{ // UIImagePickerController点击取消后调用
[controller dismissViewControllerAnimated:YES completion:^(){ //相当于cancel
}];
}];
}
}];
[sheet showInView:self.view];
六.信号混合使用,RACSubject的使用,将非RAC带入RAC。
self.textSingal = [RACSubject subject]; //先声明
self.publishBtn.rac_command = [[RACCommand alloc] initWithEnabled:self.textSingal signalBlock:^RACSignal *(id input) {
if (self.imageHasUploadToUpYun) {
return [self tellServerSignalWith:nil]; //图片已经上传成功了,如果告诉我们的服务器失败了,第二次点击按钮的时候,不用重新上传图片,直接将地址告诉我们的服务器。
}else{
return [self submitSignal]; //开始上传图片
}
}];
[self.textSingal sendNext:[self isValid]]; //[self isValid]函数返回的NSNumber的值就是通过非RAC的普通代码计算得来,来控制publishBtn的enable状态,这句话可以放在其他需要控制按钮状态的地方,比如选择图片后,调用一下,初始化的使用调用一下。
//代码的例子是先把图片上传到云服务器,上传成功后,再将取得的图片地址告诉自己的服务器。
-(RACSignal *)submitSignal{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
self.uploadImageManager = [[UploadImageManager alloc] initWithMultiImageArray:self.multiPhotoVCManager.lastUploadImageArray andNeedWait:YES andMultiPhotoVCManager:self.multiPhotoVCManager andSuccessBlock:^(BOOL isAllComplete, NSArray *imageSrcArray, NSArray *hasSuccessObjArray, NSArray *hasFailedObjArray) {
if (imageSrcArray.count > 0) { //开始告诉我们自己的服务器。
self.imageHasUploadToUpYun = YES; //这个时候,已经到云了。除非再动图片了,否则如果接下去告诉我们自己的服务器失败后,也不用再重新上传图片。
RACSignal *temp = [self tellServerSignalWith:subscriber]; //此时信号为冷的,将上传到云的信号的subscriber传递到告诉服务器的函数,这样才能在告诉服务器的信号完成后,也让上传到云的信号能够完成,形成回路。
[temp subscribeNext:^(id x) { //调用一下,激活告诉服务器的信号。
}];
}else{ //上传完成了,一张都没有成功。
[subscriber sendNext:@(0)];
[subscriber sendCompleted];
}
return nil;
}];
}
//将图片地址告诉我们自己的服务器
-(RACSignal *)tellServerSignalWith:(id<RACSubscriber>)subscriber1{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[[AuthorizeURL sharedInstance] startWithM:URL_M_Daily andWithA:@"add" andOtherDic:dict andIsNeedWaitingView:YES andIsNeedPopMessageWhenSuccess:YES andIsNeedCallBlockWhenNoNetwork:YES andSuccessBlock:^(id responseObj, BOOL isSuccess) {
[subscriber sendNext:@(isSuccess)]; //让告诉我们服务器图片地址的信号结束
[subscriber sendCompleted];
[subscriber1 sendNext:@(isSuccess)]; //让云服务器的信号结束
[subscriber1 sendCompleted];
}];
return nil;
}];
}