preface

From the beginning of 2016 to the end of 2016, the discussion of componentalization in the domestic industry was nothing more than two schemes: URL/ Protocol registration scheduling and Runtime scheduling.

I have previously criticized URL registration scheduling as a wrong componentized implementation. In all schemes based on URL registration scheduling, there are two common problems:

  1. Named domain penetration
  2. Because registration is not necessary, there are also unnecessary maintenance costs of registration lists

There are various other schemes based on URL registration that have all sorts of other problems with these two general problems, For example, the FRDIntent object in the FRDIntent library is a chicken object in nature, the business originally belonging to the responder is infiltrated into the business of the caller, and there will be intrusive modification of the original code in the process of componentization implementation scheme.

In addition, I also found that some people did not understand clearly under the premise of their own interpretation, very toxic. I have shared the description of CTMediator comparison theory before, but I have not written any practical description. Now it seems to be a practical article.

Another purpose of this article is to make people understand that the implementation of the componentization scheme based on CTMediator is actually very simple, and there are rules and regulations to follow. This article may discuss some theoretical things, but it will mainly be practical. Try to make it so that after reading the article, you can directly implement componentization in your own projects.


The preparatory work

An orgnization has been opened on github, and there is a MainProject: MainProject, for which we will make componentization. The main project after the implementation of componentization is ModulizedMainProject. Isolated and private Pod sources that are pulled out are also placed in orgnization.

Before implementing a componentized solution for a project, we need to do a preparatory work by setting up our own private Pod source and the configuration of the Kuaishou tool script:

  1. Start with a REPO, which is our private Pod source repository
  2. Pod repo add [private POD source repository name] [private POD source repo address]
  3. Create a folder, such as Project. Put our main Project folder under Project:~/Project/MainProject
  4. Clone the private source script repo under ~/Project:git clone [email protected]:casatwy/ConfigPrivatePod.git
  5. Place ConfigPrivatePod’s template folder in Podfilesource 'https://github.com/ModulizationDemo/PrivatePods.git'Change the repO address of your own private Pod source repository from step 1
  6. File ConfigPrivatePod’s template folder in upload.shPrivatePodsChange the name of your own private Pod source repository from step 2

Your final file directory structure should look like this:

Project ├── ├─ Private ExercisesCopy the code

So much for the preparations.

Step 1: Create a private Pod project and Category project

MainProject is a very simple application with three pages. The home page pushes the AViewController, and the AViewController pushes the BViewController. We can understand that this project consists of three businesses: home page, A business, B business.

Our implementation goal of componentization this time is to componentize A business, and both the home page and B business are still placed in the main project.

Because in practice, componentization needs to be implemented step by step. Especially some mature projects, the business will be very much, it is impossible to completely componentize for a while. During the implementation of CTMediator scheme, it has little impact on the main engineering business, and can support gradual transformation. I will mention this at the end of the article when I conclude.

Since we want to extract A business as A component, we need to make two private pods for this: A business Pod (hereafter referred to as A Pod), and A Pod for others to call A business CTMediator Category (hereafter referred to as A_Category Pod). Here’s one more thing: A_Category Pod is essentially A convenience method that doesn’t have any dependencies on A Pod.

Let’s first create A Pod

  1. Create A new Xcode project, call it A, and put it under Projects
  2. Create A new Repo, also named A, and do not close the page after it is created

Your file directory structure should look like this:

├── Heavy Exercises ── Heavy ExercisesCopy the code

Then CD to ConfigPrivatePod and run the./config.sh script to configure A private Pod. The script will ask you for some information. The Project Name is A, which should be the same as the directory Name of your Project A. HTTPS Repo, SSH Repo Page, Home Page URL, you can fill in the URL of A Repo Page.

This script is written by me to facilitate the configuration of private libraries. Pod Lib Create can also be used, but it will directly pull a complete template project from Github, but domestic access to Github is actually slow, which will affect efficiency. And the configuration was not complicated, so I wrote a script myself.

This script requires that the private Pod file directory be at the same level as the script directory, and also creates a new directory with the same name as the project under the XCode project code directory. Code placed in this directory will be released with the Pod release, and code outside this directory will not be released with the Pod release, making it easier to write code for testing.

Then in the main project, we carried out the code belonging to A business, put it in the A folder of the newly built A project, and then drag and drop it into the A project. Originally, the code of A business in the main project was directly deleted. At this time, both the compilation of the main project and project A were normal. We will solve the compilation problem of the main project in the second step and the compilation problem of project A in the third step.

At this point your main project should have no code for business A, and then your project A should look like this:

A ├ ─ ─ A | ├ ─ ─ A | │ ├ ─ ─ AViewController. H | │ └ ─ ─ AViewController. M | ├ ─ ─ AppDelegate. H | ├ ─ ─ AppDelegate. M | ├ ─ ─ ViewController. H | ├ ─ ─ ViewController. M | └ ─ ─ main. M └ ─ ─ A.x codeprojCopy the code

