iOS开发基础111-RAC

ReactiveCocoa(RAC)是一个基于函数响应式编程(FRP)的框架,广泛用于iOS开发中。其核心思想是通过流和信号(signal)来处理多变、复杂的事件。以下是ReactiveCocoa常见的一些用法场景,并深入解析其原理。

1. 响应用户输入

场景:表单验证

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

RACSignal *validUsernameSignal = [self.usernameTextField.rac_textSignal
    map:^id _Nullable(NSString * _Nullable username) {
        return @(username.length > 3);
    }];

RACSignal *validPasswordSignal = [self.passwordTextField.rac_textSignal
    map:^id _Nullable(NSString * _Nullable password) {
        return @(password.length > 3);
    }];

RACSignal *formValidSignal = [RACSignal combineLatest:@[validUsernameSignal, validPasswordSignal]
    reduce:^id (NSNumber *usernameValid, NSNumber *passwordValid){
        return @([usernameValid boolValue] && [passwordValid boolValue]);
}];

[formValidSignal subscribeNext:^(NSNumber *formValid) {
    self.loginButton.enabled = [formValid boolValue];
}];

原理解析:

  1. rac_textSignal将UITextField的文字变化转化为信号。
  2. map:操作符将输入的字符串转化为有效性的布尔值(长度大于3)。
  3. combineLatest:reduce:将多个信号结合成一个新的信号,并通过reduce块生成最终结果。
  4. subscribeNext:订阅信号,且在信号值变化时对登录按钮进行启用/禁用。

2. 数据绑定

场景:双向绑定ViewModel和View

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

RAC(self.viewModel, username) = self.usernameTextField.rac_textSignal;
RAC(self.usernameTextField, text) = RACObserve(self.viewModel, username);

原理解析:

  1. RAC(target, keyPath)将信号绑定到ViewModel的属性。
  2. RACObserve创建一个观察信号,当viewModel.username变化时,更新UITextField的text。
  3. 双向绑定确保View和ViewModel的属性同步变化。

3. 处理异步操作

场景:网络请求

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

- (RACSignal *)fetchDataFromURL:(NSURL *)url {
    return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                [subscriber sendError:error];
            } else {
                [subscriber sendNext:data];
                [subscriber sendCompleted];
            }
        }];
        [task resume];
        
        return [RACDisposable disposableWithBlock:^{
            [task cancel];
        }];
    }];
}

[[self fetchDataFromURL:url] subscribeNext:^(NSData *data) {
    // Process data
} error:^(NSError *error) {
    // Handle error
}];

原理解析:

  1. createSignal:创建一个信号并手动控制其数据(如下一步、完成、错误)流的发送。
  2. 在信号内部,执行异步操作(例如网络请求),并在操作完成时发送事件(数据、完成、错误)。
  3. subscribeNext:error:对信号的成功和失败情况进行订阅和处理。

4. 信号组合和变换

场景:信号合并和过滤

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

// 合并信号
RACSignal *mergedSignal = [RACSignal merge:@[self.signalA, self.signalB]];
[mergedSignal subscribeNext:^(id  _Nullable x) {
    NSLog(@"Received: %@", x);
}];

// 过滤信号
RACSignal *filteredSignal = [self.inputSignal filter:^BOOL(id  _Nullable value) {
    return [value length] > 3;
}];
[filteredSignal subscribeNext:^(id  _Nullable x) {
    NSLog(@"Filtered value: %@", x);
}];

原理解析:

  1. merge:将多个信号合并成一个信号,同时订阅多个信号的所有事件。
  2. filter:只允许满足条件的事件通过,其他事件将被过滤掉。

5. 基于时间的操作

场景:延迟、定时器、节流

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

// 延迟操作
[[RACSignal return:@"Hello, World!"] delay:2.0];

// 定时器
RACSignal *timerSignal = [RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]];
[timerSignal subscribeNext:^(NSDate * _Nullable x) {
    NSLog(@"Timer tick: %@", x);
}];

// 节流操作
RACSignal *throttledSignal = [[self.usernameTextField.rac_textSignal throttle:0.5];
[throttledSignal subscribeNext:^(NSString * _Nullable x) {
    NSLog(@"Throttled value: %@", x);
}];

原理解析:

  1. delay:将信号的所有事件延迟指定的时间发送。
  2. interval:创建一个定时器信号,每隔指定的时间发出一个事件。
  3. throttle:在指定的时间内只发送最新的一次事件,防止高频率触发。

6. 错误处理

场景:网络请求错误与重试机制

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

- (RACSignal *)fetchDataFromURL:(NSURL *)url {
    return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                [subscriber sendError:error];
            } else {
                [subscriber sendNext:data];
                [subscriber sendCompleted];
            }
        }];
        [task resume];
        
        return [RACDisposable disposableWithBlock:^{
            [task cancel];
        }];
    }];
}

