ReactiveObjC学习笔记
来源:互联网 发布:阿里云 关闭快照功能 编辑:程序博客网 时间:2024/05/16 15:28
这篇文章的内容绝大部分翻译自github上的ReactiveObjC
ReactiveObjC
注意 : 这是ReactiveCocoa Objective-C的介绍,ReactiveCocoa的OC版本现在叫做ReactiveObjC了, ReactiveCocoa的升级版本是用Swift语言写的,想了解它的升级版本,请看ReactiveCocoa 或者 ReactiveSwift。
ReactiveObjC(ReactiveCocoa 或 RAC) 是一个基于函数响应式编程思想的Objective-C框架, 它提供了各种APIs,这些 APIs 可用于组合,转换数据流。
简介
ReactiveObjC(RAC)是一个函数响应式编程框架。RAC用信号(类名为RACSignal)来代替和处理各种变量的变化和传递。
通过信号signals的传输,重新组合和响应,软件代码的编写逻辑思路将变得更清晰紧凑,有条理,而不再需要对变量的变化不断的观察更新。
例如,UITextField的文本内容(text)刚起了变化,就要立即作出响应(UITextField还没失去焦点)更新动作,我们是不会看着手表来时刻观察更新textField,类似于采取KVO措施一样重写的-observeValueForKeyPath:ofObject:change:context:
,而是通过信号Signals的block实现这一动作。
信号Signals还能够代替实现异步操作,或者是并发处理问题。这大大的简化了异步操作(如网络)的代码。
RAC的主要好处是它提供了一个信号Signal,来统一处理Cocoa的各种行为,包括delegate-methods,block回调,target-action机制,通知和KVO等等。
这里是个简单的例子:
//当self.username 改变时,在控制台打印出新的username//宏定义RACObserve(self, username)会创建一个新的RACSignal信号,只要self.username的值有新变化,信号就会发送传递self.username新的值//当信号signal传送一个数据时,-subscribeNext:将执行block里的语句[RACObserve(self, username) subscribeNext:^(NSString *newName) { NSLog(@"%@", newName);}];
但与KVO通知模式不同,信号signals是可以被串联起来进行操作:
// 只打印以'j'开头的username.//当-filter的block返回YES时,-filter将会返回一个新的RACSigal信号,这个信号只传送username新的值[[RACObserve(self, username) filter:^(NSString *newName) { return [newName hasPrefix:@"j"]; }] subscribeNext:^(NSString *newName) { NSLog(@"%@", newName); }];
Signals 也能够用于派生出新的东西,RAC通过信号传送和信号操作,令属性重新赋值成为可能(不是通过传统的监听属性和为属性设置其它值来响应属性的变化):
// 创建一个单向绑定,当self.password与self.passwordConfirmation相等时,self.createEnabled将被赋值为true// RAC()是一个让绑定看起来更好的宏// +combineLatest:reduce:接收一个存有信号的数组,当数组中任一信号更新值时,block就会被执行并会以block的返回值作为信号的传递因子创建新的信号RACSignal并返回此信号RAC(self, createEnabled) = [RACSignal combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ] reduce:^(NSString *password, NSString *passwordConfirm) { return @([passwordConfirm isEqualToString:password]); }];
不仅是KVO可以,信号Signals也可以基于任何数据流而被创建。比方说,信号可以表示button按压事件
//只要button被按压了,就打印信息////RACCommand类会创建信号来表示UI控件的动作事件,比如说,信号可以表示button的一次按压事件或者一些与button相关的其它事件,当事件发生时,signalBlock将会被执行并返回////-rac_command 除了用在Button外,还可以用在其它UI控件。当button被按压时,button将向自己发送这个指令(command)self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) { NSLog(@"button was pressed!"); return [RACSignal empty];}];
或者是异步网络操作:
//点击'Log in'按钮,通过网络请求登录//当登录指令被执行开始登录操作时,这个block将被运行,这个block返回一个RACSignalself.loginCommand = [[RACCommand alloc] initWithSignalBlock:^(id sender) { //假设:-logIn 方法r 返回值是一个信号(这个信号能够发送一个数据当网络请求结束时) return [client logIn];}];//-executionSignals会返回一个信号,这个信号取自上面Signalblock的每次运行返回的值[self.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) { //登录成功后就打印 [loginSignal subscribeCompleted:^{ NSLog(@"Logged in successfully!"); }];}];//button被按压,将执行登录事件指令self.loginButton.rac_command = self.loginCommand;
Signals还可以表示时钟或其它UI事件,或者任何随时间发生改变的事件。
通过节节紧扣的链式编程和传送这些信号,让异步操作处理更多复杂的事件成为可能。在一系列动作(数据请求,验证,格式化等)完成后,紧接的动作会很容易地被触发。
//执行2个网络任务并在控制台打印信息,当2个网络任务都完成时。//+merge类方法持有一个信号数组并返回一个新的RACSignal信号。当数组中所有信号都完成时,这个新信号会传递数组中所有信号包裹的数据。//当新信号结束传递时,-subscribeCompleted将执行block[[RACSignal merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]] subscribeCompleted:^{ NSLog(@"They're both done!"); }];
Signals 可以不通过嵌套用作回调的block来顺序地执行异步操作,这与同步处理有相似之处
//用户进行登录,加载缓存信息,然后从服务器拉取余下的信息。等这些动作完成后,在控制台打印信息//假设-logInUser 方法在登录完成后返回一个signal//当信号发送一个数据时,-flattenMap:的block将被执行,并在block执行后-flattenMap:返回一个新的信号RACSignal,这个信号将block返回的全部signal都并入进来[[[[client logInUser] flattenMap:^(User *user) { //返回一个信号,这个信号包裹用户加载缓存的信息 return [client loadCachedMessagesForUser:user]; }] flattenMap:^(NSArray *messages) { //返回一个信号,这个信号包裹从网络上拉取剩余的信号 return [client fetchMessagesAfterMessage:messages.lastObject]; }] subscribeNext:^(NSArray *newMessages) { NSLog(@"New messages: %@", newMessages); } completed:^{ NSLog(@"Fetched all messages."); }];
甚至,RAC可以很容易地绑定异步操作的结果:
//创建一个单向绑定,目的是:只要图片下载完成就把它设成用户的头像图片//假设:--fetchUserWithUsername:方法返回一个信号,这个信号用于传送user对象//-deliverOn:会创建新的信号,这些信号将工作在后台队列。在这个例子中,deliverOn方法将任务搬到后台队列去工作,然后返回到主线程//-map:传参user给block并调用它,-map:执行结后会返回一个新的RACSignal,这个信号传送的数据是来自block的返回值RAC(self.imageView, image) = [[[[client fetchUserWithUsername:@"joshaber"] deliverOn:[RACScheduler scheduler]] map:^(User *user) { //下载图像数据(这个任务在后台队列执行) return [[NSImage alloc] initWithContentsOfURL:user.avatarURL]; }] // 此时,任务运行在主线程 deliverOn:RACScheduler.mainThreadScheduler];
想看看真正使用RAC创建的项目?请check out C-41 或 GroceryList, 它们都是用ReactiveObjC链式响应编程框架写的iOS app。
当你要使用ReactiveObjc时
粗略地回头看看上面的说明,ReactiveObjC 真的是挺抽象的,而且它让人感觉会很困难,如果把它应用在具体的问题上。
处理异步任务或事件驱动数据更新
在Cocoa框架下编程,大多数都会关注如何响应用户交互或者改变app的状态(更新页面数据或其它),这让处理这些事件的代码很快变得像意大利一样,乱成一团并且很复杂,因为这些代码会用到大量的回调和状态变量去handle问题。
RAC的编程模式在表面上看,跟UI callbacks,网络请求响应和KVO通知很不一样,实际上它们是极大地相同的。RACSignal信号只不过把它们不同的APIs统一了起来,目的是让他们变得可组合且能够以相同的方式来操作。
例如,下面的代码:
static void *ObservationContext = &ObservationContext;- (void)viewDidLoad { [super viewDidLoad]; [LoginManager.sharedManager addObserver:self forKeyPath:@"loggingIn" options:NSKeyValueObservingOptionInitial context:&ObservationContext]; [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(loggedOut:) name:UserDidLogOutNotification object:LoginManager.sharedManager]; [self.usernameTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged]; [self.passwordTextField addTarget:self action:@selector(updateLogInButton) forControlEvents:UIControlEventEditingChanged]; [self.logInButton addTarget:self action:@selector(logInPressed:) forControlEvents:UIControlEventTouchUpInside];}- (void)dealloc { [LoginManager.sharedManager removeObserver:self forKeyPath:@"loggingIn" context:ObservationContext]; [NSNotificationCenter.defaultCenter removeObserver:self];}- (void)updateLogInButton { BOOL textFieldsNonEmpty = self.usernameTextField.text.length > 0 && self.passwordTextField.text.length > 0; BOOL readyToLogIn = !LoginManager.sharedManager.isLoggingIn && !self.loggedIn; self.logInButton.enabled = textFieldsNonEmpty && readyToLogIn;}- (IBAction)logInPressed:(UIButton *)sender { [[LoginManager sharedManager] logInWithUsername:self.usernameTextField.text password:self.passwordTextField.text success:^{ self.loggedIn = YES; } failure:^(NSError *error) { [self presentError:error]; }];}- (void)loggedOut:(NSNotification *)notification { self.loggedIn = NO;}- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == ObservationContext) { [self updateLogInButton]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; }}
…可以用RAC写成:
- (void)viewDidLoad { [super viewDidLoad]; @weakify(self); RAC(self.logInButton, enabled) = [RACSignal combineLatest:@[ self.usernameTextField.rac_textSignal, self.passwordTextField.rac_textSignal, RACObserve(LoginManager.sharedManager, loggingIn), RACObserve(self, loggedIn) ] reduce:^(NSString *username, NSString *password, NSNumber *loggingIn, NSNumber *loggedIn) { return @(username.length > 0 && password.length > 0 && !loggingIn.boolValue && !loggedIn.boolValue); }]; [[self.logInButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(UIButton *sender) { @strongify(self); RACSignal *loginSignal = [LoginManager.sharedManager logInWithUsername:self.usernameTextField.text password:self.passwordTextField.text]; [loginSignal subscribeError:^(NSError *error) { @strongify(self); [self presentError:error]; } completed:^{ @strongify(self); self.loggedIn = YES; }]; }]; RAC(self, loggedIn) = [[NSNotificationCenter.defaultCenter rac_addObserverForName:UserDidLogOutNotification object:nil] mapReplace:@NO];}
串联相关的操作(下一任务需要上一任务的执行结果)
在网络请求中,相互关联的事件是非常普遍的,下一请求需要在上一请求完成后才能被发起,像如下:
[client logInWithSuccess:^{ [client loadCachedMessagesWithSuccess:^(NSArray *messages) { [client fetchMessagesAfterMessage:messages.lastObject success:^(NSArray *nextMessages) { NSLog(@"Fetched all messages."); } failure:^(NSError *error) { [self presentError:error]; }]; } failure:^(NSError *error) { [self presentError:error]; }];} failure:^(NSError *error) { [self presentError:error];}];
ReactiveObjC让这种模式异常简洁:
[[[[client logIn] then:^{ return [client loadCachedMessages]; }] flattenMap:^(NSArray *messages) { return [client fetchMessagesAfterMessage:messages.lastObject]; }] subscribeError:^(NSError *error) { [self presentError:error]; } completed:^{ NSLog(@"Fetched all messages."); }];
平行不相关的任务(并发任务,各自执行各自的)
把几个并发任务的执行结果(数据)归并成最终的结果,这在Cocoa编程中是非常重要的,而且通常牵扯到很多同步问题:
__block NSArray *databaseObjects;__block NSArray *fileContents;NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];NSBlockOperation *databaseOperation = [NSBlockOperation blockOperationWithBlock:^{ databaseObjects = [databaseClient fetchObjectsMatchingPredicate:predicate];}];NSBlockOperation *filesOperation = [NSBlockOperation blockOperationWithBlock:^{ NSMutableArray *filesInProgress = [NSMutableArray array]; for (NSString *path in files) { [filesInProgress addObject:[NSData dataWithContentsOfFile:path]]; } fileContents = [filesInProgress copy];}];NSBlockOperation *finishOperation = [NSBlockOperation blockOperationWithBlock:^{ [self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents]; NSLog(@"Done processing");}];[finishOperation addDependency:databaseOperation];[finishOperation addDependency:filesOperation];[backgroundQueue addOperation:databaseOperation];[backgroundQueue addOperation:filesOperation];[backgroundQueue addOperation:finishOperation];
上面的代码可以通过简单地组合signal信号来清减和优化:
RACSignal *databaseSignal = [[databaseClient fetchObjectsMatchingPredicate:predicate] subscribeOn:[RACScheduler scheduler]];RACSignal *fileSignal = [RACSignal startEagerlyWithScheduler:[RACScheduler scheduler] block:^(id<RACSubscriber> subscriber) { NSMutableArray *filesInProgress = [NSMutableArray array]; for (NSString *path in files) { [filesInProgress addObject:[NSData dataWithContentsOfFile:path]]; } [subscriber sendNext:[filesInProgress copy]]; [subscriber sendCompleted];}];[[RACSignal combineLatest:@[ databaseSignal, fileSignal ] reduce:^ id (NSArray *databaseObjects, NSArray *fileContents) { [self finishProcessingDatabaseObjects:databaseObjects fileContents:fileContents]; return nil; }] subscribeCompleted:^{ NSLog(@"Done processing"); }];
简化集合类的转换
高阶函数如 map
,filter
, fold
/reduce
在Foundation框架里是非常缺少的,这导致了代码要循环遍历,像这样:
NSMutableArray *results = [NSMutableArray array];for (NSString *str in strings) { if (str.length < 2) { continue; } NSString *newString = [str stringByAppendingString:@"foobar"]; [results addObject:newString];}
然而,RACSequence序列能够让任何Cocoa集合类以统一和见文知义的方式来操作处理:
RACSequence *results = [[strings.rac_sequence filter:^ BOOL (NSString *str) { return str.length >= 2; }] map:^(NSString *str) { return [str stringByAppendingString:@"foobar"]; }];
系统配置
ReactiveObjC 支持 OS X 10.8+ 和 iOS 8.0+
导入ReactiveObjC
这样来将RAC添加到你的App:
使用CocoaPods来管理RAC
前提:已经安装了CocoaPods
1. 在Podfile
写上pod 'ReactiveObjC', '~>3.0.0'
2. 更新pod依赖:进入到工程目录下,在终端输入pod update --no-repo-update
然后回车
3. 导入#import “ReactiveObjC.h”
- ReactiveObjC学习笔记
- 函数响应式编程及ReactiveObjC学习笔记 (-)
- 函数响应式编程及ReactiveObjC学习笔记 (二)
- ReactiveObjC的使用
- ReactiveObjc的使用总结
- iOS-ReactiveObjC 的基本使用(二)
- iOS-ReactiveObjC 的高级使用(二)
- ReactiveObjC 响应函数式框架 简单实用
- 将ReactiveObjC整成动态库来调用
- 学习笔记?
- 学习笔记
- 学习笔记
- 学习笔记
- 学习笔记
- 学习笔记
- 学习笔记
- 学习笔记
- 学习笔记
- Calender打印日历
- 在Linux 操作系统下获得cpu的core的数量
- PDF文档转换成CAD图纸的常用方法
- 应届毕业生如何成为一名服务器端开发工程师(一)
- java循环分别实现将10进值整数和小数变成二进制数
- ReactiveObjC学习笔记
- 论如何画好一条虚线
- 1秒30000QPS,前后端设计思路
- 通过live555实现H264 RTSP直播
- [Java]Java Socket选项解析
- FastDFS实战(三)- 配置和运行
- Maven报错Please ensure you are using JDK 1.4 or above and not a JRE解决方法
- ios 视图center的一个坑
- JS_正则表达式