Preface to the zero.

“Hummingbird Distribution Merchant Edition” is a professional distribution software designed for merchants. With this application, you can use hummingbird Distribution to call all platform orders and telephone orders for distribution, including catering, flowers, cakes, fresh food and supermarkets. Ultra low freight, clear and reasonable. Massive subsidies, recharge and cashback.

The above description of “Hummingbird Merchant Edition” is taken from hummingbird distribution website, probably can be interpreted as hummingbird Merchant Edition is an App for the majority of merchants to issue orders to call deliverers. Many students may have only heard of the food delivery application “Ele. me”, but they know little about the logistics service “Hummingbird Delivery”, which supports food delivery. In fact, a large number of food orders are processed and delivered to consumers by hummingbird delivery system every day. Takeout O2O is completed by the cooperation of takeout platform, merchants and distribution system. The core value of O2O is the connection between people and services, which is finally realized through distribution.

Since the end of 2016, I have been involved in the maintenance of hummingbird business edition. In addition to the daily development iterations, I have also participated in the promotion of Swift, componentization/modularization of the project, open source of non-business components and other technical transformation. Today, this article will share with you the componentization/modularization practice process and my own experience of the Bee merchant version of iOS.

I. Background analysis

The iOS code of Hummingbird Merchant edition is managed by Git, and the code is hosted on the GitLab on the Intranet. The dependency management tool of the project is the familiar CocoaPods. Except for RN modules, which are managed by Submodule for common use with Android group, all other submodules are introduced by Pods library.

1. Existing problems

Before the work of “Hummingbird Business Edition iOS componentization/modularization” was carried out, the project mainly had the following problems:

  • Bloated projects

Before componentation/modularization, all codes/resource files of Hummingbird Merchant App are in the same main project, and only a few codes such as RN warehouse or public private library within the group are separated from the main project. Therefore, it is very inefficient to compile all codes of the whole project every time during development. This problem is less obvious in indie development, where the project is large but only one person is submitting the code, so the increase in project code is less dramatic and the changes are more familiar. However, this defect is exposed when many people collaborate in development. When they are developing different businesses, they not only need to synchronize project changes with others all the time and understand others’ code, but also need to compile the whole project every time to debug their own modifications, which is inefficient.

  • Team size changes

When I started to participate in the maintenance of the iOS version of Hummingbird Merchant, there was only one senior before, that is, one person maintained an App independently. And then, not long after that, he left for another company, so he was back to maintaining the App on his own. At this time because it is independent development, so there is no too big a problem. However, with the expansion of the team, several colleagues came later to jointly take charge of the maintenance work of the project. All of them carried out business development on the same project, and often encountered problems such as code conflicts, low development efficiency, unclear division of responsibilities, and chaotic code management.

  • Business development pressure

As the company is in the stage of rapid development and the business is growing rapidly, the most intuitive manifestation is that the marketing & customer service department constantly receives a large number of feedbacks or appeals from front-line users, which finally becomes PRD after PRD for our developers. Tight business development requirements and flexible capabilities force us to do everything at our disposal to improve development efficiency and test quality.

  • Code management mess

When I started to participate in the maintenance of this project, it was already a Swift and OC mixed project, and then there were RN and H5 codes, which could be said to be very complicated. Although this is not the only Swift and OC mixed project in our factory, it is definitely the project with the highest Swift at that time, and about 25% of the codes are Swift. As we all know, Swift and OC calls to each other are not nearly as smooth as Java and Kotlin calls to each other (you know that by now), and there are so many dangers and pothole that there is an urgent need to find a way to collate, transform, or separate Swift and OC code. After all, this file is OC and the next file is Swift. Such frequent mind shifts can be very tiring in an already stressful situation like business development, which is not conducive to smooth development.

2. How to solve it

To address these issues, we have explored the following:

  1. Remove useless third-party libraries and resource files to reduce packaging time: no obvious effect;
  2. Organize and promote internal Gitflow workflow, improve collaboration efficiency: some effects, but daily collaboration is still difficult due to the large project;
  3. Study Swift compilation time optimization methods to improve compilation efficiency: it is found that some common grammar sugars of Swift increase compilation time, if not used, seriously reduce development efficiency, so give up;
  4. Promote the Swift of the whole project without splitting the main project: Due to the departure of the predecessors of the maintenance project, the current project developers are not very familiar with the original code and dare not make any changes. In addition, the frequent business iterations and the tight development and testing resources lead to the slow progress of the project.

