preface

Speaking of componentization, we should all be familiar with it, but also mention that, due to the complexity of business expansion, the coupling degree between each module is getting higher and higher, not only resulting in the embarrassing situation of “dragging the whole body”, but also increasing the repetitive engineering of the test, at this time, componentization is worth considering. Componentization is to split APP into components (or modules), decouple these components, and then combine the components required by the project through the routing middleware. The benefits of this are:

  • Decoupling, enhanced portability, no need to introduce a large number of other business header files in their own business module.
  • Improve reusability. If other projects have similar functionality, just introduce the module and make some modifications to make it usable.
  • Reduce testing costs and eliminate the need for extensive regression testing when modifying or iterating a widget.

There are many programs about componentization on the Internet, the most widely spread is mogujie componentization technology program and iOS application architecture talk about componentization program here is not the bigwigs of the program arbitrary evaluation, interested students can have a look. Let’s talk about another Protocol Moudle approach

Train of thought

In iOS, protocols define a programmatic interface that all classes can choose to implement. It is mainly used to define a set of rules for communication between objects. Protocol is also one of the things that we often use in design, and protocol tends to be more composite than direct inheritance. It enables two unrelated classes to communicate with each other to achieve specific goals.

In the ResponderChain+Strategy+MVVM implementation of an elegant TableView we used protocol to provide a common method for the View:

- (void)configCellDateByModel:(id<QFModelProtocol>)model;

Provide common methods for models:

- (NSString *)identifier;
- (CGFloat)height;
Copy the code

Then we can also build a lightweight routing middleware, define a set of communication rules for each component, manage and maintain their respective modules, and provide necessary interfaces externally.

practice

Let’s take a look at the Demo structure and how it works

routing

Ok, let’s look at some of the details of routing. It only needs to provide two key things:

  1. Provides router singletons
  2. Get the corresponding Moudle
    • Obtain from Protocol
    • Get by URL

First provide singletons:

+ (instancetype)router {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _router = [[self alloc]init];
    });
    return _router;
}
Copy the code

You can use [QFRouter Router] and [[QFRouter alloc]init] and [router copy] to see if they can generate three instances with different memory addresses. You might say, who would be bored doing that? But wouldn’t it be better if the design could be more rigorous in avoiding such pits? So how do you make a rigorous singleton? It is possible to override its alloc and copy methods to avoid creating multiple instances, as you can see in detail in the Demo project, which will not be expanded here.

Getting back to business, let’s see how to get the Module

Through the reflection mechanism of Runtime, we can get a class from NSString and create the corresponding object, and Protocol can get an NSString. So can we start from this? The answer is yes:

- (Class)classForProtocol:(Protocol *)protocol {
    NSString *classString = NSStringFromProtocol(protocol);
    return NSClassFromString(classString);
}
Copy the code

If you pass in a protocol, you can get the corresponding Module’s class, and then you can get the corresponding Module’s object through class.

Obtain the corresponding Module from Protocol or URL:

#pragma mark - Public
- (id)interfaceForProtocol:(Protocol *)protocol {
    Class class = [self classForProtocol:protocol];
    return [[class alloc]init];
}

- (id)interfaceForURL:(NSURL *)url {
    id result = [self interfaceForProtocol:objc_getProtocol(url.scheme.UTF8String)];
    NSURLComponents *cp = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
    [cp.queryItems enumerateObjectsUsingBlock:^(NSURLQueryItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [result setValue:obj.value forKey:obj.name]; / / set KVC}];return result;
}

Copy the code

One flaw is that the caller (external component) needs to know the name of the protocol exposed by the target component, but since it is a protocol, it should not be a big problem.

id <MoudleHome>homeMoudle = [[QFRouter router]interfaceForProtocol:@protocol(MoudleHome)];
Copy the code

This gets the corresponding target component instance, which can be passed through the exposed property, and the callback argument through its callback block.

WayTwo:

id <MoudleMe>meMoudle = [[QFRouter router]interfaceForURL:[NSURL URLWithString:@"MoudleMe://? paramterForMe=ModuleMe"]].Copy the code

