This article is a companion article on MVVM pseudo-framework architecture and the IMPLEMENTATION mechanism of M in MVC. In the previous article, I introduced more theoretical things. Some partners also said in the comments that they hoped to have some specific design practice examples and carried out more in-depth discussions on some issues, so I prepared this article. This article will explain more about how to build the model layer.

The hierarchy of the framework is mainly based on roles and responsibilities as standards, some of the same nature of roles and responsibilities together to form a layer concept. MVC framework is the same, M layer is responsible for business construction and implementation, V layer is responsible for display and input and output interaction, and C layer is responsible for coordination and control of the whole system. In layman’s terms, layer V is what I want, layer M is what I have, and layer C is how DO I do it?

In the comments section of the previous article, some students proposed that the data model constructed with JSON is called the model layer. In fact, this is a mistake. The data model constructed with JSON is only a description of data structure, and it is not a role or a responsibility, so it is not M as described in MVC. Strictly speaking, it is just a data object operated by M, and I hope you can appreciate this.

So much nonsense, back to the main topic of building a model layer, how to build a model layer? Apple’s development framework does not define a standard pattern because businesses are so complex and diverse that there are no standards, and only standards are possible when a business scenario is clear. In addition to providing V layer and C UIKit.framkework frame, does Apple’s SDK framework provide some specific business framework?

There are!

We don’t have to look anywhere else for examples or to learn how to define the M-tier architecture. There are many business frameworks in iOS that provide very classic design ideas. For example, coreLocation. framework and mapKit. framework implement the classic MVC M layer design pattern. The main thing I want to show is how the positioning framework implements the M layer. It is important to note that this article is not about how the location library is used, but how the library implements the M layer.

IOS location library CoreLocation.framework of M layer encapsulation implementation

◎ Step 1: Business modeling

We know that CoreLocation.framework is a library that iOS uses for location. Positioning is a specific business requirement scenario. The general positioning requirement is to obtain my current location at any time, and after my current location updates also need to inform the observation user in real time; And need to know the specific location in which country, which city, which street and other landmark information. With these requirements in place, the business model can be built:

  • You need a location class to describe a location. The location should contain latitude and longitude, altitude, and direction.
  • You need a landmark class to describe which country, city, street, etc., a location is.
  • I need a location manager to get my current location and to be notified of location updates and location changes in real time.
  • A landmark parser is required to retrieve the corresponding landmark data based on the specified location.

The above are the basic requirements that a positioning business should have, so we can model based on these requirements:

Yes, the class diagram you see above is the definition of the Business model framework for Apple’s Location library. Here is a rough definition of the body class (excerpted from the coreLocation. framework header) :

// class @interface CLLocation: NSObject <NSCopying, NSSecureCoding> - (instancetype)initWithLatitude:(CLLocationDegrees)latitude longitude:(CLLocationDegrees)longitude; @property(readonly, nonatomic) CLLocationCoordinate2D coordinate; @end // CLPlacemark: NSObject <NSCopying, NSSecureCoding> @property (nonatomic, readonly, copy, nullable) CLLocation *location; @property (nonatomic, readonly, copy, nullable) NSString *locality; @property (nonatomic, readonly, copy, nullable) NSString *country; @interface CLLocationManager: NSObject @property(assign, nonatomic, nullable) id<CLLocationManagerDelegate> delegate; @property(readonly, nonatomic, copy, nullable) CLLocation *location; - (void)startUpdatingLocation; - (void)stopUpdatingLocation; @end // Landmark resolver class @interface CLGeocoder: NSObject - (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler; @ the end / / @ position update interface protocol CLLocationManagerDelegate < NSObject > - (void) locationManager (CLLocationManager *) manager didUpdateLocations:(NSArray<CLLocation *> *)locations; @endCopy the code

◎ Step 2: Attribute design

