[IOS] KVO + KVC build MVVM

Understand MVVM

MVVM and MVC are built very similar, or even that the use of these two architectures in the same project at the same time there will not be any sense of violation and. MVVM can be seen as MVC derivative version, which bear the MVC under the framework Controller part of the responsibility, which is part of the responsibility ViewModel need to do. In MVVM the Model and View communications between, by ViewModel constructed a data pipeline, ViewModel the View to be displayed Model data layer, into the final desired version, View directly come and collect impressions. Of course, this pipe is preferably constructed by frame response: ReactiveObjc , RxSwift . Similarly, the two architectures are equally Controller to fully understand the various components of the program, and will build them and connected. But compared to the MVC , MVVM There are the following differences:

  • Model from the ViewModel hold, not Controller
  • The need to establish a ViewModel and View binding relationship between

Used herein is not responsive framework to build a binding relationship, but by native the API: the KVO + KVC constructed manner.

Function Package

  • Controller base class

    As we all know, in MVVM framework, the Controller is required to hold ViewModel 's. So build a base class to create a ViewModel property is very necessary. So that all inherited from the base class Controller sub-controller will have ViewModel .

    @interface MVVMGenericsController<ViewModelType: id<ViewModelProtocol>> : UIViewController
    
    @property (nonatomic, strong) ViewModelType viewModel;
    
    @end
    复制代码

    First, the base class Controller is generic (Objective-C in view of the generic function do not want to Swift so strong, only played here a marked role in helping the compiler infers the type ViewModel), let us call MVVMGenericsController , its ViewModel type required achieve ViewModelProtocol agreement, for the time being ignore this agreement, for now, will not have any impact on the reading code. Next, define the viewModel property.

  • Binding opportunity

    Speaking above, MVVM key is to build ViewModel and View conduit between, establish a binding relationship. In this case, it is possible Controller set an automatic callback method, an opportunity to build and trigger a binding relationship among the methods.

     - (void)bind:(id<ViewModelProtocol>)viewModel {     }
    复制代码

    So, when to trigger this approach? In the trigger bind:before the method, it is necessary to determine the View and ViewModel not empty (View here refer to the need to control that displays data, such as the Controller of UITableView, ViewModelProtocol agreement will be mentioned later), because of the need to establish binding in this method relations, it is necessary to ensure that both have value. In general, create the controller controls the neutron, is placed - (void)viewDidLoador - (void)loadViewmethod inside, so you can call the following two methods - (void)viewWillAppear:(BOOL)animatedin response bind:method. Of course, go in manually add each controller [self bind]code like this, no doubt a lot of trouble. IOS by black magic: Hook operate automatically invoked.

    @implementation UIViewController (Binding)
    
    + (void)load {
         [self hookOrigInstanceMenthod:@selector(viewWillAppear:) newInstanceMenthod:@selector(mvvm_viewWillAppear:)];
    }
    
    - (void)mvvm_viewWillAppear:(BOOL)animated {
       [self mvvm_viewWillAppear:animated];
    
       if (!self.isAlreadyBind) {
            if ([self isKindOfClass:[MVVMGenericsController class]]) {
                objc_msgSend((MVVMGenericsController *)self, @selector(bindTransfrom));
            }   
           self.isAlreadyBind = YES;
        }
    }
    
    - (void)setIsAlreadyBind:(BOOL)isAlreadyBind {
        objc_setAssociatedObject(self, &kIsAlreadyBind, @(isAlreadyBind), OBJC_ASSOCIATION_ASSIGN);
    }
    
    - (BOOL)isAlreadyBind {
        return !(objc_getAssociatedObject(self, &kIsAlreadyBind) == nil);
    }
    
    - (void)bindTransfrom {}
    
    @end
    复制代码

    First, Hook operation is extended among implementations. In + (void)loadthe method of the custom methods and systems which viewWillAppear:exchange. + (void)loadBefore the main function is called in the program compiled by the system load phase, and only called once, and. So here it is to deploy hook place the best. Secondly, in this extension which is associated a isAlreadyBind property in order to bring a Controller before destroying triggered only once bind:method. Again, by isKindOfClassdetermining the current class is not MVVMGenericsControllera subclass, if so, to send bindTransfroma message, bindTransfromjust an empty method, not surprisingly, never called here, it is just to let the compiler annoying yellow warning does not appear.

  • MVVMGenericsController achieve some

    In MVVMGenericsController is the realization of bindTransfrom:the place, because it is the real message sent.

    @implementation MVVMGenericsController
    
    - (void)bindTransfrom {
        if ([self conformsToProtocol:@protocol(ViewBinder)] && [self respondsToSelector:@selector(bind:)]) {
            if ([self.viewModel conformsToProtocol:@protocol(ViewModelProtocol)]) {
            [((id <ViewBinder>)self) bind:self.viewModel];
                return;
            }
        }
    }
    
    @end
    复制代码

    <ViewBinder>Providing protocol mentioned above, - (void)bind:(id<ViewModelProtocol>)viewModela method

    @protocol ViewBinder <NSObject>
    
    - (void)bind:(id<ViewModelProtocol>)viewModel; 
    
    @end
    复制代码

    First, the controller determines whether to implement the current ViewBinderprotocol and whether the response bind:method, if the distribution bind:parameter is the ViewModel . ViewModel assignment method in a custom configuration controller, or - (void)viewWillAppear:before. Once the assignment is not in place, there will be nil .

  • Implement binding interfaces

    Here binding responsive function by changing the properties observed immediately get feedback. Of course, it can also be achieved through a proxy, but undoubtedly the most responsive lightweight. Here means KVOController + native system the API KVC achieved. After a property of an object to be observed, as soon as it changes value, it immediately results by KVC assigned to a particular attribute of another object, that is, this process of establishing binding. Here to NSObject extension methods:

    @implementation NSObject (Binder)
    
    - (void)bind:(NSString *)sourceKeyPath to:(id)target at:(NSString *)targetKeyPath {
        [self.KVOController observe:self keyPath:sourceKeyPath options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionInitial block:^(id  _Nullable observer, id  _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
            id newValue = change[NSKeyValueChangeNewKey];
            if ([self verification:newValue]) {
                [target setValue:newValue forKey:targetKeyPath];
            }
        }];
    }
    
    - (BOOL)verification:(id)newValue {
     if ([newValue isEqual: [NSNull null]]) {
         return NO;
      }
      return YES;
    }
    
    @end
    复制代码

    sourceKeyPath: The observed object properties keyPath , targetobject value assigned to a target object, i.e., observed: at: the attribute of the target object keyPath . In Objective-C does not like not Swift among \Foo.barthe KeyPath function, so here only the critical path is a string.