So let’s create A_Category Pod again

Similarly, we’ll create the A_Category, which is also a private Pod, so run the config.sh script to configure it as well. Your final directory structure should look like this:

Project ├ ─ ─ A │ ├ ─ ─ A │ │ ├ ─ ─ A │ │ ├ ─ ─ AppDelegate. H │ │ ├ ─ ─ AppDelegate. M │ │ ├ ─ ─ Assets. Xcassets │ │ ├ ─ ─ Info. The plist │ │ ├ ─ ─ ViewController. H │ │ ├ ─ ─ ViewController. M │ │ └ ─ ─ main. M │ ├ ─ ─ Amy polumbo odspec │ ├ ─ ─ A.x codeproj │ ├ ─ ─ FILE_LICENSE │ ├ ─ ─ Podfile │ ├ ─ ─ the readme. Md │ └ ─ ─ the upload. Sh ├ ─ ─ A_Category │ ├ ─ ─ A_Category │ │ ├ ─ ─ A_Category │ │ ├ ─ ─ AppDelegate. H │ │ ├ ─ ─ AppDelegate. M │ │ ├ ─ ─ the Info. The plist │ │ ├ ─ ─ ViewController. H │ │ ├ ─ ─ ViewController. M │ │ └ ─ ─ main. M │ ├ ─ ─ ├── PodSpec │ ├─ ├─ Podfile │ ├── ├.md │ ├.txt ├── ConfigPrivatePod │ ├ ─ ─ config. Sh │ └ ─ ─ templates └ ─ ─ MainProject ├ ─ ─ FILE_LICENSE ├ ─ ─ MainProject ├ ─ ─ ├── Podfile ├── Bass Exercises ├─ PodfileCopy the code

Then go to the A_Category and add pod “CTMediator” to the Podfile. At the end of the podSpec file, add s.Doendency “CTMediator”. Then run pod Install –verbose.

Next, open a_category.xcworkspace, drag and drop the empty directory named A_Category generated by the script to its location in Xcode, and create A new Category based on CTMediator: CTMediator+A. Finally, your A_Category project should look like this:

A_Category ├ ─ ─ A_Category | ├ ─ ─ A_Category | │ ├ ─ ─ CTMediator + A.h | │ └ ─ ─ CTMediator + arjun | ├ ─ ─ AppDelegate. H | ├ ─ ─ AppDelegate. M | ├ ─ ─ ViewController. H | └ ─ ─ ViewController. M └ ─ ─ A_Category. XcodeprojCopy the code

So at this point, we’re ready for project A and project A category.

Implement the componentization scheme step 2: introduce the A_Category project in the main project and make the main project compile and pass

Go to the main project Podfile and add pod “A_Category”, :path => “.. /A_Category” to reference A_Category locally.

I’m going to compile it and say I can’t find the AViewController header. At this point we change the header file reference to #import

.

I’m going to go ahead and compile and say I can’t find AViewController. See here’s where AViewController is used, so we found CTMediator+A.h under Development Pods and added a method to it:

- (UIViewController *)A_aViewController;
Copy the code

To CTMediator + arjun, catch up on the implementation of this method, the main engineering call statements as a note in the release, writing in the future Target – the Action to use:

- (UIViewController *)A_aViewController
{
    /*
        AViewController *viewController = [[AViewController alloc] init];
     */
    return [self performTarget:@"A" action:@"viewController" params:nil shouldCacheTarget:NO];
}
Copy the code

PerformTarget :@”A” is the name of the Target object. Generally speaking, a business Pod needs only one Target, but a Target can have many actions. The Action name is also optional, as long as the corresponding Action is available in the Target object.

We will implement target-action in step 3. Not implementing target-action now will not affect the main project compilation.

So that’s the end of the category, and I’m not going to change it in the implementation.

Then we change the main project to call the AViewController implementation based on CTMediator Category:

    UIViewController *viewController = [[CTMediator sharedInstance] A_aViewController];
    [self.navigationController pushViewController:viewController animated:YES];
Copy the code

Let me compile it again, and it passes.

This is the end of the main project. Now it is normal to go to the main project and click this button and not jump to page A, because we have not implemented target-Action in project A.

At this time, all the changes of A business in the main project will be finished, and there will be no more changes to the main project for A business line in the subsequent componentization implementation process.

Implement componentization step 3: add target-action and let project A compile

At this point we close all XCode Windows. Then open up two projects: A_Category and A.

We create A target folder in project A: Targets, and then we see performTarget:@”A” in A_Category, so we create A new object called Target_A.

Then you see that the corresponding Action is a viewController, so create a new method in Target_A: Action_viewController. The Target object looks like this:

#import <UIKit/ uikit. h> @interface Target_A: NSObject - (UIViewController *)Action_viewController:(NSDictionary *)params; @end implementation file:  #import "Target_A.h" #import "AViewController.h" @implementation Target_A - (UIViewController *)Action_viewController:(NSDictionary *)params { AViewController *viewController = [[AViewController alloc] init]; return viewController; } @endCopy the code

So when you write your implementation file here, just follow the comments in the A_Category.

Because the Target object is in the named domain of A, it is free to import any header file from A’s line of business.

In addition, the Action of the Target object is not designed to just return ViewController instances; it can be used to perform various tasks belonging to the line of business itself. For example, uploading files, transcoding and so on can be used as an Action to external call, Action to complete these tasks, the business logic can be written in the Action method.

To put it another way: An Action has the ability to schedule any object and method provided by the line of business to complete its task. Its essence is a layer of service encapsulation of external business.

Now all this Action needs to do is instantiate a ViewController and return it. As described above, the Action can do much more complicated things.

And then we compile project A, and we can’t find the BViewController. Since the purpose of our componentization implementation is only to pull out business line A, and BViewController belongs to business line B, there is no need to pull out business line B from the main project. But in order for project A to compile, we need to provide A B_Category so that project A can be scheduled to project B and also compile.

The B_Category creation steps are the same as the A_Category creation steps: create a new Xcode project, create a new Repo on the page, run the script to configure the Repo, and add the Category code.

Once the B_Category is added, we will also point it out locally in project A’s Podfile as we did in the main project.

So B_Category looks like this:

The header file:  #import <CTMediator/CTMediator.h> #import <UIKit/UIKit.h> @interface CTMediator (B) - (UIViewController *)B_viewControllerWithContentText:(NSString *)contentText; @end implementation file:  #import "CTMediator+B.h" @implementation CTMediator (B) - (UIViewController *)B_viewControllerWithContentText:(NSString  *)contentText { /* BViewController *viewController = [[BViewController alloc] initWithContentText:@"hello, world!"] ; */ NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; params[@"contentText"] = contentText; return [self performTarget:@"B" action:@"viewController" params:params shouldCacheTarget:NO]; } @endCopy the code

#import

#import

UIViewController *viewController = [[CTMediator sharedInstance] B_viewControllerWithContentText:@"hello, world!"] ; [self.navigationController pushViewController:viewController animated:YES];Copy the code

Now, if I compile it again, it passes. Notice, here line A is completely decoupled from line B, and it’s completely decoupled from the main project.

The last step of componentization program implementation: finishing work, component release

One final piece of work at this point is that we created a Category for the B line of business, but not a target-action. So we’ll go to the main project and create a target-Action for the B line of business. You don’t need to touch the B line of business at all, just add the Target_B object:

#import <UIKit/ uikit. h> @interface Target_B: NSObject - (UIViewController *)Action_viewController:(NSDictionary *)params; @end Target_B implementation file:  #import "Target_B.h" #import "BViewController.h" @implementation Target_B - (UIViewController *)Action_viewController:(NSDictionary *)params { NSString *contentText = params[@"contentText"]; BViewController *viewController = [[BViewController alloc] initWithContentText:contentText]; return viewController; } @endCopy the code

The Target object is not intrusive in the main project. If B wants to be a separate component in the future, it can be brought with it.

That’s where the finishing touches end, and we’ve created three private pods: A, A_Category, and B_Category.

The next thing to do is to ship the three private Pods, checking the version number and dependency in podSpec before shipping.

Category dependency does not need to fill in the corresponding line of business, it should only rely on a CTMediator. Another line of business dependency does not depend on the line of business either, only on the Category of the line of business. For example, line A only needs to depend on B_Category, but not on line B or the main project.

Publishing is just a few lines of command:

Git add. Git commit -m "version" git tag version git push origin master --tags./upload.shCopy the code

The command line CD enters the corresponding project, and then executes the above command.

Note that the version number must be the same as that given by S.Sion in the PodSpec file. Upload. sh is generated by the script to configure the private Pod. If you do not have the upload.sh file, you have not configured the private Pod with the script.

Finally, after all the pods have been released, we will change the original local reference in our Podfile back to a normal reference. Remove that from your Podfile, commit and push.

That’s the three steps of componentization, and that’s the end of it.

conclusion

hard code

The hard code of this componentized scheme only exists in the Target object and Category method, and the impact is very small. It will not leak into the business code of the main project or the business code of the business line.

And in the actual implementation of componentization, the componentization of business lines is also done according to category. The target name, action name, and param parameters in the category will be copied to the target object in the business line component. It does not involve objects existing in the line of business itself.

To eliminate this layer of hard code, you would have to introduce a third-party POD that the target object’s line of business and category would depend on. In order to eliminate such a small impact of hard code, and as long as the rules are followed, there is no mistake. It is not cost-effective to introduce a new dependency for this purpose.