It can be found that the results of the above attempts are not very ideal. After some communication with the leaders in the iOS group and listening to their opinions, we decided to carry out the “componentalization/modularization split” work on the original project, which can bring the following benefits:

  • Faster compilation, no longer need to compile components/modules that are not dependent on code;
  • It is convenient to assign each module to a different person for management;
  • Reduce the difficulty of merger, reduce the probability of conflict and error, improve the efficiency of business development;
  • Swift and OC codes are separated to facilitate the Swift work.
  • Unit tests can be written for modules to improve work efficiency and facilitate testers to conduct targeted tests.

2. Goal setting

  • Independence of functional components: Ensure that all the underlying functional components are removed from the main project, independent from the main project, easy to reuse and call business modules;
  • Division and disassembly of business modules: divide and disassemble businesses according to their corresponding uses, and try to cut off the strong dependence between businesses;
  • Independent compilation of all components/modules: All functional components and business modules can be compiled independently of the main project, with their own Demo projects;
  • CocoaPods publishing: Publish in GitLab, and then manage and publish each module using GitFlow workflow.

Plan making

Speaking of componentization/modularization, what is componentization/modularization? What’s the difference between componentization and modularization?

A component is a function that we encapsulate. A function is a component. Database, network, file manipulation, social sharing, and so on are all components. We developed the concept of components so that our upper-level business modules could rely on and invoke these basic functions at any time. Components can be basically divided into basic functional components, general UI components, basic business components and so on. Therefore, in order to meet the above requirements, components must be highly independent, extensible, and reusable.

Module is to sort out a series of cohesive businesses, cut and split them with other businesses, and extract them from the main project or original location into a relatively independent part. Just for business, for example, we can separate the order business into a module, the personal center into a module, the user login into a module, etc., which are reflected in the App as independent Git repositories. One advantage of modularization is that building blocks can be built when used, for example, the same or several business modules can be reused between multiple projects, such as QQ and TIM of Tencent. In addition to the UI interface, TIM obviously reused a large number of existing business module codes of the original QQ project. Of course, we do not have this requirement for the time being.

After discussion in the group meeting, our idea is to separate the common components and directly split the existing main project according to the business while taking into account the separation of Swift and OC, which is roughly divided as follows:

1. The components

component The library The main content
Foundation (OC) LPDBOCFoundationGarbage The base OC components, the various scattered and messy views, components, controls, constants, OC macro definitions, etc., are all here for the upper level to call. Like its name, it is basically a garbage can.
Foundation (Swift) LPDBPublicModule The basic Swift component, which contains some common Swift extensions, and protocols for module decoupling.
Network (OC) LPDBNetwork Network component, which is a shallow encapsulation of AFNetworking and includes networking functions.
. . .

2. The module

The module The library The main content
History (OC) LPDBHistoryModule History order module, including and history order related resource files, UI, business logic code, etc.
Login (OC) LPDBLoginModule User login module, including and login, registration page related resource files, UI, business logic code.
User Center (OC) LPDBUserCenterModule User center module, including user personal center and state related resource files, UI, business logic code, etc.
. . .

3. The relationship between

An ideal module/component dependency diagram would look something like this:

Because the hummingbird business version of the team of developers have no previous experience of breaking up any project, we are also feeling the stones across the river, see step by step. Therefore, although the above idea of splitting is generally correct, dismantling components first and then business, but due to a variety of reasons, some problems were exposed in the implementation process of the following work.

Iv. Work implementation

Our team is still mainly focused on business development, so we take our spare time to complete the componentation/modularization work without rigid schedule and Deadline. According to the previous plan, we carried out the following work:

1. Independent functional components

1.1 LPDBOCFoundationGarbage

Lpdbocfoundationgbage is the first part of our project to be drawn. This library will serve as the bottom of the project, along with the LPDBPublicModule, just below. This library is positioned as its name, is a trash can, everything into it. These generally include the following things:

  • Custom View and control, such as: red dot control, refresh control, load control, Tips View, etc.
  • Customized controllers, such as BaseViewController, BaseWebViewController, and customized pop-up AlertController.
  • Business related extensions to basic types or system controls: extension code categories for NSObject, UIButton, UIImageView, UILabel, etc.
  • Even the version control module LPDBVersionManager is placed here.