Implement a Case

  • ViewModel

    There is no doubt, ViewModel is MVVM core component. A complex function modules, ViewModel may be of great length code. ViewModel should contain most of the business logic of a function module, a page with interactive features, will undoubtedly need the support of the state. So ViewModel after good data processed by the State thrown to the outside. Another part, by external Action notification ViewModel needs to be done.

    So, a ViewModel mainly consists of two parts Action and State :

    @interface DemoViewModel : NSObject<ViewModelProtocol> // 只是个空协议
    
    // Action
    - (void)changeTitle;
    
    // State
    @property (nonatomic, copy, readonly) NSString *title;
    
    // Model
    @property (nonatomic, copy, readonly) NSArray *titleArray;
    
    @end
    复制代码

    Note: This title (ie State ) is readonly, it must strictly in this way, because a State merely read-only is enough.

    @interface DemoViewModel()
    
    @property (nonatomic, copy, readwrite) NSString *title;
    
    @end
    
    @implementation DemoViewModel
    
    - (instancetype)init {
          self = [super init];
          if (self) {
              _titleArray = @[@"MVC", @"MVVM", @"SWift", @"ReactNative"];
             _title = _titleArray[1];
          }
         return self;
    }
    
    - (void)changeTitle {
          self.title = _titleArray[[self randomFloatBetween:0 andLargerFloat:4]];
    }
    
    @end
    复制代码

    In ViewModel part will be achieved title reset ReadWrite , because through changeTitle (i.e. the Action ) to change the title value.

  • Controller

    Controller 's role is to connect the components together, where build View <-> the ViewModel pipe.

    @interface DemoViewController : MVVMGenericsController<DemoViewModel *><ViewBinder>
    
    @end
    复制代码

    First, MVVMGenericsController as parent, due MVVMGenericsController defined generic ViewModelType , where the need to specify ViewModel particular type <DemoViewModel *>. Secondly, to achieve <ViewBinder>agreement, the agreement provides a - (void)bind:(DemoViewModel *)viewModelmethod.

    @interface DemoViewController ()
    
    @property (nonatomic, strong) UILabel *titleLabel;
    
    @end
    
    @implementation DemoViewController
    
    - (void)bind:(DemoViewModel *)viewModel {
         [viewModel bind:@"title" to:self.titleLabel at:@"text"];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
         [self.viewModel changeTitle];
    }
    
    复制代码

    In the - (void)bind:(DemoViewModel *)viewModelprocess, the establishment ViewModelof titlewith titleLabelthe textbinding relationship, where the real ViewModel with the View of the pipeline to get through.

    In the - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)eventmethod, call the ViewModel 's - (void)changeTitlemethods, the purpose is to change the titlevalue, and once titlethe value of change, bind:the method will be to monitor and change the value of the new value assigned to titleLabel.text . This forms a one-way flow of data. As shown below:

    A principle: State changes required by Action .

    So far, a simple MVVM set up is completed. Of course, there are many State may also have a lot of Action . Just follow this rule, a responsive , one-way data flow applications was born.