Once the structure and framework of the class are determined, the next step is to design the attributes of the class. Attributes of a class describe the properties of a class, and it is because of the difference in attribute values that the objects differ. From the above class diagram and business requirements, we can know that a location class should have longitude and latitude attributes, while a landmark class should have location, country, city and street information, and a location manager class should have a current location attribute and delegate attribute. We know that a class is a collection of properties and methods, but in practice not all classes have to have properties and methods. How do you tell which classes need methods and which classes don’t? One principle is to identify the operation and the operated from a business analysis perspective. Generally, the operator only needs to define the attribute, which has the function of abstraction of each real thing; The operator, on the other hand, usually has both properties and methods for manipulating other properties, and is responsible for implementing certain capabilities and maintaining and updating certain data. We usually refer to classes that have only attributes but no processing methods as data model classes, while classes that have both attributes and processing methods are called business classes or service classes. In the above example, we can see that the location and landmark classes are data model classes, while the location manager and landmark parser are business classes.

Read-only property

Notice that most of the attributes of the categories above are set to read-only. For example, the CLLocationManager class defines an attribute for the current location:

   @property(readonly, nonatomic, copy, nullable) CLLocation *location;
Copy the code

The location property in this case represents the current location of the location manager object. We find that this property is defined as read-only, why is it defined as read-only here? The reason is that our location manager class is responsible for managing the current location and internally updating the current location in real time. External consumers simply need to read the data in this property when appropriate. The user does not need to maintain or update this location value. This design mechanism also conveys a clear message to the external consumer that the external consumer is only responsible for reading the data, and the specific data update is done by the provider. The idea of this design clearly embodies the concept of hierarchy. And from the point of view of coding, it can reduce the error update and misuse of attribute values. Another reason is to protect the security of data. Once the properties of a class are exposed, you have no control over how users use them. If users do not understand the business logic and manually rewrite a data model or the property values of the business model, the results can be disastrous. So we are better off leaving data updates to the business provider rather than the business user.

In practice, class design at the model layer should also follow this principle:

  • Properties in a business class are designed to be read-only. The user can only read data through attributes. The values of these attributes are updated internally by the methods in the business class.
  • Property definitions in data model classes are also best set to read-only because data model creation is done inside the business class method and delivered to the consumer via notifications or asynchronous callbacks. It should not be left to users to create and update.
  • Data model classes typically provide an init initialization method with all properties that in principle cannot be changed again, so should be set to read-only.

The above design principles are based on the theory of consumers and producers. The producer, namely M layer, is responsible for the construction and update of data, and the consumer, namely C layer or V layer, is responsible for the use and consumption of data. We can experience this differentiation with the following two examples:

  1. Data model for read-write properties
/ /... User.h@interface user@property (nonatomic, copy) NSString *name; @property(nonatomic, assign) BOOL isLogin; @end //.......................................... / / model layer in the realization of the User class User. M @ implementation User @ the end / /... UserManager.h@interface UserManager // singleton +(instanceType)sharedInstance; // Define the current logged-in user. @property(nonatomic, strong) User *currentUser; -(void)loginWith:(User*) User; @end //.......................................... Usermanager. m@implementation UserManager -(void)loginWith:(User*) User {user.islogin = YES; self.currentUser = user; } @end //.......................................... -(void)handleLogin:(id)sender {User * User =[User new]; user.name = @"jack"; // Log in successfully with jack!! [[UserManager sharedInstance] loginWith:user]; /* Because there is no constraint, the caller can change the name and status of the login at will, and change currentUser to nil to indicate that no user is logged in. The lack of attribute protection may lead to improper use in the process of use and cause unknown problems. */ user.name = @"bob";
   user.isLogin = NO;
   [UserManager sharedInstance].currentUser = nil;
}


Copy the code

2. Data model for read-only attributes

