ReactiveObjC
本文内容来自github ReactiveObjC的官方文档
介绍
ReactiveObjC 是一个函数相应式编程的OC框架,为我们提供了组合、转换流的api
RAC没有使用可修改或可替换的变量,而是提供了信号(RACSignal
)来捕获当前或者未来的信号,我们通过串联、绑定、响应信号,就可以无需持续的观察和更新值来检测变化
例如: 为了检测一个textField的text的最新内容,我们不需要每秒钟就去获取其值的变化,使用RAC更像KVO可以实时获取到其内容变化,与KVO不同的时 我们使用block
而不是使用-observeValueForKeyPath:ofObject:change:context:
信号也可以帮助我们进行异步操作,就像对未来结果的许诺,极大的帮助我们简化网络请求等异步操作代码
RAC主要提供了一个统一的方法 来处理异步行为,包括delegate、kvo、target-action、notification、block
监测属性值变化
类似kvo //检测self.username属性变化
//RACObserve(TARGET, KEYPATH)是一个创建检测值变化的信号
//subscribeNext 当检测的信号变化时 就会执行这个信号
[RACObserve(self, username) subscribeNext:^(NSString *newName) {
NSLog(@"%@", newName);
}];监测过滤出来的部分信号值
与kvo不同的是,我们可以串联、操作这些信号//过滤 信号中以"j"开头的信号值的变化 [[RACObserve(self, username)
filter:^(NSString *newName) {
return [newName hasPrefix:@"j"];
}]
subscribeNext:^(NSString *newName) {
NSLog(@"%@", newName);
}];通过RAC我们可以很方便的通过信号监测变化,而不需要通过观察和设置其它属性
//combineLatest: reduce:方法 合并一组信号,当其中任意一个信号的最新值变化时,会执行该block 并返回新的值 RAC(self, createEnabled) = [RACSignal
combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ]
reduce:^(NSString *password, NSString *passwordConfirm) {
return @([passwordConfirm isEqualToString:password]);
}];信号不止类似kvo可以监测属性,还可以监测其它任何随时间变化的值或者流
//RACCommand 创建一个与UI有关的信号 此处是按钮点击信号,当按钮被点击时执行该block //rac_command是按钮类别的属性,当按钮被点击自动发送
self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
NSLog(@"button was pressed!");
return [RACSignal empty];
}];RAC表示网络操作
//创建一个信号 当self.loginCommand执行其block self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^(id sender) {
return [client logIn];
}];
//executionSignals 返回上面RACComond中block返回的信号的信号,即当上面block返回时 会触发该信号 并可以监测block返回的信号的变化
[self.loginCommand.executionSignals subscribeNext:^(RACSignal *loginSignal) {
// Log a message whenever we log in successfully.
[loginSignal subscribeCompleted:^{
NSLog(@"Logged in successfully!");
}];
}];
//当按钮点击时 执行该self.loginCommand
self.loginButton.rac_command = self.loginCommand;表示异步操作的信号间可以链接来表示更加复杂的行为(在一组操作完成后开始另外一个操作)
//merge: 合并两个信号 返回一个新的信号 //subscribeCompleted: 订阅返回完成的信号
[[RACSignal merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]]
subscribeCompleted:^{
NSLog(@"They're both done!");
}];信号可以串联起来 避免使用block嵌套来表示依赖
// [[[[client
logInUser]
flattenMap:^(User *user) {
// Return a signal that loads cached messages for the user.
return [client loadCachedMessagesForUser:user];
}]
flattenMap:^(NSArray *messages) {
// Return a signal that fetches any remaining messages.
return [client fetchMessagesAfterMessage:messages.lastObject];
}]
subscribeNext:^(NSArray *newMessages) {
NSLog(@"New messages: %@", newMessages);
} completed:^{
NSLog(@"Fetched all messages.");
}];获取异步操作的结果
//当异步图片下载完成后赋值到图片上 //deliverOn: 创建信号并执行在其他队列线程 [RACScheduler scheduler] background线程 RACScheduler.mainThreadScheduler]主线程
RAC(self.imageView, image) = [[[[client
fetchUserWithUsername:@"joshaber"]
deliverOn:[RACScheduler scheduler]]
map:^(User *user) {
// Download the avatar (this is done on a background queue).
return [[NSImage alloc] initWithContentsOfURL:user.avatarURL];
}]
// Now the assignment will be done on the main thread.
deliverOn:RACScheduler.mainThreadScheduler];
示例
1. 处理多来源的异步操作或事件状态
不使用RAC的情况
页面中的登录按钮 只有在usernameTextField
passwordTextField
不为空 并且当前不在登录状态 才可以点击
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
p**assword: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];
}
2.串联异步操作的依赖
//logInWithSuccess 登录成功 -> loadCachedMessagesWithSuccess 加载本地数据 -> fetchMessagesAfterMessage 请求本地为缓存的最新的数据
//不使用色RAC 使用block嵌套
[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];
}];
//RAC版本
[[[[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.");
}];
3.用RAC实现 OC中 集合没有的 map、filter、fold/reduce等方法
//获取数组中字符串长度大于2并且拼接字符串`foobar`
NSMutableArray *results = [NSMutableArray array];
for (NSString *str in strings) {
if (str.length < 2) {
continue;
}
NSString *newString = [str stringByAppendingString:@"foobar"];
[results addObject:newString];
}
RACSequence *results = [[strings.rac_sequence
filter:^ BOOL (NSString *str) {
return str.length >= 2;
}]
map:^(NSString *str) {
return [str stringByAppendingString:@"foobar"];
}];