The opening

Lotusoot, which was introduced in the previous iOS Hybrid Modularization/componentization Lesson Guide, will be covered in more detail in this article.

Lotusoot originally referred to it simply as “mixed routing,” but has since misinterpreted its functionality to be “modular tools and specifications.”

Lotusoot can do this:

  1. Inter-module and intra-module service invocation
  2. Swift, OC, or a combination of the two can be used
  3. Short chain registration, routing calls
  4. Scripts automatically register service/routing tables

Note: Modularization, also known as “componentization”, does not mean that modules are separated into folders in the main project. Rather, it means that individual modules are isolated into CocoaPods libraries or other forms of library files and become a single project. The module below represents a CocoaPods library

Module decoupling – Routing or service calls?

About modularity, most people’s first reaction is to make a route, register short chain, call short chain, through such a way to decouple, to realize the page jump between modules, service call. Libraries like MGJRouter are based on this idea.

But I also appreciate casa’s argument against using urls as the core of modularity. That is, “The essence of the short chain is still to call the service or open the page through the URL, which is not as direct as the string, but increases the maintenance cost of the URL itself”.

So, we should go back to the essence of modularity. Our initial goals for modularity are often to:

  1. Code splitting, the strong correlation of the basic service code or business code together, separate version sealing, independent development
  2. Prevent the main project from getting bigger and bigger and becoming bloated

Correspondingly, modularity requires the following functions:

  1. Provides service invocation between multiple libraries
  2. Maintain independent, non-strong dependencies between libraries

So, in general, the focus of modularization is how to remove the coupling between multiple modules, so that each module can call the services of other modules without strong dependence.

**URL short chains, or even routing, are not the point of modularity. ** Routing can be implemented through service registration as long as you want.

Note: “not strongly dependent” means that module A calls module B without having to write A dependency on B in the Pod dependency, or simply that module A does not have import B in its code.

Common modules and dependencies

Lotusoot is decoupled like this:

1. Lotus

Create a PublicModule with Lotus for each module. Lotus is a protocol that defines the services (methods that can be called) that each module can provide. For example:

public protocol AccountLotus {
    func login(username: String.password: String.complete: (Error?). ->Void)
    func register(username: String.password: String.complete: (Error?). ->Void)
    func email(username: String) -> String
    func showLoginVC(username: String.password: String)
}
Copy the code

2. Lotusoot

In each module, the implementation of the corresponding Lotus in the PublicModule, the concrete service class, is called Lotusoot. The logic of the service is implemented in Lotusoot, and the module’s namespaces -@NameSpace, Lotusoot-@Lotusoot, and Lotus-@Lotus are indicated in annotations. Examples are as follows:

// @NameSpace(TestAccountModule)
// @Lotusoot(AccountLotusoot)
// @Lotus(AccountLotus)
class AccountLotusoot: NSObject.AccountLotus {
    
    func email(username: String) -> String {
        return OtherService.email(username: "zhoulingyu")}func login(username: String.password: String.complete: (Error?). ->Void) {
        LoginService.login(username: username, password: password, complete: complete)
    }
    
    func register(username: String.password: String.complete: (Error?). ->Void) {
        RegisterService.register(username: username, password: password, complete: complete)
    }
    
    func showLoginVC(username: String.password: String) {
        // You can handle jumps in any uncoupled way you like
        // Or pass rootvc
        // A better way is to have your own uncoupled UI jump processing module
        print("show login view controller")}}Copy the code

Annotations are optional so Lotusoot. Py can scan Lotusoot for automatic registration, as described in a later section. If you don’t want to use automatic registration, you can also choose manual registration.

Note: To explain this, ‘protocol-service class’ is named lotus-Lotusoot because it is named Lotus because the protocol is exposed externally, while the specific service class is naturally Loutsoot.

3. Automatic registration or manual registration

If annotations are used, all services can be automatically registered by:

func application(_ application: UIApplication.didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    LotusootCoordinator.registerAll()
}
Copy the code

If you register manually first, the following is displayed:

[LotusootCoordinator registerWithLotusoot:[AppDelegateLotusoot new] lotusName:@"AppDelegateLotus"];
    [LotusootCoordinator registerWithLotusoot:[MainLotusoot new] lotusName:@"MainLotus"];
Copy the code

Manual registration, however, removes the point of using Lotusoot, so use manual registration when conditions are not met (for example, in the current version 0.0.2 of Lotusoot, if the main project has multiple targets, the Target name cannot be dynamically obtained, causing the namespace to be incorrectly obtained and reflected into the class. Except for projects, modules don’t have this problem because they use CocoaPos. The next version of Lotusoot will focus on this issue.)

4. The diagram

The project built by Lotusoot is shown in the figure below:

[image upload failed…(image-442260-1513091120014)]