[[[self fetchDataFromURL:url] retry:3] subscribeNext:^(NSData *data) {
    // Process data
} error:^(NSError *error) {
    // Handle error
}];

原理解析:

  1. retry:在信号遇到错误时重新订阅信号,最多重试指定的次数。
  2. 使用sendError:, sendNext:, sendCompleted:手动控制信号的错误和数据发送。

7. 依赖信号

场景:顺序执行多个异步任务

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

- (RACSignal *)loginSignal:(NSString *)username password:(NSString *)password {
    // 登录 signal implementation
}

- (RACSignal *)fetchUserProfileSignal {
    // 获取用户Profile的信号
}

[[[self loginSignal:@"user" password:@"pass"]
    flattenMap:^__kindof RACSignal * _Nullable(id  _Nullable value) {
        return [self fetchUserProfileSignal];
    }]
    subscribeNext:^(id  _Nullable x) {
        NSLog(@"User Profile: %@", x);
    } error:^(NSError * _Nullable error) {
        NSLog(@"Error: %@", error);
   }];

原理解析:

  1. flattenMap:将第一个信号的值映射为另一个信号,并订阅这个新信号。这通常用于依赖关系,即在一个任务完成后启动另一个任务。
  2. 在登录成功后,使用flattenMap:触发获取用户Profile的信号。

8. 控件事件处理

场景:按钮点击事件

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIButton *myButton;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [[self.myButton rac_signalForControlEvents:UIControlEventTouchUpInside]
        subscribeNext:^(UIButton *button) {
            NSLog(@"Button clicked");
        }];
}

@end

原理解析:

  1. rac_signalForControlEvents:将UI控件的事件(例如按钮的点击)转化为信号。
  2. 通过subscribeNext:订阅该信号,以处理点击事件。

9. 定时操作

场景:倒计时功能

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UILabel *countdownLabel;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __block int remainingTime = 60;
    RACSignal *countdownSignal = [[RACSignal interval:1.0 onScheduler:[RACScheduler mainThreadScheduler]]
                                  take:remainingTime];
    
    [countdownSignal subscribeNext:^(NSDate * _Nullable x) {
        self.countdownLabel.text = [NSString stringWithFormat:@"%d", remainingTime];
        remainingTime--;
    } completed:^{
        self.countdownLabel.text = @"Time's up!";
    }];
}

@end

原理解析:

  1. interval:onScheduler:创建一个每秒发出事件的信号。
  2. take:指定信号发送的事件次数。
  3. 通过subscribeNext:更新界面。

10. 表格视图刷新

场景:下拉刷新

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>
#import <MJRefresh/MJRefresh.h>

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        [[self fetchData] subscribeNext:^(NSArray *data) {
            // Update table view with new data
            [self.tableView reloadData];
            [self.tableView.mj_header endRefreshing];
        }];
    }];
}

- (RACSignal *)fetchData {
    return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        // Simulate network request
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSArray *data = @[@"Item 1", @"Item 2", @"Item 3"];
            [subscriber sendNext:data];
            [subscriber sendCompleted];
        });
        return nil;
    }];
}

@end

原理解析:

  1. 使用第三方库MJRefresh添加下拉刷新控件。
  2. 在刷新时,发起数据请求的信号并更新表格视图数据。
  3. 在订阅数据之后结束刷新状态。

11. 链式反应

场景:密码和确认密码验证

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

RACSignal *validPasswordSignal = [self.passwordTextField.rac_textSignal
    map:^id _Nullable(NSString * _Nullable password) {
        return @(password.length > 3);
    }];

RACSignal *validConfirmPasswordSignal = [RACSignal
    combineLatest:@[self.passwordTextField.rac_textSignal, self.confirmPasswordTextField.rac_textSignal]
    reduce:^id (NSString *password, NSString *confirmPassword) {
        return @(password.length > 3 && [password isEqualToString:confirmPassword]);
}];

[[RACSignal combineLatest:@[validPasswordSignal, validConfirmPasswordSignal]
                   reduce:^id (NSNumber *passwordValid, NSNumber *confirmPasswordValid){
                       return @([passwordValid boolValue] && [confirmPasswordValid boolValue]);
                   }]
          subscribeNext:^(NSNumber *formValid) {
              self.registerButton.enabled = [formValid boolValue];
}];

原理解析:

  1. rac_textSignal处理密码和确认密码输入框的文本变化。
  2. combineLatest:reduce:将两个信号结合,并在两个信号联动时验证密码和确认密码。
  3. 通过组合后的信号来控制注册按钮是否可用。

12. 进度条更新

场景:文件下载进度

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