/ /... h@interface user@property (nonatomic, copy,readonly)  NSString *name;
       @property(nonatomic, assign, readonly) BOOL isLogin; @end //.......................................... // Implementation of user classes in the model layer. m // Properties are redefined to read and write in internal extensions for internal modification. @interface User() @property(nonatomic, copy) NSString *name; @property(nonatomic, assign) BOOL isLogin; @end @implementation User @end //.......................................... UserManager.h@interface UserManager // singleton +(instanceType)sharedInstance; // Define the current logged-in user. @property(nonatomic, strong,readonly) User *currentUser; -(void)loginWith:(NSString *)name; @end //.......................................... M // Since UserManager internally reads and writes User properties, these properties are restated here. @interface User(UsedByUserManager) @property(nonatomic, copy) NSString *name; @property(nonatomic, assign) BOOL isLogin; @end@implementation UserManager {// You can also define a read-write property internally like this. User *_currentUser; } -(void)loginWith:(NSString*)name { _currentUser = [User new]; _currentUser.name = name; _currentUser.isLogin = YES; } @end .......................................... -(void)handleLogin:(id)sender { [[UserManager sharedInstance] loginWith:@"jack"]; // Users can't make any subsequent changes to currentUser! It can only read. This ensures the security and reliability of data. }Copy the code

It is clear that by encapsulating read-only attributes above, our model layer header code definition and usage will be clearer, and data and usage security issues will be ensured. At the same time, the above also introduces a kind of skill of defining differentiation between inside and outside attributes, which exposes as little and simple as possible to the outside, while many hidden attributes and methods can be released inside the same level. Or take the following code:

// External header file. @interface User @property(nonatomic, readonly) NSString *name; @property(nonatomic, readonly) NSArray *accounts; @end // Internal implementation file. @interface User() @property(nonatomic, copy) NSString *name; @property(nonatomic, strong) NSMutableArray *accounts; -(id)initWithName:(NSString*)name; @end @implementation User //.... @endCopy the code

◎ Step 3: Method design

With the attributes of the class designed, the next step is to consider the methods of the class. In a typical scenario, the business model will eventually have to go over the network to access the server, or access the local database. Both of these types of processing are related to IO, and one of the problems with doing IO is that it can block. If we put the IO on the main thread, then the main thread can block and not respond to the user’s request. So in general we can’t design the methods of business classes with synchronous return and synchronous blocking in mind. Instead, the calling method returns immediately and is notified asynchronously when the data is updated.

As mentioned above, one of the desired features is that the location manager can update the current location in real time and notify the user, and the landmark parser can parse a landmark object based on the input location. Both requirements can block, so the methods provided in the corresponding classes should be implemented asynchronously. IOS uses two classic asynchronous notification return mechanisms: Delegate and Block callbacks.

Delegate Indicates the asynchronous notification mode

Consider a property in the definition of the location manager class CLLocationManager:

    @property(assign, nonatomic, nullable) id<CLLocationManagerDelegate> delegate;
Copy the code

This attribute specifies a consignor, meaning that if the user object to real-time receives the notice of the variation of the then he only need to implement the CLLocationManagerDelegate interface protocols and assign a value to the CLLocationManager object’s delegate. We come to the part of the CLLocationManagerDelegate definition:

@protocol CLLocationManagerDelegate<NSObject> @optional - (void)locationManager:(CLLocationManager *)manager DidUpdateLocations :(NSArray<CLLocation *> *) API_AVAILABLE(ios(6.0), macos(10.9)); @endCopy the code

As you can see, when the location manager object updates the current location, it calls the delegate object’s didUpdateLocations method to notify the corresponding user observer. The user observer then performs some specific processing based on the latest location. But how many problems do we need to solve here?

  1. Who creates the M layer location management objects? The answer is: Controller C. Because the controller is the object responsible for coordinating and using m-layer objects, layer C is responsible for creating and holding m-layer objects, and layer C is also a usage observer.

  2. How does layer M implement real-time updates and stop updates? The answer is: there are two methods provided in the location manager class:

/*
 *  startUpdatingLocation
 *  
 *  Discussion:
 *      Start updating locations.
 */
- (void)startUpdatingLocation API_AVAILABLE(watchos(3.0)) __TVOS_PROHIBITED;

/*
 *  stopUpdatingLocation
 *  
 *  Discussion:
 *      Stop updating locations.
 */
