美文网首页
OC的MVVM的数据双向绑定改造

OC的MVVM的数据双向绑定改造

作者: Johnny_Wu | 来源:发表于2023-03-24 13:58 被阅读0次

    目标

    OC改造为MVVM设计模式,并且实现VM与View和Model的数据双向绑定。使用ReactiveObjC来进行改造。想进一步了解ReactiveObjC可以参考:
    https://blog.devtang.com/2014/02/11/reactivecocoa-introduction/

    https://cloud.tencent.com/developer/article/1017790

    https://www.jianshu.com/p/87ef6720a096

    项目结构

    本次可能我会淡化Model,而是直接在VM上定义了Model相关。主要关注VM和View的数据双向绑定。
    功能是,登录界面,有一个username TF,一个password TF,一个登录按钮:
    ReactiveLogin-UIViewController

    @interface ReactiveLogin ()
    <UITextFieldDelegate>
    {
        
    }
    
    @property(nonatomic) UIImageView *headImgV;
    @property(nonatomic) UITextField *usernameTf;
    @property(nonatomic) UITextField *pwdTf;
    @property(nonatomic) UIButton *loginBtn;
    
    @property(nonatomic) UserVM *userVM;
    @end
    
    @implementation ReactiveLogin
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        [self setNavWithTitle:@"Login" leftImage:@"arrow" leftTitle:nil leftAction:nil rightImage:nil rightTitle:nil rightAction:nil];
        [self setupUI];
        [self bindVM];
    }
    
    - (void)setupUI{
        [self.view addSubview:self.headImgV];
        [self.view addSubview:self.usernameTf];
        [self.view addSubview:self.pwdTf];
        [self.view addSubview:self.loginBtn];
        
        [self.headImgV mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.mas_equalTo(100);
            make.centerX.mas_equalTo(0);
            make.width.height.mas_equalTo(80);
        }];
        [self.usernameTf mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(self.headImgV.mas_bottom).mas_offset(14);
            make.centerX.mas_equalTo(0);
            make.width.mas_equalTo(200);
            make.height.mas_equalTo(44);
        }];
        
        [self.pwdTf mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(self.usernameTf.mas_bottom).mas_offset(10);
            make.centerX.mas_equalTo(0);
            make.width.height.equalTo(self.usernameTf);
        }];
        [self.loginBtn mas_makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(self.pwdTf.mas_bottom).mas_offset(14);
            make.centerX.mas_equalTo(0);
            make.width.height.equalTo(self.usernameTf);
        }];
    }
    
    - (void)bindVM{
        self.userVM = [[UserVM alloc] init];
        
        @weakify(self)
        [[self rac_signalForSelector:@selector(textFieldShouldReturn:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(RACTuple * _Nullable x) {
            NSLog(@"%@",x);
            UITextField *tf = (UITextField *)x[0];
            [tf resignFirstResponder];
        }];
        
        RAC(self.userVM,username) = self.usernameTf.rac_textSignal;
        RAC(self.userVM,pwd) = self.pwdTf.rac_textSignal;
        
        
        [[RACSignal combineLatest:@[self.usernameTf.rac_textSignal, self.pwdTf.rac_textSignal] reduce:^id _Nonnull(NSString *username, NSString *password){
            return @(username.length && password.length);
        }] subscribeNext:^(id  _Nullable x) {
            NSLog(@"combineLatest>>>%@ %@",x,[NSThread currentThread]);
            BOOL enable = [x boolValue];
            self.loginBtn.enabled = enable;
            if(enable){
                [self.loginBtn setBackgroundColor:[UIColor greenColor]];
            }else{
                [self.loginBtn setBackgroundColor:[UIColor grayColor]];
            }
        }];
        
        [[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
            [self.userVM.loginCommand execute:nil];
        }];
        [self.userVM.loginCommand.executing subscribeNext:^(NSNumber * _Nullable x) {
            BOOL ret = [x boolValue];
            ret?[PublicFunction showNoHiddenLoading:@""]:[PublicFunction hiddenHUD];
        }];
        
        [self.userVM.loginCommand.executionSignals subscribeNext:^(RACSignal  *x) {
            
            [x subscribeNext:^(id  _Nullable x) {
                NSLog(@"login succ>>>%@",x);
            }];
            [x subscribeCompleted:^{
                NSLog(@"login completed");
            }];
            [x subscribeError:^(NSError * _Nullable error) {
                NSLog(@"login errors>>>%@",x);
            }];
            
    //        [x subscribeNext:^(id  _Nullable x) {
    //            NSLog(@"login succ>>>%@",x);
    //        } completed:^{
    //            NSLog(@"login completed");
    //        }];
            
        }];
        [self.userVM.loginCommand.errors subscribeNext:^(NSError * _Nullable x) {
            NSLog(@"errors>>>%@",x);
        }];
    //    [self.userVM.loginCommand.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
    //        NSLog(@"login succ>>>%@",x);
    //    } error:^(NSError * _Nullable error) {
    //        NSLog(@"login error>>>%@",error);
    //    } completed:^{
    //        NSLog(@"login completed");
    //    }];
    //    [self.userVM.loginCommand.executionSignals.switchToLatest subscribeError:^(NSError * _Nullable error) {
    //        NSLog(@"login error>>>%@",error);
    //    }];
        
        [RACObserve(self.userVM, headUrl) subscribeNext:^(id  _Nullable x) {
            @strongify(self)
    //        self.headImgV.image = [UIImage imageNamed:x];
            NSLog(@"headUrl>>>%@",x);
            self.headImgV.backgroundColor = [UIColor redColor];
            if([x isEqualToString:@"blue"]){
                self.headImgV.backgroundColor = [UIColor blueColor];
            }
            
        }];
    }
    
    #pragma -mark getting or setting
    
    - (UIImageView *)headImgV{
        if(!_headImgV){
            _headImgV = [[UIImageView alloc] init];
            _headImgV.backgroundColor = [UIColor redColor];
        }
        return _headImgV;
    }
    
    - (UITextField *)usernameTf{
        if(!_usernameTf){
            _usernameTf = [[UITextField alloc] init];
            _usernameTf.layer.borderWidth = 1;
            _usernameTf.delegate = self;
        }
        return _usernameTf;
    }
    
    - (UITextField *)pwdTf{
        if(!_pwdTf){
            _pwdTf = [[UITextField alloc] init];
            _pwdTf.layer.borderWidth = 1;
            _pwdTf.delegate = self;
        }
        return _pwdTf;
    }
    
    - (UIButton *)loginBtn{
        if(!_loginBtn){
            _loginBtn = [UIButton buttonWithType:UIButtonTypeCustom];
            [_loginBtn setBackgroundColor:[UIColor greenColor]];
            [_loginBtn setTitle:@"login" forState:UIControlStateNormal];
        }
        return _loginBtn;
    }
    

    UserVM-ViewModel

    @interface UserVM : NSObject
    
    @property(nonatomic) NSString *username;
    @property(nonatomic) NSString *pwd;
    @property(nonatomic) NSString *headUrl;
    @property(nonatomic) RACCommand *loginCommand;
    @end
    
    @interface UserVM()
    {
        BOOL _result;
    }
    @property(nonatomic) RACSubject *loginSignal;
    @end
    
    @implementation UserVM
    
    - (instancetype)init{
        if(self = [super init]){
            [self initParams];
        }
        return self;
    }
    
    - (void)initParams{
        self.headUrl = @"red";
    //    self.loginSignal = [[RACSubject alloc] init];
    //    self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
    //
    //        [self loginWithUsername:self.username pwd:self.pwd callback:^(BOOL ret) {
    //            if(ret){
    //                [self.loginSignal sendNext:@"YES"];
    //            }else{
    //                [self.loginSignal sendError:nil];
    //            }
    //            [self.loginSignal sendCompleted];
    //        }];
    //        return self.loginSignal;
    //    }];
        
        self.loginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
            NSLog(@"in RACCommand block");
            return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
                [self loginWithUsername:self.username pwd:self.pwd callback:^(BOOL ret) {
                    if(ret){
                        [subscriber sendNext:@"YES"];
                    }else{
                        [subscriber sendError:nil];
                    }
                    [subscriber sendCompleted];
                }];
                return nil;
            }];
        }];
    }
    
    //login
    - (void)loginWithUsername:(NSString *)username pwd:(NSString *)pwd callback:(void(^)(BOOL ret))callback{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"start login>>%@,%@",username,pwd);
            [NSThread sleepForTimeInterval:2];
            
            dispatch_async(dispatch_get_main_queue(), ^{
                _headUrl = @"blue";
                _result = !_result;
                if(callback) callback(_result);
            });
        });
    }
    @end
    

    TF:
    usernameTf的值输入直接同步到self.userVM的username
    RAC(self.userVM,username) = self.usernameTf.rac_textSignal;
    RAC(self.userVM,pwd) = self.pwdTf.rac_textSignal;

    Btn:
    通过RACCommand来与btn建立关系,点击btn后的处理逻辑再VM内,然后VM把处理的结果反馈给VC
    关联btn点击事件:
    [[self.loginBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
    [self.userVM.loginCommand execute:nil];
    }];
    事件处理后的反馈:
    [self.userVM.loginCommand.executionSignals subscribeNext:^(RACSignal *x) {

        [x subscribeNext:^(id  _Nullable x) {
            NSLog(@"login succ>>>%@",x);
        }];
        [x subscribeCompleted:^{
            NSLog(@"login completed");
        }];
        [x subscribeError:^(NSError * _Nullable error) {
            NSLog(@"login errors>>>%@",x);
        }];
    }];
    

    头像控件的关联:
    VM内调用登录接口后,会获取到用户头像url,如何做到在VM内赋值了url后,VC内的头像控件就直接刷新呢:

    [RACObserve(self.userVM, headUrl) subscribeNext:^(id  _Nullable x) {
            @strongify(self)
            NSLog(@"headUrl>>>%@",x);
            self.headImgV.backgroundColor = [UIColor redColor];
            if([x isEqualToString:@"blue"]){
                self.headImgV.backgroundColor = [UIColor blueColor];
            }
            
        }];
    

    这样改造后,你会发现,数据处理与UI完全解耦了。

    相关文章

      网友评论

          本文标题:OC的MVVM的数据双向绑定改造

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