Because we are maintaining the development of the project while carrying out the splitting task, we do not have the energy to do the detailed splitting work for the time being. We can only put these scattered parts together for management first.

1.2 LPDBPublicModule

The LPDBPublicModule is the basic Swift component. This library contains:

  • Some common Swift extensions, such as CGFloat, Date, NSString, etc.
  • A protocol for decoupling between modules.

Since most of the Swift codes in the project are newly written by us, compared with the old OC codes, they are better organized, so the warehouse is much cleaner

1.3 LPDBNetwork

LPDBNetwork network component was the first part of our project after the completion of OC and Swift basic parts. At the beginning, we thought that this part was only simple business network request operation and shallow encapsulation of AFNetworking, excluding interface and UI logic. But when we took it apart, it also contained a bunch of weird stuff:

  • Some definitions of AFNetworking encapsulation and network operations, such as LPDBHttpManager, LPDBRequestObject and LPDBModel, etc.
  • UI operations, such as waiting for LPDBLoadingView and network request failure messages.

For this part, the original developers no longer maintained it because it was an old code. Therefore, we had to split it by ourselves. In order to prevent problems caused by big changes, we did not carry out more detailed disassembly work for this part. After all, bad code is better than code that doesn’t work.

1.4 LPDBUIKit

Swift UI library, we collected some Swift views and controls from the project into this project, mainly including the following contents:

  • Views, such as LPDBEmptyDataView, SlideScrollView, etc.
  • Control, such as SlideTabKit.

Since the Swift code total is not very large, the library is not very large at the moment, and it will be gradually enriched.

2. Split service modules

After completing the independent work of the above component library, the disassembly of business modules is relatively easier. At present, we have mainly completed the disassembly of three business modules.

2.1 LPDBHistoryModule

LPDBHistoryModule History order module, and history order page related information in this module, mainly contains the following contents:

  • UI, such as: history order interface, history order list Cell, load view, etc.
  • Data models, such as historical order models;
  • Network requests related to the history order list.

Because the module is relatively independent, the separation process is relatively smooth, mainly relying on LPDBPublicModule, LPDBNetwork, LPDBOcfoundationgbage components.

2.2 LPDBLoginModule

LPDBLoginModule User login module is a module related to user login, registration and user login information, mainly contains the following information:

  • UI, such as user login interface, user registration interface, etc.
  • Data model, such as user information model, user information address model, etc.
  • Login network requests related to registration.

This module is more complex than the historical order module, but it is still relatively smooth. It mainly relies on LPDBPublicModule, LPDBocfoundationgbage, and LPDBNetwork components.

2.3 LPDBUserCenterModule

LPDBUserCenterModule The LPDBUserCenterModule is a module related to the user personal center and user information modification. It contains the following information:

  • UI, such as user center interface, user phone change interface, user password change interface, etc.
  • Data model, such as user details model, user information address model, etc.
  • Network requests related to the user center, for example, modifying phone numbers and request verification codes.

This module mainly relies on THE LPDBOcFoundationgbage component and the LPDBLoginModule module.

2.4 other

The remaining modules are still in the planned state and have not been split yet. At this point, the inter-library dependencies look something like this:

You can see that there are some unjustified dependencies, such as LPDBUserCenterModule’s dependence on the LPDBLoginModule module, known as the business module horizontal dependency problem, which we will deal with next.

3. Uncoupling

Because there was never any consideration of modularity in the development process, the hummingbird Business edition code is very mixed and the project dependencies are very complex, which can be divided into the following three types of coupling:

  • Interface coupling: jump behavior between hard-coded interfaces during App execution;
  • Engineering coupling: some modules rely on the code of the main project at runtime to run or achieve full functionality;
  • Dependency coupling: There are dependencies between two business modules.

3.1 Component Sharing between modules

In the process of splitting business modules, two business modules often reference a certain piece of business code at the same time. At this time, we need to understand this piece of code. Firstly, we should distinguish whether it should be divided into the business layer.

  • If so, which module should be classified into more reasonable;
  • If not, you should sink the code into a component library, or make it a component.

LPDBUserCenterModule and LPDBLoginModule depend on several data models related to user information, resulting in horizontal dependencies between modules. So we pull out the shared data model, and we sink it into the LPD Bocfoundationgbage.

3.2 Inter-module coupling

Another problem we often encounter is the problem of cross-module call code. It is not only the mutual call of code between modules, the jump of page between modules, but also the reverse call of main project code by modules. We have divided three steps to solve this problem:

  • Reflection calls