- (void)stopUpdatingLocation;

Copy the code

The location manager object uses these two methods to start and stop locations with real-time updates. This means that the real-time updating and stopping of locations is done by layer M, and how it is done is a black box, so the caller does not need to care about any implementation details.

  1. Who is responsible for calling the methods provided by the M layer? The answer is: controller LAYER C. Since the controller is responsible for building m-layer objects, it is also responsible for calling m-layer methods.

  2. Who will observe m-layer data change notifications and act accordingly? The answer is: controller LAYER C. Since layer C is responsible for calling methods provided by layer M, it is also responsible for handling method returns and updates. Here we C layer of the controller needs to realize the CLLocationManagerDelegate interface, and assigned to the position manager delegate attributes of objects.

Have you ever had a similar feeling about the location manager’s Delegate notification mechanism? Sure enough, UITableView also uses this mechanism to implement the interaction and data update between controller C and view V. The UITableView also specifies a dataSource and delegate object for interface update notifications, and also provides a reloadData method for interface updates.

We know that layer C is a key player in the MVC structure that coordinates and schedules layers M and V. The interaction and coordination between C and M, as well as between V, is mostly achieved through the Delegate mode. The Delegate mode is not limited to M and C, but can also be applied between V and C. The essence of a Delegate is an interface for two parties to communicate, and communicating through an interface minimizes the coupling of the interaction between objects. Here’s a classic picture of the Delegate interface:

Block Indicates asynchronous notification

In addition to using a Delegate, we can also use Block callbacks to implement asynchronous notification handling of method calls. The standard format is as follows:

typedef void (^BlockHandler)(id obj, NSError * error); Return value method name :(parameter type) parameter 1... Other parameters... Callback :(BlockHandler) callbackCopy the code

This can be represented as calling a method and specifying a block callback to handle the asynchronous return of the method. When defining asynchronous methods in block mode, the following rules must be met:

  1. BlockHandler has two fixed arguments: one is an object returned by an asynchronous method, which can return different objects based on different methods. One is that an NSError object returns an error from an asynchronous access.

  2. Take the block callback processing as the last argument to the method.

  3. It is not recommended to have two block callbacks in a method: one correct and one failed. For example:

typedef void (^ SuccessfulBlockHandler)(id obj); Typedef void (^ FailedBlockHandler)(NSError *error) return value method name :(parameter type) parameter 1... Other parameters... Successful callback :(SuccessfulBlockHandler) successful callback failed callback :(FailedBlockHandler) failed callbackCopy the code

Implementing two blocks to handle success and failure separately can lead to more code and unnecessary redundant code. Such as:


