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

  1. 监测属性值变化

    类似kvo
    //检测self.username属性变化
    //RACObserve(TARGET, KEYPATH)是一个创建检测值变化的信号
    //subscribeNext 当检测的信号变化时 就会执行这个信号
    [RACObserve(self, username) subscribeNext:^(NSString *newName) {
    NSLog(@"%@", newName);
    }];
  2. 监测过滤出来的部分信号值
    与kvo不同的是,我们可以串联、操作这些信号

    //过滤 信号中以"j"开头的信号值的变化
    [[RACObserve(self, username)
    filter:^(NSString *newName) {
    return [newName hasPrefix:@"j"];
    }]
    subscribeNext:^(NSString *newName) {
    NSLog(@"%@", newName);
    }];
  3. 通过RAC我们可以很方便的通过信号监测变化,而不需要通过观察和设置其它属性

    //combineLatest: reduce:方法 合并一组信号,当其中任意一个信号的最新值变化时,会执行该block 并返回新的值
    RAC(self, createEnabled) = [RACSignal
    combineLatest:@[ RACObserve(self, password), RACObserve(self, passwordConfirmation) ]
    reduce:^(NSString *password, NSString *passwordConfirm) {
    return @([passwordConfirm isEqualToString:password]);
    }];
  4. 信号不止类似kvo可以监测属性,还可以监测其它任何随时间变化的值或者流

    //RACCommand 创建一个与UI有关的信号 此处是按钮点击信号,当按钮被点击时执行该block
    //rac_command是按钮类别的属性,当按钮被点击自动发送
    self.button.rac_command = [[RACCommand alloc] initWithSignalBlock:^(id _) {
    NSLog(@"button was pressed!");
    return [RACSignal empty];
    }];
  5. 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;
  6. 表示异步操作的信号间可以链接来表示更加复杂的行为(在一组操作完成后开始另外一个操作)

    //merge: 合并两个信号 返回一个新的信号
    //subscribeCompleted: 订阅返回完成的信号
    [[RACSignal merge:@[ [client fetchUserRepos], [client fetchOrgRepos] ]]
    subscribeCompleted:^{
    NSLog(@"They're both done!");
    }];
  7. 信号可以串联起来 避免使用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.");
    }];
  8. 获取异步操作的结果

    //当异步图片下载完成后赋值到图片上
    //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"];
    }];