All modules need to rely only on PublicModule, and the services of other modules can be invoked through Lotusoot under PublicMoudle. Example code is as follows:

let loginLotus = s(LoginLotus.self) 
let loginModule: LoginLotus = LotusootCoordinator.lotusoot(lotus: loginLotus) as! LoginLotus
loginModule.login(username: "test", password: "test", complete:nil)
Copy the code

Used in OC:

id<LoginLotus> loginModule = [LotusootCoordinator lotusootWithLotus:@"LoginLotus"];
[loginModule login:@"test" password:@"test" complete:nil];
Copy the code

How does Lotusoot implement automatic registration of services? At what time?

This question was also the focus of the initial Lotusoot writing. The reason is Swift doesn’t have +(void)load.

Probably the same problem that everyone who develops a module routing and decoupling tool with Swift has struggled with.

1. Common solutions in OC

OC routing or decoupling is usually registered in +(void)load, which is similar to:

+ (void)load {
    @autoreleasepool {
        [[Router shared] map:@"LoginViewController" toController:[self class]]. }}Copy the code

or

+ (void)load {
    @autoreleasepool {
        [[ServiceManager shared] register:@"LoginService" toService:[self class]]. }}Copy the code

In this way, even within each module, you can register your own services normally. The main project and other module calls are just string calls.

2. Pain points in Swift

Due to the Swift, there is no + (void) of the load, and no other reliable method can replace, then certainly will need to add registered routing in the main engineering this step, can often put didFinishLaunchingWithOptions, for all modules can be transferred to the module class. The problem is that you might end up with code like this:

ServiceManager().register("LoginService", toService:LoginService.self)
ServiceManager().register("UserCenterService", toService:UserCenterService.self)
ServiceManager().register("HistoryService", toService:HistoryService.self)
.
Copy the code

There may be a long list, and because the services are scattered across modules but concentrated in the main module, it is often difficult to see the association between the routing table and the service class, the representation is not obvious, the relationship is not strong.

3. Lotusoot solution

Is there a better way to register? Mmoaay gave me a great suggestion to follow r. Swift’s lead and script automatic registration.

R. Swift offers the ability to use iOSer to call resource files like images, strings, audio, and so on, just as you would on Android. Insert the Run Script into the Project. This Script can scan the entire Project at compile time, count all the resource files, and finally generate an r.enerated. swift file that looks like this:

[image upload failed…(image-4C4F17-1513091120014)]

When used:

[image upload failed…(image-75F168-1513091120014)]

Similarly, Lotusoot uses a Python script to scan the files in the project directory before “Compile Source”, find the Lotusoot and Lotus mapping, and generate a Lotusoot. Plist file:

[image uploading failed…(image-91BED0-1513091120014)]

How do I recognize Lotus?

Currently, Lotusoot uses a very Low approach, indicating the module’s namespaces -@NameSpace, Lotusoot-@Lotusoot, and Lotus-@Lotus in annotations that the script can recognize. Examples are as follows:

[image upload failed…(image-e94313-1513091120014)]

So, didFinishLaunchingWithOptions only need 1, can automatically register routing.

LotusootCoordinator.registerAll()
Copy the code

Why script solution? So far, all decoupling tools or routes have been added manually by the programmer, whether registered in + (void) Load or after the program is started. The use of scripts is to hope that before the compilation stage, all the “protocol – service class” corresponding relationship table is ready, after the program starts through this table automatic registration, to achieve programmers do not register manually, completely no sense. I think that’s what a true decoupling tool should do.

Major weaknesses of Lotusoot and goals for the next release

The disadvantages of Lotusoot are obvious, although it is possible to create a “protocol-service class” relational table at compile time with scripts. But Lotusoot. Py recognizes the “protocol-service class” through annotations, which are really annotations that cannot be compiled to detect errors, miswritten to detect problems, or checked for problems. If you solve this pain point, you can solve the Swift modular solution relatively perfectly.

Here’s the current thinking:

Try implementing real annotations through global methods or other syntactic means, like annotations in Java, not only as an identifier but also as a compilation check.

We can use the macro definition directly in OC:

#define Service(_name_) \
+ (void)load { \
    [selfregisterService:_name_]; The \}/ / use
@Service(@"LoginService")
@implementation LoginService
.
@end
Copy the code

But Lotusoot is available for both Swift and OC as well as hybrid projects, so I need to explore the implementation.

Another way to use LLVM is to provide the @annotation operation, and if you generate the.plist registry file this way it should be at the end of compilation.

Above, are some ideas for the future, hoping to perfect the Swift modular solution. If you have any good suggestions, please come to me for discussion

The Demo and making

To get a better feel for the modularity changes Lotusoot brings, download the Demo.

The address of the main project is here.

Welcome to discuss with us (cute ~~)