-(void)ClickHandle:(UIButton*)sender
{
      sender.userInteractionEnabled = NO;
       __weak XXXVC  *weakSelf = self;

      [user login:@"jack"  
       successful:^(id obj){
         if (weakSelf == nil)
            return; sender.userInteractionEnabled = YES; } failed:^(NSError *error){// It is inevitable to add duplicate code here.if (weakSelf == nil)
              return; sender.userInteractionEnabled = YES; // Handle the failure logic. }];Copy the code

The landmark parser class CLGeocoder in CoreLocation.framework uses block callbacks to implement asynchronous notifications. Let’s look at some of the class definitions:

// geocoding handler, CLPlacemarks are provided in order of most confident to least confident
typedef void (^CLGeocodeCompletionHandler)(NSArray< CLPlacemark *> * __nullable placemarks, NSError * __nullable error);

@interface CLGeocoder : NSObject

// reverse geocode requests
- (void)reverseGeocodeLocation:(CLLocation *)location completionHandler:(CLGeocodeCompletionHandler)completionHandler;

@end
Copy the code

As you can see from the above method, when you need to parse a CLPlacemark landmark object from a CLLocation location object, you need to create a CLGeocoder landmark parser object, The corresponding reverseGeocodeLocation method is then called and a block object is specified to handle the asynchronous return notification. The specific code is as follows:

/ / VC a click on the button in the event: - (void) ClickHandle: (UIButton *) sender {sender. UserInteractionEnabled = NO; __weak XXXVC *weakSelf = self; // Geocoder can also be a property in XXXVC to avoid creating CLGeocoder *geocoder = [CLGeocoder new]; / / assume that know a location object location [geocoder reverseGeocodeLocation: location completionHandler: ^ (NSArray < > CLPlacemark * * placemarks, NSError * error)){if (weakSelf == nil)
               return;
          sender.userInteractionEnabled = YES;
         if(error == nil) {// Process the placemarks object}else{// handle error}}]; }Copy the code

The mechanism of notifies the caller of an asynchronous update via a block callback for a request in an M-layer object is one I prefer. As a rule, any method call involving an M-layer object should follow the standard block callbacks whenever possible. For example, I define a class with many methods in it:

@interface ModelClass -(void)fn1:(parameter type) parameter callback:(BlockHandler)callback; -(void)fn2:(parameter type) parameter callback:(BlockHandler)callback; -(void)fn3:(parameter type) parameter callback:(BlockHandler)callback; . @endCopy the code

The above method implementation and invocation mechanisms look uniform and standardized. This gives the user a very understandable and clear feeling. Here you might ask, do I have to follow this pattern if a method doesn’t have any asynchronous actions?

My answer is to follow the same pattern as much as possible. It is possible that this method will one day go from synchronous to asynchronous. So when the method is implemented from synchronous to asynchronous, we need to change the C layer code, and also change the definition of M method for example:

Fn is a synchronous implementation without block mechanism:

//C layer call XXXX *mObj = [XXXX new]; id retObj = [mObj fn]; // Process the retObj object..... // implementation XXXX -(id)fn{// For example, there is only access to the local cache file, no network requests and asynchronous callsreturnAn object; } @endCopy the code

Once the requirements change, FN needs to change from reading the local cache to requesting the network and calling it asynchronously. Your C layer will need to be rewritten:

XXXX *mObj = [XXXX new]; [mObj fn:^(id retObj, NSError *error){// Handle retObj objects.}]; . @implementation XXXX -(void)fn:(BlockHandler)callback {// Request network. Callback (retObj, error) is called asynchronously after the network returns; } @endCopy the code

What if we started with a standard block approach?

@implementation -(void)fn:(BlockHandler)callback {} / / there is direct call the callback method can}... //VC call method: XXXX *mObj = [XXXX new]; [mObj fn:^(id retObj, NSError *error){// Handle retObj objects.}];Copy the code

It can be seen from the above that once fn’s processing needs to be changed to network access request, you will find that you only need to adjust the implementation mechanism of FN of XXXX, while the method in VC controller remains unchanged. Is this a very good result?

The last thing I want to say is that the mechanism of making all methods of m-layer objects asynchronous and block is not absolute, and it depends on your business scenario and circumstances.

Block asynchronous notification and Delegate asynchronous notification comparison

We can see that Apple’s core location library uses two different methods to implement asynchronous notification. So what are the pros and cons and differences between the two? Which approach should we use in which situation? Here we can sum up a few points for your reference:

  • If you have multiple methods in a class, each of which performs a different function, and the asynchronous return of the method is strongly associated with the method, then you should consider using blocks instead of delegates.

  • If the asynchronous method of a method in a class is one in which each interaction results in a different result, the result has no relation to the last result. In layman’s terms, you should consider using blocks instead of delegates.

  • If we call a method in a class and we set some context before the call, and then we want to process the result returned asynchronously from that context after the call, then we should consider using a block instead of a Delegate.

  • If we call a method in a class and the result doesn’t need to be associated with the context, consider using a Delegate instead of a block.

  • If you want to observe a property change in a business class in real time, you should consider using a Delegate rather than a block.

  • Consider using a Delegate rather than a block if asynchronous notifications in a business class can be divided into several steps.

KVO Asynchronous notification mode

You’ve seen how you can implement update listening for business logic and notification handling of method returns using the Delegate and block mechanisms. These two models are essentially observer mechanisms. Using delegates and blocks also has some drawbacks, based on the principle that every coin has two sides:

  • The Delegate approach must define an interface protocol in advance, and both the caller and the implementer must follow the interface rules for notification and data processing interactions, thus creating a certain degree of coupling. That is, there is an implicit form of dependency between the two. It is not conducive to scaling and fully customizing processing.

  • The disadvantages of the block approach are the problem of circular references and memory leaks, as well as the difficulty of debugging and tracing problems after errors with the block mechanism. The block mechanism also requires a standard BlockHandler interface to be defined in advance between the caller and the implementation for interaction and processing. Another drawback of the block mechanism is that it creates multiple nesting in your code, which can affect the aesthetics and readability of your code.

  • The Delegate and block methods, while both observer implementations, are not standard and classic observer patterns. Because these two modes cannot achieve multiple observers. That is to say, when data update is notified, only one observer can monitor and process, and multiple observers cannot be notified and updated.

What if we need to implement changes that can be received and processed by multiple observers? The answer is to use KVO or the Notification mechanism described below. Let’s start with the KVO mechanism.

The KVO mechanism is also a notification update processing mechanism that can be used for business invocation. The advantage of this mechanism is that the coupling between the business object and the observer is completely decoupled, and the notification of data changes is handled by the system without additional code and logic, and multiple observers can listen for changes in a piece of data simultaneously:

Unfortunately, the current iOS location library does not support KVO. The following is just a hypothetical scenario of what would happen if the location library supported KVO. Again, take the iOS location library as an example. If in practice multiple VC pages need to listen for position changes. So one way is to us in every VC page to create a CLLocationManager position management object, and then implement the corresponding CLLocationManagerDelegate protocol, and then call startUpdatingLocation for listening, And in the corresponding methods didUpdateLocations CLLocationManagerDelegate protocol to deal with position update data. One obvious problem is that we need to create multiple CLLocationManager objects and call startUpdatingLocation multiple times. Although we don’t know how CLLocationManager is implemented, it always feels that this multi-call mechanism is not the optimal solution. We can instead create a singleton CLLocationManager object, And in the place such as AppDelegate didFinishLaunchingWithOptions created inside the singleton object and call startUpdatingLocation method for listening. Listen for the location property of the CLLocationManager singleton via KVO in the VC page that needs to handle real-time update notifications. This property is listened for by KVO whenever you enter a page that you want to listen on, and is unlistened for when you exit the page. The multi-observer approach is now fully implemented, eliminating the need to define and implement the delegate protocol. The specific code is as follows:

// Again, CCLocationManager does not support KVO to listen for position changes. @interface AppDelegate @property(nonatomic, strong) CLLocationManager *locationManager; @end @implementation AppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.locationManager = [CLLocationManager new]; [self.locationManager startUpdatingLocation]; // Start listening for position changesreturnYES; } @implementation VC1 -(void)viewWillAppear:(BOOL)animated {[[UIApplication] sharedApplication].delegate.locationManager addObserver:selfforKeyPath:@"location" options:NSKeyValueObservingOptionNew context:NULL];
}


-(void)viewWillDisappear:(BOOL)animated
{
      [ [UIApplication sharedApplication].delegate.locationManager  removeObserver:self  forKeyPath:@"location"]; } - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context {// } @implementation VC2 -(void)viewWillAppear:(BOOL)animated {[[UIApplication] sharedApplication].delegate.locationManager addObserver:selfforKeyPath:@"location" options:NSKeyValueObservingOptionNew context:NULL];
}


-(void)viewWillDisappear:(BOOL)animated
{
      [ [UIApplication sharedApplication].delegate.locationManager  removeObserver:self  forKeyPath:@"location"]; } - (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context {// } @end //.. Other pagesCopy the code

So when do we use KVO to implement asynchronous notification callback? Here are a few summaries for your reference:

  1. An update to the same data on an object may cause multiple objects that depend on that object to be updated and changed.

  2. It is not recommended to use KVO if the lifetime of an object is shorter than that of the observer, as this can cause the system to crash with significant impact.

  3. A certain attribute of an object has a variety of states, different pages in different states of processing logic and presentation will be different, and the state of the object is constantly changing. This is a very common state machine scenario. For example, the status of an order will keep changing, and the login status of a user will keep changing. Many people in such state-machine implementations build an object when they get to the page, then call the corresponding state-fetching method from the server, and then do different things based on the current state. Take an order as an example: suppose our application logic can only process one order at a time, and this order will be accessed by different pages, each page needs to be processed differently according to the current state of the order. Here’s an example:

In the figure above we can see that the same order object creates copies between different pages, and thus the state creates copies. As the number of copies increases, we need a mechanism to uniformly update the state properties in those copies and to handle the change based on the latest state. Maintenance difficulties (data inconsistencies) are obvious due to the increase in duplicates. So how to solve this problem? Since our business scenario is that there can only be one order at a time, we should change this order object to a single existence pattern. We can pass the order object between pages, or we can design the order object as a singleton pattern. Then we use KVO mechanism to realize that when the state changes all the state-dependent pages are processed.

Notification Indicates the asynchronous Notification mode

The KVO pattern implements a mechanism for notifying and observing property changes. And this mechanism is done by the system, with the disadvantage that it can only observe property changes and cannot notify some asynchronous method calls. And what if we really wanted to implement the observer pattern, not just properties? The answer is the iOS NSNotificationCenter. That is to say, in addition to the Delegate and Block methods for asynchronous method notification callback, we can also use NSNotificationCenter method notification callback, and this mechanism can achieve multiple observers at the same time application scenarios.

If notification is so good, why not recommend it? The answer is that the mechanism is too loose. Although it solves the problem of multiple observers, the result of being too loose is that there is a certain learning cost for users. We know when the design method of the business layer by the Delegate or block time correction, can clearly know that business calls the method and implementation mechanism of the context, because these things are defined in the code has been fixed, and when using these methods are also very clear understanding of how to use a method, how to call him the most suitable. But what about NSNotificationCenter? It’s completely loose and context-free, and we have to learn extra about which business layer methods need to add observers and which don’t, and add a piece of code to the initialization whenever it’s needed. The read-write of the notification processing logic and the readability of the code are also poor. Here is the sample code.

Implementation VC -(void) viewWillAppear:(BOOL)animated {// here you have to add some observers up front to handle context-unaware events [[NSNotificationCenter] defaultCenter] addObserver:self selector:@selector(handleA:) name:@"A" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleB:) name:@"B" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleC:) name:@"C"object:nil]; CLLocationManager *locationManager = [CLLocationManager new]; self.locationManager = locationManager; [locationManager startLocationUpdate]; } -(void)viewWillDisappear:(BOOL)animated { [[NSNotificationCenter defaultCenter] removeObserver:self]; [self.locationManager stopLocationUpdate]; } @end // There is no context, so it is not clear what the callback does. -(void)handleA:(NSNotification*)noti { } -(void)handleB:(NSNotification*)noti { } -(void)handleC:(NSNotification*)noti { }Copy the code

conclusion


Above is the design method of the model layer, and has carried on the comprehensive introduction should follow some rules, based on the location of library based on iOS to deconstruction, when designing a business layer, should first have to be careful analysis and understanding of business, and then build a class diagram, this static frame design, You need to divide the roles and responsibilities of the classes, which should be designed as data model classes and which should be designed as business classes. Then design the properties that a class should have. Finally, the methods provided in the class are designed, because most of the methods provided by the model layer have asynchronous properties, so it is necessary to choose the most appropriate asynchronous invocation notification model. Of course, these are only the first steps in the design of the business model layer, but how should our implementation of the business model layer be designed and coded? I will continue to write about specific methods and practices for a business model layer in the coming days. Stay tuned.


Finally, welcome to visit my Github site and follow Ouyang Big Brother 2013