- (RACSignal *)downloadFileWithProgress {
    return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSURLSession *session = [NSURLSession sharedSession];
        NSURL *url = [NSURL URLWithString:@"https://example.com/file.zip"];
        NSURLSessionDownloadTask *task = [session downloadTaskWithURL:url
                                                    completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
            if (error) {
                [subscriber sendError:error];
            } else {
                [subscriber sendCompleted];
            }
        }];
        
        [task resume];
        
        return [RACDisposable disposableWithBlock:^{
            [task cancel];
        }];
    }];
}

[[self downloadFileWithProgress] subscribeCompleted:^{
    self.progressView.progress = 1.0;
} error:^(NSError * _Nullable error) {
    NSLog(@"Download failed: %@", error);
}];

[RACSignal interval:0.1 onScheduler:[RACScheduler mainThreadScheduler]]
  subscribeNext:^(NSDate * _Nullable x) {
      self.progressView.progress = (arc4random_uniform(100) / 100.0);
  }];

原理解析:

  1. createSignal:进行文件下载,并在下载完成时发送完成事件。
  2. 创建一个定时信号来模拟下载进度,并实时更新UI。

13. 监听值变化

场景:属性绑定

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

@interface MyViewModel : NSObject
@property (nonatomic, strong) NSString *name;
@end

@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UILabel *nameLabel;
@property (nonatomic, strong) MyViewModel *viewModel;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.viewModel = [[MyViewModel alloc] init];
    
    RAC(self.nameLabel, text) = RACObserve(self.viewModel, name);
    
    self.viewModel.name = @"Initial name";
}

@end

原理解析:

  1. RACObserve监听ViewModel属性变化。
  2. 使用RAC(target, keyPath)绑定属性值到UILabel的text属性。

14. 动态计算属性

场景:计算两数之和

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

@interface ViewController ()
@property (weak, nonatomic) IBOutlet UITextField *firstNumberTextField;
@property (weak, nonatomic) IBOutlet UITextField *secondNumberTextField;
@property (weak, nonatomic) IBOutlet UILabel *sumLabel;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RACSignal *firstNumberSignal = [self.firstNumberTextField.rac_textSignal map:^id _Nullable(NSString * _Nullable text) {
        return @([text integerValue]);
    }];
    
    RACSignal *secondNumberSignal = [self.secondNumberTextField.rac_textSignal map:^id _Nullable(NSString * _Nullable text) {
        return @([text integerValue]);
    }];
    
    RACSignal *sumSignal = [RACSignal combineLatest:@[firstNumberSignal, secondNumberSignal] reduce:^id (NSNumber *firstNumber, NSNumber *secondNumber) {
        return @([firstNumber integerValue] + [secondNumber integerValue]);
    }];
    
    RAC(self.sumLabel, text) = [sumSignal map:^id _Nullable(NSNumber * _Nullable sum) {
        return [sum stringValue];
    }];
}

@end

原理解析:

  1. map:将文本转化为数字。
  2. combineLatest:reduce:计算两数之和。
  3. 使用RAC(target, keyPath)绑定结果到UILabel。

15. 处理通知

场景:监听键盘弹出和隐藏

示例代码:

#import <ReactiveObjC/ReactiveObjC.h>

@interface ViewController ()
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomConstraint;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    RACSignal *keyboardShowSignal = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] takeUntil:self.rac_willDeallocSignal];
    
    RACSignal *keyboardHideSignal = [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillHideNotification object:nil] takeUntil:self.rac_willDeallocSignal];
    
    RACSignal *keyboardFrameSignal = [RACSignal merge:@[keyboardShowSignal, keyboardHideSignal]]
    .map(^id (NSNotification *notification) {
        return [notification.userInfo valueForKey:UIKeyboardFrameEndUserInfoKey];
    }];
    
    [keyboardFrameSignal subscribeNext:^(NSValue *keyboardFrame) {
        CGRect frame = keyboardFrame.CGRectValue;
        self.bottomConstraint.constant = frame.size.height;
        [UIView animateWithDuration:0.25 animations:^{
            [self.view layoutIfNeeded];
        }];
    }];
}

@end

原理解析:

  1. rac_addObserverForName:监听键盘显示和隐藏通知。
  2. map:将通知转化为键盘的frame值。
  3. 通过订阅信号动态调整底部约束。

这些场景展示了ReactiveCocoa在iOS开发中的多种应用,通过信号和操作符,能够简化异步操作和事件处理,使代码更加简洁和可维护。

posted @   Mr.陳  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
历史上的今天:
2015-07-17 iOS开发基础10-UIButton内边距和图片拉伸模式
2015-07-17 iOS开发基础9-提示框(UIAlertController)
点击右上角即可分享
微信分享提示