background

Componentization, as one of the mainstream methods of mobile application architecture, has been actively explored and practiced by the industry in recent years.

In the beginning of the project, the App is only one product line, code logic is relatively clear, along with the rapid development of the company, the late App hosted inside now probably WuLiuTiao product lines, each process is part of the product line is the same, also has the part is not the same, it is need to do all kinds of judgment and customized requirements. After more than a year of work, there were demands from different product lines, and the developers all needed to develop in the main project. However, the developers were developing different product lines and had to run the whole project, with obvious limitations on code management, parallel development efficiency, branch management and online time. At the end of last year, our leader put forward this problem, hoping to make it componentalization, refactoring the code into modules, assembling the modules in the main project, and forming a complete App.

  1. Different from functional modules/components (e.g., photo library, network library), this article discusses the architecture design related to business modules/components (e.g., order module, commodity module).
  2. I think it’s more appropriate to call it a Module than a Component. Components emphasize physical separation for reuse; Modules emphasize logical separation for decoupling. And if you’ve used Android Studio, you’ll notice that it creates subsystems called Modules. But because the industry is used to calling it componentization, we continue to use that term. The term “module” is the same as “component” as used in the rest of this article.

The body of the

First knowledge of componentization

Traditional App architecture design emphasizes layering. Based on the principle of single responsibility, one of the six principles of design mode, the system is divided into basic layer, network layer, UI layer and so on, so as to facilitate maintenance and expansion. But as businesses grow and systems become more complex, layering is no longer enough. The coupling between subsystems in App is serious, and the boundary is becoming increasingly blurred, which often leads to interdependencies (as shown in Figure 1).

The architectural logic is much clearer at this point, but because the intermediary still needs to rely on the business module in reverse, this does not fundamentally eliminate the problem of round-robin dependencies. From time to time, a module changes and multiple modules fail to compile. Further, through technical means, the intermediaries’ dependence on business modules is eliminated, that is, business modular architecture design is formed (Figure 3).

Through the business modular architecture, the responsibilities and boundaries of modules can be defined, code quality can be improved, complex dependencies can be reduced, compilation speed can be optimized, development efficiency can be improved and other effects can be achieved. Many articles have related analysis, which will not be repeated here.

Disadvantages of componentized development:

  • Serious code coupling
  • Depend on the serious
  • It is difficult to integrate other apps into a product line
  • Projects are complex, bloated, large, and take too long to compile
  • Integration testing is difficult
  • For developers, only the same development pattern can be used

Advantages of componentized development:

  • Clear project structure
  • Clear code logic
  • Small resolution size
  • Rapid integration
  • Ability to do unit tests
  • High code utilization
  • High iteration efficiency

2. Common componentization schemes

Business modular design can improve the efficiency and quality of development by decoupling the business modules and avoiding bidirectional dependence. However, the dependency of business requirements cannot be eliminated, so the modular solution first needs to solve the problem of how to achieve cross-module communication without code dependency. IOS makes it easy to do this because of its powerful runtime features, whether based on NSInvocation or on the Page Selector method. However, we can not decouple for the sake of decoupling. It is our purpose to improve quality and efficiency. Code that is directly based on hardcode string + reflection is obviously a huge detriment to development quality and efficiency, which is the opposite of the goal. Therefore, a more accurate description of module decoupling requirements would be “how to achieve cross-module communication without code dependency while ensuring development quality and efficiency”. At present, common inter-module communication schemes in the industry are as follows:

UI page hop management based on routing URL. Reflection – based encapsulation of remote interface calls. Service registration scheme based on protocol – oriented idea. Notification based broadcast scheme. Most companies use one or a combination of these, depending on their business and needs.

2.1 URL Forward Scheme

Hop routing is the most common method of page decoupling and is widely used in front page. By binding a URL to a page, you can easily open the corresponding page through the URL when needed.

/ / by routing the URL / / jump to the commodity list page kRouteGoodsList = @ "/ / goods/goods_list" UIViewController * vc = [the Router handleURL: kRouteGoodsList]; if(vc) { [self.navigationController pushViewController:vc animated:YES]; }Copy the code

Of course, some scenarios are more complex than this, such as pages that require more parameters. Basic types of parameters that URL protocol naturally supports:

/ / 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

ComplexParams, which provide an additional dictionary parameter, place complex parameters in the dictionary:

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

Copy the code

The completion parameter in the above method is a callback block that handles scenarios where opening a page requires a callback function. For example, open the page of member selection, search for members, and click OK to return the member data:

//kRouteMemberSearch = @" //member/member_search "UIViewController *vc = [Router handleURL:urlStr complexParams:nil completion:^(id _Nullable result) { //code to handle the result ... }]; if(vc) { [self.navigationController pushViewController:vc animated:YES]; }Copy the code

