iOS-MVVM与RAC

作者: CDLOG | 来源:发表于2022-01-29 15:43 被阅读0次

    1.介绍MVVM架构思想。

    2.1 程序为什么要架构:便于程序员开发和维护代码。

    2.2 常见的架构思想:

    2.3 MVVM介绍

    • 模型(M):保存视图数据。

    • 视图+控制器(V):展示内容 + 如何展示

    • 视图模型(VM):处理展示的业务逻辑,包括视图事件的处理,数据的请求和解析等等。
      通过绑定和信号将V和VM连接起来。
      每个控制器对应一个VM模型。VM里面最好不要包括V。事件都封装到RACCommand处理。
      控制器内获取RACCommand数据

    RACSignal *si = [self.loginVM.loginCommand execute:@"执行1"];
            [si subscribeNext:^(id  _Nullable x) {
                NSLog(@"获取到00%@",x);
            }];
    

    VM内获取RACCommand数据

    [_loginCommand.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
            NSLog(@"获取到数据-----%@",x);
        }];
    

    3.ReactiveCocoa + MVVM 实战一:登录界面

    • 3.1需求+分析+步骤

    处理事件:使用RACCommand

    /* 需求:1.监听两个文本框的内容,有内容才允许按钮点击
            2.默认登录请求.
    
       用MVVM:实现,之前界面的所有业务逻辑
       分析:1.之前界面的所有业务逻辑都交给控制器做处理
            2.在MVVM架构中把控制器的业务全部搬去VM模型,也就是每个控制器对应一个VM模型.
    
       步骤:1.创建LoginViewModel类,处理登录界面业务逻辑.
            2.这个类里面应该保存着账号的信息,创建一个账号Account模型
            3.LoginViewModel应该保存着账号信息Account模型。
            4.需要时刻监听Account模型中的账号和密码的改变,怎么监听?
            5.在非RAC开发中,都是习惯赋值,在RAC开发中,需要改变开发思维,由赋值转变为绑定,可以在一开始初始化的时候,就给Account模型中的属性绑定,并不需要重写set方法。
            6.每次Account模型的值改变,就需要判断按钮能否点击,在VM模型中做处理,给外界提供一个能否点击按钮的信号.
            7.这个登录信号需要判断Account中账号和密码是否有值,用KVO监听这两个值的改变,把他们聚合成登录信号.
            8.监听按钮的点击,由VM处理,应该给VM声明一个RACCommand,专门处理登录业务逻辑.
            9.执行命令,把数据包装成信号传递出去
            10.监听命令中信号的数据传递
            11.监听命令的执行时刻
     */
    
    • 3.2 控制器的代码
    @interface ViewController ()
    
    @property (nonatomic, strong) LoginViewModel *loginViewModel;
    
    @property (weak, nonatomic) IBOutlet UITextField *accountField;
    @property (weak, nonatomic) IBOutlet UITextField *pwdField;
    
    @property (weak, nonatomic) IBOutlet UIButton *loginBtn;
    
    
    @end
    
    - (LoginViewModel *)loginViewModel
    {
        if (_loginViewModel == nil) {
    
            _loginViewModel = [[LoginViewModel alloc] init];
        }
        return _loginViewModel;
    }
    
    // 视图模型绑定
    - (void)bindModel
    {
        // 给模型的属性绑定信号
        // 只要账号文本框一改变,就会给account赋值
        RAC(self.loginViewModel.account, account) = _accountField.rac_textSignal;
        RAC(self.loginViewModel.account, pwd) = _pwdField.rac_textSignal;
    
        // 绑定登录按钮
        RAC(self.loginBtn,enabled) = self.loginViewModel.enableLoginSignal;
    
       // 监听登录按钮点击
        [[_loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
    
            // 执行登录事件
            [self.loginViewModel.LoginCommand execute:nil];
        }];
    }
    
    
    • 3.3 VM的代码
    @interface LoginViewModel : NSObject
    
    @property (nonatomic, strong) Account *account;
    
    
    // 是否允许登录的信号
    @property (nonatomic, strong, readonly) RACSignal *enableLoginSignal;
    
    @property (nonatomic, strong, readonly) RACCommand *LoginCommand;
    
    @end
    
    @implementation LoginViewModel
    - (Account *)account
    {
        if (_account == nil) {
            _account = [[Account alloc] init];
        }
        return _account;
    }
    - (instancetype)init
    {
        if (self = [super init]) {
            [self initialBind];
        }
        return self;
    }
    
    
    // 初始化绑定
    - (void)initialBind
    {
        // 监听账号的属性值改变,把他们聚合成一个信号。
        _enableLoginSignal = [RACSignal combineLatest:@[RACObserve(self.account, account),RACObserve(self.account, pwd)] reduce:^id(NSString *account,NSString *pwd){
    
            return @(account.length && pwd.length);
    
        }];
    
        // 处理登录业务逻辑
        _LoginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    
            NSLog(@"点击了登录");
            return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    
                // 模仿网络延迟
                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
                    [subscriber sendNext:@"登录成功"];
    
                    // 数据传送完毕,必须调用完成,否则命令永远处于执行状态
                    [subscriber sendCompleted];
                });
    
                return nil;
            }];
        }];
    
        // 监听登录产生的数据
        [_LoginCommand.executionSignals.switchToLatest subscribeNext:^(id x) {
    
            if ([x isEqualToString:@"登录成功"]) {
                NSLog(@"登录成功");
            }
        }];
    
        // 监听登录状态
        [[_LoginCommand.executing skip:1] subscribeNext:^(id x) {
            if ([x isEqualToNumber:@(YES)]) {
    
                // 正在登录ing...
                // 用蒙版提示
                [MBProgressHUD showMessage:@"正在登录..."];
    
    
            }else
            {
                // 登录成功
                // 隐藏蒙版
                [MBProgressHUD hideHUD];
            }
        }];
    }
    
    

    4.ReactiveCocoa + MVVM 实战二:网络请求数据

    • 4.1 接口:这里先给朋友介绍一个免费的网络数据接口,豆瓣。可以经常用来练习一些网络请求的小Demo.

    • 4.2 需求+分析+步骤

    /*
        需求:请求豆瓣图书信息,url:https://api.douban.com/v2/book/search?q=基础
    
        分析:请求一样,交给VM模型管理
    
        步骤:
            1.控制器提供一个视图模型(requesViewModel),处理界面的业务逻辑
            2.VM提供一个命令,处理请求业务逻辑
            3.在创建命令的block中,会把请求包装成一个信号,等请求成功的时候,就会把数据传递出去。
            4.请求数据成功,应该把字典转换成模型,保存到视图模型中,控制器想用就直接从视图模型中获取。
            5.假设控制器想展示内容到tableView,直接让视图模型成为tableView的数据源,把所有的业务逻辑交给视图模型去做,这样控制器的代码就非常少了。
     */
    
    
    • 4.3控制器代码
    @interface ViewController ()
    
    @property (nonatomic, weak) UITableView *tableView;
    
    @property (nonatomic, strong) RequestViewModel *requesViewModel;
    
    
    @end
    
    @implementation ViewController
    - (RequestViewModel *)requesViewModel
    {
        if (_requesViewModel == nil) {
            _requesViewModel = [[RequestViewModel alloc] init];
        }
        return _requesViewModel;
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
    
        // 创建tableView
        UITableView *tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
        tableView.dataSource = self.requesViewModel;
        self.requesViewModel.tableView = tableView;
        [self.view addSubview:tableView];
    
        // 执行请求
        [self.requesViewModel.reuqesCommand execute:nil];
    
    }
    
    
    @end
    
    
    
    • 4.4视图模型(VM)代码
    @interface RequestViewModel : NSObject<UITableViewDataSource>
    
    
        // 请求命令
        @property (nonatomic, strong, readonly) RACCommand *reuqesCommand;
    
        //模型数组
        @property (nonatomic, strong, readonly) NSArray *models;
    
        // 控制器中的view
        @property (nonatomic, weak) UITableView *tableView;
    
    @end
    
    @implementation RequestViewModel
    
    - (instancetype)init
    {
        if (self = [super init]) {
    
            [self initialBind];
        }
        return self;
    }
    
    
    - (void)initialBind
    {
        _reuqesCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
    
            RACSignal *requestSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    
    
                NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
                parameters[@"q"] = @"基础";
    
                // 发送请求
                [[AFHTTPRequestOperationManager manager] GET:@"https://api.douban.com/v2/book/search" parameters:parameters success:^(AFHTTPRequestOperation * _Nonnull operation, id  _Nonnull responseObject) {
                    NSLog(@"%@",responseObject);
    
                    // 请求成功调用
                    // 把数据用信号传递出去
                    [subscriber sendNext:responseObject];
    
                    [subscriber sendCompleted];
    
    
                } failure:^(AFHTTPRequestOperation * _Nonnull operation, NSError * _Nonnull error) {
                    // 请求失败调用
    
                }];
    
                return nil;
            }];
    
    
    
    
            // 在返回数据信号时,把数据中的字典映射成模型信号,传递出去
            return [requestSignal map:^id(NSDictionary *value) {
                NSMutableArray *dictArr = value[@"books"];
    
                // 字典转模型,遍历字典中的所有元素,全部映射成模型,并且生成数组
                NSArray *modelArr = [[dictArr.rac_sequence map:^id(id value) {
    
                    return [Book bookWithDict:value];
                }] array];
    
                return modelArr;
            }];
    
        }];
    
        // 获取请求的数据
        [_reuqesCommand.executionSignals.switchToLatest subscribeNext:^(NSArray *x) {
    
            // 有了新数据,刷新表格
            _models = x;
    
            // 刷新表格
            [self.tableView reloadData];
    
        }];
    }
    
    #pragma mark - UITableViewDataSource
    
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        return self.models.count;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *ID = @"cell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
        if (cell == nil) {
    
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
        }
    
        Book *book = self.models[indexPath.row];
        cell.detailTextLabel.text = book.subtitle;
        cell.textLabel.text = book.title;
    
        return cell;
    }
    
    @end
    

    相关文章

      网友评论

        本文标题:iOS-MVVM与RAC

        本文链接:https://www.haomeiwen.com/subject/agfccltx.html