This is passed in through the URL, and its property value is set through KVC. Similarly, callback parameters can be obtained through its callback block.

Common protocol

Protocol can be used to retrieve component instances. Where is the Protocol? How do you manage it? In daily development, the most common cross-component interaction scenarios are: Jumping from component A with parameters to A page of component B, doing something in that page of component B, and then back to component A (with or without callback parameters), our protocol should be able to handle the two most common and basic operations, so we define two properties for protocol:

typedef void(^QFCallBackBlock)(id parameter);

#pragma Mark - Base protocol@protocol QFMoudleProtocol <NSObject> @property (nonatomic, weak) UIViewController *interfaceViewController; /// @property (nonatomic, copy) QFCallBackBlock callbackBlock; @endCopy the code

Why is the interfaceViewController declared weak? I’ll leave that for now, and we’ll get to that later.

With these two properties we can do the corresponding jump and parameter callback, but how do we forward the value?

The corresponding attributes should also be required to do the input parameters, but as many components, as many input parameters, if all the positive attributes are written in this protocol, then over time and business growth, the protocol may be very messy and bloated.

Therefore, we define this protocol as the base protocol from which the corresponding components inherit, and then define their required input attributes:

Home page components:

#pragma Mark - "Home page" component@protocol MoudleHome <QFMoudleProtocol> @property (nonatomic, copy) NSString *paramterForHome; @property (nonatomic, copy) NSString *titleString; @property (nonatomic, copy) NSString *descString; @property (nonatomic, weak) UIViewController *detailViewController; @endCopy the code

As you can see, the home page component needs to expose a home page QFHomeViewController and a detail page QFDetailViewController so there are a few more parameters.

My component:

#pragma Mark - "my" component@protocol MoudleMe <QFMoudleProtocol> @property (nonatomic, copy) NSString *paramterForMe; @endCopy the code

The “my” component, on the other hand, only provides a QFMeViewController page with relatively simple parameters.

In this way, the protocol processing is basically reached, but the inevitable problem is: the protocol of each component is defined in this common protocol, so it needs to be visible to multiple development teams. I feel that this is also a common problem in the process of componentization, and no good solution has been found.

Module

So we said that the open Protocol defines some properties, like the interfaceViewController so who provides those properties? Yes, it is Module, through the above steps we can obtain the corresponding Module instance, but we need to jump to the Controller, so, in this case, we need the help of Module, Module through the public protocol defined properties to provide external Controller interface:

- (UIViewController *)interfaceViewController {
    QFHomeViewController *homeViewController = [[QFHomeViewController alloc]init];
    homeViewController.interface = self;
    interfaceViewController = (UIViewController *)homeViewController;
    return interfaceViewController;
}
Copy the code

Since the Module is in the corresponding component, it can reference the internal header file of its own component to complete initialization. The corresponding controller needs external parameters, so the Module instance is also exposed to the corresponding controller instance. Namely homeViewController. Interface = self; The things that you do.

———— breaks the loop of strong references. If you want to use weak, you need to use it.

The Module is decoupled from the common protocol, and the Module completes the work of bridging inside and outside the component. Thus, value transfer, invocation and parameter callback can be realized across components. See QFMoudle for more details.

Due to time, how to make private library is no longer described, there is a need to welcome the message, we together hand by hand to create a pod private library that belongs to you.

Afterword.

In this componentized practice, the corresponding components, routes, and public protocols are made into a POD private library, which is the only public library that multiple teams need to know about. All protocols are put into the public library, and only the library needs to be updated with each update.

If the team size is small and the business complexity is low, it is not recommended to do a private library, because:

  1. Its modification -> upgrade -> integration -> test is a time-consuming process, maybe a small change (such as changing a color, changing the background) needs to consume multiple times of time.
  2. Increase the call complexity of the project and increase the familiarity cost of new members.

Everything is double-sided, pros and cons, hope you see their own choice. At last, due to my limited writing, I’m really sorry if I have caused any trouble to you. If you have any questions or comments or suggestions, welcome to exchange and discuss. Of course, if it is useful to you, I hope to give you a Star and click on your attention. Give rose, hand left lingering fragrance, your support is my biggest power!