Dereference cycle

Unfortunately that [viewModel bind:@"title" to:self.titleLabel at:@"text"];code will produce a reference cycle: viewModel by KVO his observations title attribute. Such KVOController not automatically remove the observer, so be removed manually, of course, this process is behind the operation:

const void* const kIsCallPop = &kIsCallPop;

@implementation UIViewController (RetainCircle)

+ (void)load {
    [self hookOrigInstanceMenthod:@selector(viewDidDisappear:) newInstanceMenthod:@selector(mvvm_viewDidDisappear:)];
}

- (void)mvvm_viewDidDisappear:(BOOL)animated {
    [self mvvm_viewDidDisappear:animated];
    
    if ([objc_getAssociatedObject(self, kIsCallPop) boolValue]) {
        if ([self isKindOfClass:[MVVMGenericsController class]] && [((MVVMGenericsController *)self).viewModel conformsToProtocol:@protocol(ViewModelProtocol)]) {
            NSObject *vm = ((MVVMGenericsController *)self).viewModel;
            [vm.KVOController unobserveAll];
        }
    }
}

@end

@implementation UINavigationController (RetainCircle)

+ (void)load {
    [self hookOrigInstanceMenthod:@selector(popViewControllerAnimated:) newInstanceMenthod:@selector(mvvm_popViewControllerAnimated:)];
}

- (UIViewController *)mvvm_popViewControllerAnimated:(BOOL)animated {
    UIViewController* popViewController = [self mvvm_popViewControllerAnimated:animated];
    objc_setAssociatedObject(popViewController, kIsCallPop, @(YES), OBJC_ASSOCIATION_RETAIN);
    return popViewController;
}
复制代码

By the same method the exchange is very simple, the code is not explained.

End

By reading this article on MVVM whether there was a new understanding of it? This code is of course, there are many imperfections, but does not affect the reading, does not affect the understanding of the code. I think this is enough.

That these, here is the Demo .

Reproduced in: https: //juejin.im/post/5cfbcd41f265da1b8f1ab3c8

Guess you like

Origin blog.csdn.net/weixin_33889245/article/details/93169723