background

Recently I have been studying some scenarios of componentization. Here is a collection of digestion, share for your reference.

Componentization is a concept at the architecture level. It divides a project into several units with smaller granularity according to certain rules (such as by function and by business), which are called components or modules to optimize the project structure.

Components can be subdivided into functional components (e.g., photo library, network library), business components are also called modules (e.g., order module, personal center module)

Functional components are mainly physical layer split, convenient reuse later

Business components emphasize logical separation for decoupling

The development of componentization

At the beginning of development, we will divide the project into basic layer, network layer, data layer, etc. At the business level, we just make a simple module layer according to the directory structure, such as order module, personal center module

Note: Since functional components are relied upon by most business modules, the architecture of functional modules is not discussed here.

With the development of the business, the project becomes more and more complex, and the coupling between various businesses in the APP becomes serious and the boundary becomes more and more blurred. It is often the case that there are two parts in each other, as shown in the figure

It can be seen that the serious coupling between modules has a huge impact on the expansion of the code and the development efficiency of the code. There is a feeling that one part changes the whole body. At this stage of development, we will separate the modules and use intermediaries to complete the interaction between the different modules. As shown in figure

At this point, the architecture looks much clearer. But because the intermediary still relies on the business module in reverse, and there are still cases where multiple modules are affected in one place, the dependency is still bi-directional. Let’s take an example:

If I am in the membership module and want to jump to the goods module, the membership module needs to go through the intermediary to complete the jump

// Membership module
[self.Intermediary gotoGoodsModuleWithParam:param]

// Among the intermediaries
- (void)gotoGoodsModuleWithParam:(id)param {
    self.goodsModule.param = param; / / parameters
   [self.memberModule.navigationController pushViewController:self.goodsModule] 
}
Copy the code

If our requirements change and we need to pass an additional parameter to determine the hidden or background color of the navigation bar, then we need to modify the structure of the gotoGoodsModuleWithParam function. In addition to modifying the method in the intermediary, You also need to modify where this function is used in the module.

In fact, interdependence is a big taboo when developing, we also need to consider the problem of circular reference module, development efficiency, reuse and so on

What about eliminating the dependency of intermediaries on modules? As shown in figure:

I think it smells like that. Each module is non-interference, clear responsibilities and boundaries.

So how do you implement this functionality? Here’s a look at some of the technology proposals in the industry.

Industry componentization scheme

  1. UI page hop management based on routing URL
  2. Reflection – based interface call encapsulation
  3. Service registration scheme based on protocol – oriented idea
  4. Notification based broadcast scheme

UI page hop management based on routing URL

General usage

// kRouteGoodsDetail = @"/goods/goods_detail"
UIViewController * vc = [Router handleURL:kRouteGoodsDetail];
if (vc) [self.navigationController pushViewController:vc animated:YES];
Copy the code

The situation of parameter transfer

/ / kRouteGoodsDetails = @ "/ / goods/goods_detail? Goods_id = % d"
NSString *urlStr = [NSString stringWithFormat:kRouteGoodsDetails, 123];  
UIViewController *vc = [Router handleURL:urlStr];  
if(vc) {  
   [self.navigationController pushViewController:vc animated:YES];
}
Copy the code

Complex parameter types

+ (nullable id)handleURL:(nonnull NSString *)urlStr
           complexParams:(nullable NSDictionary*)complexParams
              completion:(nullable RouteCompletion)completion;
Copy the code

As you can see from the interface, with the @”/goods/goods_detail” string, we can take the module we want to interact with and operate on it. If you look at Bifrost, it looks easy if you have some knowledge. That’s just one way of thinking, of course.

In the Demo, the author gathered each module in a main project in the form of sub-projects, and completed the binding of string and route in the form of bindURL on the page requiring routing service.

However, routing bindings in the LOAD method can have an impact on the cold start of a project, and binding all business functional components will inevitably result in unnecessary memory residency.

Of course, the Youzan team has done a lot of testing and thinking, and the final plan is definitely trustworthy.

Reflection – based interface call encapsulation

OC is known to support reflection, for example:

Class className = NSClassFromString(@"Person");
SEL sel = NSSelectorFromString(@"getPersonName:"); .Copy the code

And then you can do that by saying – (id)performSelector:(SEL)aSelector.