For implementation flexibility, pages that provide routing services bind urls to a block. Put the required initialization code in the block. You can bind initialization blocks to the route URL where appropriate, such as in the +load method:

+ (void)load {
    [Router bindURL:kRouteGoodsList
           toHandler:^id _Nullable(NSDictionary * _Nullable parameters) {
        return [[GoodsListViewController alloc] init];
    }];

Copy the code

For more examples of routing urls, see the Demo in the Bifrost project.

URL itself is a generic protocol that spans multiple ends. The advantages of using routing URL hopping scheme are dynamic and multi-terminal unification (H5, iOS, Android, Weex/RN). The disadvantage is that the interaction scenes that can be processed are relatively simple. Therefore, it is generally more suitable for simple UI page jumps. Some complex operations and data transfer, although possible through this method, are not very efficient.

At present, Both Tmall and Mogujie use routing URLS as their page hopping schemes to achieve the purpose of decoupling.

2.2 the Target – the Action plan

When you can’t import a class’s header file but still need to call its methods, reflection is the most common way to do it. Ex. :

Class manager = NSClassFromString(@"YZGoodsManager");  
NSArray *list = [manager performSelector:@selector(getGoodsList)];  
//code to handle the list

Copy the code

But there are a lot of Hardcode strings in this way. Failure to trigger code completion is prone to spelling errors that can only be detected at runtime after the relevant methods are triggered. Both development efficiency and development quality have a great impact.

How do you optimize? This is actually a problem that needs to be solved by all remote calls. The most common remote call on a mobile terminal is a network request to a back-end interface. It is easy to think of creating a network layer to encapsulate such “dangerous code”. There is no need for hardcode strings and no need to understand the internal cumbersome logic when upper-layer business calls the network-layer interface.

Similarly, I can encapsulate communication between modules into a “network layer” (or message forwarding layer). In this way, the dangerous code is only stored in a few files, which can be specially reviewed and tested jointly. Later quality can also be guaranteed through unit testing. For modular solutions, this type of “Mediator” can be called a Mediator (although you can call it something else). Also, because the Form Selector method has a limited number of parameters and no return value, it is better to use NSInvocation.

//Mediator provides uniform encapsulation of the NSInvocation remote interface invocation - (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params; @interface Mediator(Goods) - (NSArray*)goods_getGoodsList; - (NSInteger)goods_getGoodsCount; . @end @impleiterator (Goods) - (NSArray*)goods_getGoodsList {return [self performTarget: @" GoodsModule" action:@"getGoodsList" params:nil]; } - (NSInteger)goods_getGoodsCount {return [self performTarget:@ "GoodsModule" action:@"getGoodsCount" params:nil]; }... @endCopy the code

Each business module then relies on Mediator to call these methods directly.

NSArray *list = [[Mediator sharedInstance] goods_getGoodsList];Copy the code

The advantage of this scheme is that it is easy to call and code auto-completion and compile-time checking are still valid. The disadvantage is that categories have the risk of overlapping names, which needs to be circumvent through development specifications and some checking mechanisms. At the same time, Mediator only converges hardcode, but does not eliminate hardcode, which still has a certain impact on development efficiency.

CTMediator open source library and Meituan all adopt a similar scheme.

2.3 Service registration scheme

Is there a way to absolutely avoid Hardcode? If you look at the back-end servitization transformation, you will find that it is very similar to the mobile business modularization. Dubbo is one of the classic frameworks for servitization. It implements remote interface calls through service registration. That is, each module provides its own protocol declaration for external services and then registers this declaration with the middle tier. The caller can see what service interfaces exist from the middle tier and invoke them directly. Ex. :

@protocol GoodsModuleService - (NSArray*)getGoodsList; @protocol GoodsModuleService - (NSArray*)getGoodsList; - (NSInteger)getGoodsCount; . The @end //Goods module provides an object that implements GoodsModuleService, and registers @interface GoodsModule in the +load method: NSObject<GoodsModuleService> @end@implementation GoodsModule + (void)load {// Register service [ServiceManager] RegisterService :@protocol(Service_protocol) withModule:self.class]} // Provide a specific implementation - (NSArray*)getGoodsList {... } - (NSInteger)getGoodsCount {... } @end // Put GoodsModuleService in a public module, visible to all business modules // Business modules can call the relevant interface directly... id<GoodsModuleService> module = [ServiceManager objByService:@protocol(GoodsModuleService)]; NSArray *list = [module getGoodsList]; .Copy the code

The advantages of this approach also include simplicity of invocation. Both code auto-completion and compile-time checking are valid. It is also simple to implement; all implementations of the protocol are still inside the module, so there is no need to write reflection code. At the same time, only the protocol is exposed, which is in line with the idea of “protocol-oriented programming” for team collaboration. The disadvantage is that if service providers and consumers rely on the same protocol in the common module, there is a risk that all service dependent modules will fail to compile when the protocol content changes. A registration process is also required to bind the Protocol to the implementation.

In the industry, mushroom Street ServiceManager and Ali BeeHive are using this scheme.

2.4 Notification broadcast scheme

The communication scheme between modules based on notification is very simple, which can be directly based on NSNotificationCenter of the system. Advantages of simple implementation, very suitable for dealing with one-to-many communication scenarios. The disadvantage is that it only applies to simple communication scenarios. Complex data transfer, synchronous call and other methods are not very convenient. In the modular communication scheme, the notification scheme is more complementary to the above schemes.

Three, componentized development necessary tools

Components exist in the form of each POD library. So the way to combine components is to add and install each component by using CocoaPods. We need to make CocoaPods remote private library, which can not be sent to the company’s GitLab or GitHub, so that the project can be Pod download.

3.1 Git basic Commands:

echo "# test" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/c/test.git
git push -u origin master
Copy the code

3.2 CocoaPods Remote Private Library Production:

  • 1. Create Component Project
pod lib create ProjectName
Copy the code
  • 2, Use the Git
echo "# test" >> README.md
git init
git add README.md
git commit -m "first commit"
git remote add origin https://github.com/c/test.git
git push -u origin master
Copy the code

3. Edit podSpec file

vim CoreLib.podspec
Pod::Spec.new do |s|
  s.name             = 'Component Project Name'
  s.version          = '0.0.1'
  s.summary          = 'summary'

  s.description      = <<-DESC
  description
                       DESC

  s.homepage         = 'Remote warehouse address'
  s.license          = { :type= >'MIT', :file => 'LICENSE' }
  s.author           = { 'the writer'= >'the writer' }
  s.source           = { :git => 'Remote warehouse address', :tag => s.version.to_s }

  s.ios.deployment_target = '8.0'

  s.source_files = 'Classes/**/*.{swift,h,m,c}'
  s.resources = 'Assets/*'
  
  s.dependency 'AFNetworking'.'~ > 2.3'
end
Copy the code
  • 4, Create the tag
//create local tag
git tag '0.0.1'Git tag 0.0.1 //localTag push to remote git push -- Tags or git push origin 0.0.1 //deletelocal tag
git tag -d 0.0.1

//delete remote tag
git tag origin :0.0.1
Copy the code
  • Verify Component Project
pod lib lint --allow-warnings --no-clean
Copy the code
  • Push To CocoaPods
pod repo add CoreLib [email protected]/CoreLib.git
pod repo push CoreLib CoreLib.podspec --allow-warnings
Copy the code

How to split each component

There is no complete standard on how to split components, because the business scenarios of each company are different, and the corresponding derived business modules are also different. Therefore, the split of business components can be reasonably divided according to the business modules of the company. Here we talk about the general division direction of the components of the whole project.

    1. Master project: When our project was fully developed using a componentized architecture, we were surprised to find that our master project was an empty shell. Because all of the main project presentation is broken down into separate business components, including the tools components are also independent of each other. In this way, we find that developing a complete APP is just like building Lego blocks, with all the parts, and we can build them by combination at will. Isn’t it cool?
    1. Business Components: Business components are the individual product business function modules shown in our sample figure above, which we encapsulate as separate components. For example, the electronic invoice business component in sample Demo, business component A, business component B. We build a complete APP project by assembling individual business components.
    1. Base toolclass components: Base toolclasses are independent tool components without any dependencies. They have no dependencies on other tool components, business components, and so on. Examples of such components are the Safe component for exception protection of arrays and dictionaries, the Array component for extending Array functionality, and the encryption component for encrypting strings.
    1. Middleware component: This is a special component that we derived for componentized development. The intermediate scheduler in the example figure above is a functionally independent middleware component.
    1. Basic UI components: View components are more common, such as our encapsulated navigation component, Modal popbox component, PickerView component, etc.
    1. Business tool components: These are components that provide the basic functionality for various business components. Such components may depend on other components. For example: network request component, image cache component, Jspatch component, and so on

As for the granularity of component resolution, this is really not good to determine, vary from person to person, different requirements function complexity split out of the component size is not the same

conclusion

Above we just explained the simple theoretical knowledge, if you want to practice or more information, but at present our APP is the use of component-based development, each module is decoupled, can quickly develop new APP. There are a lot of advantages, I hope you can also have the courage to try. However, componentization, modularization and separation should also be considered for their own projects. Finally, it is what you need to do to find a solution suitable for your own projects. I hope this documentary can provide you with some ideas.

reference

  • Limboy. Me/tech / 2016/0…
  • Github.com/youzan/Bifr…
  • Github.com/meili/MGJRo…
  • www.jianshu.com/p/59c2d2c4b…
  • www.jianshu.com/p/f472fa9f0…
  • Tech.youzan.com/you-zan-ios…