Because of the complexity of the project and the irregularities of the previous code, we had to deal with the pain of cutting business modules, so we used a fast but not very safe way to decouple the modules at the beginning. For example, when the LPDBUserCenterModule module needs to call the getMiddlePageVC method of the main project, we use the following temporary solution:

if ([[UIApplication sharedApplication].delegate respondsToSelector:@selector(getMiddlePageVC)]) {
    UIViewController *info = [[UIApplication sharedApplication].delegate performSelector:@selector(getMiddlePageVC)]; . }Copy the code

Then implement this interface in the main project:

// .h
@interface AppDelegate : UIResponder <UIApplicationDelegate>.// LPDBUserCenterModule
- (UIViewController*)getMiddlePageVC; .@end

// .m
@implementation AppDelegate. - (UIViewController *)getMiddlePageVC {
    ...

    returnxxx; }...@end
Copy the code

The advantage of this scheme is flexibility, using NSClassFromString, formSelector and other ways, can quickly solve a variety of coupling problems, instantly cut modules. But the disadvantages are also obvious, string hard coding, high maintenance costs, removed the compiler check, easy to roll over.

  • The agreement calls

So naturally, when the separation of a certain business module is basically finalized, we start to replace the reflection call method in the first step with the protocol call method. For example, when the LPDBLoginModule module needs to call the getCoordinate method of the main project, the example is as follows:

id delegate = [[UIApplication sharedApplication] delegate];

if(! [delegate conformsToProtocol:@protocol(AppDelegateProtocol)]) {
    return;
}
CLLocationCoordinate2D coordinate = [delegate coordinate];
Copy the code

Then implement the method in the main project:

// .h
#import "AppDelegate.h"

@import LPDBLoginModule;

@interface AppDelegate (Protocol)  <AppDelegateProtocol>

@end

// .m
@implementation AppDelegate (Protocol)

- (CLLocationCoordinate2D)getCoordinate {
    return self.coordinate;
}

@end
Copy the code

Kind of change, however, cannot completely solve the write call each other between the modules of code to check the problem of lack of the compiler, but merely made a judgment and fault tolerance, the caller is not at compile time let developers aware of the problem, must test can be, so this way also is not very ideal.

  • Lotusoot decoupling tool

So to solve the problem completely, we developed and introduced component communication and tool Lotusoot, which can be called in the following ways:

  • The service call
let lotus = s(AccountLotus.self) 
let accountModule: AccountLotus = LotusootCoordinator.lotusoot(lotus: lotus) as! AccountLotus
accountModule.login(username: "admin", password: "wow") { (error) in
    print(error ?? "")}Copy the code
  • Short chains of registration
let error: NSError? = LotusootRouter.register(route: "newproj://account/login") { (lotusootURL) in
    accountModule.showLoginVC(username: "admin", password: "wow")}Copy the code
  • Short chains of calls
let param: Dictionary = ["username" : "admin"."password" : "wow"]

/ / no callback
LotusootRouter.open(route: "newproj://account/login", params: param)
/ / a callback
LotusootRouter.open(route: "newproj://account/login", params: param).completion { (error) in
    print(error ?? "open success")}// ⚠️ do not recommend using? Forms like pram0= XXX result in strings scattered and unmanageable.
// However, this call is provided to ensure a normal jump to the H5 page in a Hybrid project
LotusootRouter.open(url: "newproj://account/login? username=zhoulingyu").completion { (error) in
    print(error ?? "open success")}Copy the code

See iOS’s flexible modularization/componentization tools and specification Lotusoot for more details here. There are other similar tools like BeeHive and LPDMvvmRouterKit that you can explore on your own.

The resulting structure looks something like this:

V. Problem sorting

1. Unreasonable hierarchical structure and dependency between libraries

Due to the lack of componentalization experience of the people involved in the split, some libraries were not split properly, and some common models and constants that should have been sunk into the bottom layer were not put in a proper place at the beginning. There are also some unreasonable horizontal dependencies between business modules, without a reasonable division of business boundaries. Because of these reasons, we often need to go back to reorganize and process the modules and components that have been removed, which is a lot of repetitive labor.

2. The splitting granularity is not appropriate

Some libraries, such as LPDBoCFoundationgbage, are large, while libraries like LPDBUIKit have very little content, which is problematic to handle. If a split library is still bloated, there is still room to refine the split.

3. The work schedule is difficult to control