However, there is a lot of hard coding in this approach, and the auto-completion of the compiler cannot be triggered. At the same time, some unknown errors can only be found at runtime. In addition, the problem of multi-parameter value transmission and method return value acquisition cannot be realized. So NSInvocation is more appropriate here

Check out the CTMediator open source library in the industry. Mediator mode and Target-Action mode are used to accomplish this process

First, call methods: (Live: this time only discusses calls between local modules, not remote calls)

Local component A calls [[CTMediator sharedInstance] performTarget:targetName Action :actionName params:@{…}] somewhere to initiate A cross-component call to CTMediator, CTMediator generates target instances and corresponding action options through The Objective-C Runtime transformation based on the obtained target and action information, and then finally invokes the logic provided by the target business to fulfill the requirements.

The component only needs to expose the callable interface through Action. All components respond through their own target-action, that is to say, the interface between modules is solidified in the target-Action layer, which avoids the intrusion of Business in the implementation of componentized transformation and improves the maintainability of componentized interface.

Finally, the caller initiates the call through the category or extension created by the responder for CTMediator to avoid the unfriendliness of string calls.

And if you look at the code, it’s pretty neat. Very thorough consideration. For some architectural ideas, check out Casatwy’s article on iOS application architecture: Componentization solutions.

Simplify the code as follows:

// Mediator provides a unified entry to the NS-based Interface invocation method
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params;

// The methods provided by the business module are encapsulated in a Category
@interface CTMediator (Goods)
- (NSArray *)goods_getGoodsList;
- (void)goods_gotoGoodsDetail:(NSString *)id; .@end

@impletation CTMediator(Goods)

- (NSArray *)goods_getGoodsList{
    return [selfPerformTarget: @ "Target_Goods" action:@"getGoodsList" params:nil];
}
- (void *)goods_gotoGoodsDetail:(NSString *)id{
    return [selfPerformTarget: @ "Target_Goods" action:@"gotoGoodsDetail" params:{@"id":id}];
}

@interface Target_Goods : NSObject

- (NSArray *)getGoodsList;

- (void)gotoGoodsDetail:(NSString *)id;

@end

Copy the code

Service registration scheme based on protocol – oriented idea

Each module provides its own service protocol and then registers this protocol declaration with the middle tier. The caller can see the interfaces of the services from the middle tier and invoke them directly.


// Complete the protocol declaration in middleware, convenient for all modules to call and refer to
@protocol GoodsModuleService
- (NSArray*)getGoodsList;
- (NSInteger)getGoodsCount; .@end

// In the load method of the module, register the protocol. And let the module implement the methods in the protocol
@interface GoodsModule : NSObject<GoodsModuleService>
@end
@implementation GoodsModule 
+ (void)load {
    [ServiceManager registerService:@protocol(GoodsModuleService) withModule:self.class]
}
// Provide a concrete implementation
- (NSArray*)getGoodsList {... } - (NSInteger)getGoodsCount {... }// called in another module
id<GoodsModuleService> goodsModule = [ServiceManager objByService:@protocol(GoodsModuleService)];

NSArray*list = [goodsModule getGoodsList]; .Copy the code

Protocol oriented programming looks cool. And you don’t need to write reflection code.

However, if the contents of the protocol are put in the public place, any changes will mean that the protocol will have to be changed again. Moreover, the protocol binding in the load method will still have the same old problems.

The likes team tested the startup time spent on service-based registration, and the answer was that the impact was negligible.

Notification broadcast scheme

The communication scheme between modules based on notification is the simplest to implement, which can be directly based on NSNotificationCenter provided by the system. Suitable for one-to-many communication scenarios. But the disadvantages are also particularly obvious. Complex data transfer, synchronous call and other methods are not very convenient. Often used as a supplement to the above schemes.

conclusion

Componentization is an option when the project reaches a certain level. If you don’t consider the cost of time and technology, you can do it. Although the technical barrier was high at the beginning, it was very helpful for subsequent iterations and upgrades.

One more word. When we build architectures, we must be based on our own actual projects. Many excellent ideas in the industry can be referred to, and there is no need to copy them mechanically. Because there is no absolutely right architecture, only the most suitable architecture for you.

The above is some arrangement and thinking of componentization, I hope to be helpful to you.