Named domain problem

In this practice, the named domain of the responder is not leaked anywhere but the responder, which has the advantage of easy migration.

For example, our responder is an upload component. If you want to replace the upload component, you just need to surround it with a target-action and use it directly. And the package target-action process does not have any invasive effects.

For example, you used to write an upload component based on AFNetworking, but now you use qiniu SDK to upload, so you only need to provide a target-Action to encapsulate qiniu’s upload operation. There is no need to change the code of the SEVEN Cow SDK, nor the code of the caller. This can be a pain in the neck if scheduling is based on URL registration.

Service Management Issues

Because the Target object is in the named domain of the responder, the Target object can provide various actions outside of the page instance.

And, because its essence is for the Action of responder foreign business logic encapsulation (in fact, the encapsulation of service), it can make a responder foreign provides what Action (services), Action (services) the implementation of the logic is what got very good management, can greatly reduce engineering maintenance cost in the future. Then categories solve the problem of how the service should be invoked.

However, in the componentized scheme based on URL registration mechanism and Protocol sharing mechanism, service management is very difficult because the services are scattered all over the responder. If you are still obsessed with such a scheme, you just need to take the three problems mentioned above, compared with the URL registration mechanism and Protocol sharing mechanism of the componentization scheme, you can understand.

In addition, if this scheme gathers all the services into one object for easy management, its essence has become target-action mode, and the Protocol sharing mechanism actually has no meaning.

High cohesion

The componentization scheme based on the protocol sharing mechanism causes the responder’s business logic to leak into the caller’s business logic and does not achieve high cohesion.

If that part of the business is used elsewhere, the code has to be rewritten. Although it can provide a business-cohesive object that conforms to the protocol, in fact this becomes target-action mode again, and the protocol is meaningless.

Intrusive problem

As you can see, the implementation of CTMediator componentization scheme is very safe. Because there are no invasive code changes.

For responders, there is no code to change, just a layer of target-action. For example, in this example, if line B is the responder of service A, no code of service B needs to be modified.

For callers, they only need to change the calling method into CTMediator calling, and the change does not involve the original business logic, so it is very safe.

Another non-invasive feature is that the componentization scheme based on CTMediator can be implemented step by step. The implementation of this solution does not require all lines of business to be isolated as components, nor does the implementation modify the code of the uncomponentized business.

If calls to other lines of business (B lines) are involved in the process of A separate line of business (A line of business), only the Target object is needed. The Target object itself does not modify the uncomponentized line of business (B lines). And in the future, if the corresponding line of business needs to be isolated, only the Target object needs to be copied over.

However, in the componentized scheme based on URL registration and protocol sharing, the registration code and protocol declaration must be written in the uncomponentized line of business, and the corresponding URL and protocol must be allocated to the specific business object. These are actually unnecessary, gratuitous extra maintenance costs.

Registration problem

CTMediator does not have any registration logic code, avoiding the maintenance and management of the registration file. The method given by a Category tells the caller exactly how it should be called.

For example, the B_Category to – (UIViewController *) B_viewControllerWithContentText (contentText nsstrings *); Methods. This allows engineers to understand usage at a glance, rather than having to fumble through the document with a URL.

This can greatly improve work efficiency and reduce maintenance costs.

Timing of componentization

After the MVP phase, the sooner you implement it, the better.

MVP is not a design pattern, but a minimum value product. It is the first stage of product evolution.

Angel wheels are typically used for MVP validation, and at this stage the product loop is undefined, so the logic of the product itself varies. However, after the angel round, the product closed loop has been determined, and at this time, componentization should be implemented to cope with the product expansion after round A.

Some people say that there is no need to implement componentization because my project is small and there are few people. It is true that componentizing a small project is not much better than before, because the small project is not complicated in the first place, and componentizing will not be any easier.

But this is a shortsighted view.

Componentization For a small project, the real advantage comes six months or even a year into the future.

Because when the number of people is small and the project is small, the cost of componentization is also small, and it can be implemented in three or four days. So when the business expands to a larger scale a year from now, it won’t be tied down.

But if you wait until the project is big and you have more people to implement componentization, then the implementation of componentization will be much more complicated than when the scale is small now. It will not be able to be done in three or four days, and the implementation process will be very difficult. You’ll wish you hadn’t componentized sooner.

What about Swift project?

You just have to inherit the Target object from NSObject, and put at sign objc(className). The parameter name of the action should always be one, and the name should always be params. See A_swift for the notation of target in the SWIFT project

Because the Target object is independent of the business implementation, it has no problem inheriting NSObject. The full SwiftDemo is here.

The last

This article was published in 2016. The original address of this article is to implement the componentization scheme based on CTMediator. I believe it is still very useful now

Sharing:IOS development of all kinds of books to download