The lack of a detailed schedule in advance, coupled with the business pressure, resulted in fragmented time spent on componenting/modularizing work. The original intention is to hope that everyone can flexibly arrange work, reasonable disposal of business development and technical transformation work between the relationship, but the effect is not very ideal, performance is the component/modular work is not continuous, everyone’s enthusiasm and work efficiency are not high.

6. Experience summary

1. Technical research should be done before work begins

Viewing and learning some similar successful case materials or consulting industry leaders can bring convenience to the formulation of the plan, so that we can avoid a lot of wrong design, less detours, reduce the rework rate.

2. Formulate a detailed overall plan

In preparing for war, I often find that having a plan is useless, but the process of planning is essential. — Dwight Eisenhower

The formulation of detailed overall planning can expose some unreasonable places in the design stage, so as to come up with solutions to solve the problems in advance, or delete and replace the unreasonable content, such as unreasonable stratification, dependence between libraries, will reduce a lot of problems. With detailed task splitting plan and workload estimation, tasks can be more reasonably assigned to developers, which can improve work efficiency and avoid conflicts with business development.

3. Pay attention to code quality control

Good code and coding practices can greatly improve the maintainability of a project, making it easier to work on later. Our old OC code was chaotic and basically in an unmaintainable state, which was very painful to split; The new Swift code is obviously of much higher quality (we really don’t brag…). It’s much smoother to break it up.

4. Document your information

Each split module add documents in time, at least to create a common trouble by README template, the builder of each module or component split the module content, purpose, design the basic information such as records, what is the pit or attention can document, make possible future long-term project maintenance.

Vii. Open source achievements

During the componentization/modularization work, some of the libraries and tools we produced have been put on GitHub for open source. We hope to receive your comments and suggestions to improve the quality of our project.

The library Introduction to the The warehouse address
EFPodsAnalyzer The Visual Pods library relies on analysis tools Github.com/EyreFree/EF…
EFAutoScrollLabel A UILabel with a running lantern effect Github.com/EyreFree/EF…
Bamboots A protocol – oriented Swift network library Github.com/mmoaay/Bamb…
Lotusoot Flexible Swift component decoupling and communication tools Github.com/Vegetarians…
bigkeeper An iOS & Android modular project efficiency enhancement tool Github.com/BigKeeper/b…
SideNavigation A side bar that supports sideslip and can be customized Github.com/CNKCQ/SideN…
ViewPagers A support for the segented Control Github.com/CNKCQ/ViewP…

Eight. Afterword.

This article basically describes the componentization/modularization practice of hummingbird Merchant App so far, hoping that this article can provide some reference for your mobile project evolution. In this process, we have produced some articles, open source libraries and tools, and hope to give you some help or inspiration. We welcome all kinds of feedback and suggestions or help us continue to improve and improve.

At the end of 2017, almost one year after I participated in the maintenance work of hummingbird merchant version, the App had been transferred to another team for maintenance due to business adjustment, so the Swift and componenization/modularization work of the project had not been completed, which was a pity. But still hope hummingbird business version can get better and better, continue to serve the majority of business friends.

The good news is that I will mainly participate in the architecture work of hummingbird Team version App. This time, we made a detailed work plan according to the problems exposed before. With the experience of hummingbird Merchant version, I believe we can successfully accomplish the goal this time. 2018, come on, fight together!

The following articles were referred to in the preparation of this article, and I would like to express my thanks to the original authors:

  1. Instant delivery network in takeout O2O, the higher realm of distribution is community management
  2. Talk about my understanding – componentization/modularization
  3. The componentization of Mogujie App
  4. Modularization practice of Douban App
  5. The road to mobile phone tmall decoupling
  6. Jd iOS client component management practice

The epilogue to the epilogue

“Modular Daily” series of short essays, to share their own modular process of stepping in the hole, for students who have (or have not) encountered similar problems a reference and help:

  • Modular daily: The magic pod Repo push fails
  • Modular Daily: CocoaPods 1.4.0 Is great (not great)
  • Modular daily: CocoaPods library resource reference issues
  • Modular everyday: same name class
  • Modular daily: Time-consuming releases
  • Modular everyday: Open source libraries have the same name as private libraries
  • Modular routines: Libraries depend on each other

To be continued… 2333


Please correct any intellectual property rights, copyright issues or theoretical errors. Juejin. Cn/post / 684490… Please indicate the original author and above information.