What is componentization?

After looking at many other solutions, there was a slight disagreement on the idea of componentization. I think a lot of people are wrong about componentization in iOS. There is a lot of componentization in Flex development, plus after working on a web project with Vue recently, I realized that componentization is a bit inappropriate in iOS development. First of all, I think component is a relatively small functional module, which does not need to have too much communication with the outside world, let alone rely on other third parties, which is particularly important. For example, MJRefresh, as almost all iOS developers know it, is almost business independent and provides a good calling interface and usage experience called a component. While I have seen many schemes, most of them are about communication and decoupling between business components in the App. In fact, I prefer to call these things “modules”. How to distinguish the two, I think this sentence is easier to understand: core business modular, common function componentization. For example, your App is an e-commerce project, name your product details page, list page, shopping cart, search and other pages must be called very frequently VC, the jump between these interfaces will be very frequent. This creates interdependence and high coupling. As shown below:

Why componentization (modularization)

In the process of business development, the client is getting bigger and bigger, and a large number of business logic codes are stacked. The codes of different business modules are called and nested with each other. The coupling between the codes is getting higher, and the call logic becomes more and more chaotic. When a module needs to be upgraded, changing the code often feels like a big deal. Especially if the engineering design did not consider the encapsulation of the interface, and a large number of business code and functional module code mixed together, the future upgrade will need to modify and adjust the code a lot, the work is very huge. This requires the design of a set of middleware to meet the requirements of communication between components. Modularization can encapsulate the functional logic of the code as far as possible, and only provide interfaces externally. The business logic code and the functional module are weakly coupled through interfaces.

My idea of modular architecture

How to optimize communication between modulars

As long as you have some understanding of object orientation, it should not be difficult to implement, just make sure to write the call interface, I won’t go over it here. The most important part of modularization is the communication between modules. For example, in the commodity search list page, you need to view the shopping cart function and view the commodity details function, and the commodity list of the shopping cart can also click the commodity to the commodity details page. And so on, all of these pages are going to call each other, depend on each other. How do we do that in general? Like this:

#import "ProductDetailViewController.h"
#import "CartViewController.h"

@implementation ProductListViewController

- (void)gotoDetail {
	ProductDetailViewController *detailVC = [[ProductDetailViewController alloc] initWithProId:self.proId];
	[self.navigationController pushViewController:detailVC animated:YES];
}

- (void)gotoCart {
	CartViewController *cartVC = [[CartViewController alloc] init];
	[self.navigationController pushViewController:cartVC animated:YES];
}

@end
Copy the code

I believe that this code is familiar to everyone, basically do so. And there’s nothing wrong with that. But once the project got big, there were problems. Each module will depend on each other as long as it calls each other. Each jump needs to import the corresponding controller and rewrite the code once. If a small requirement change is made somewhere, such as passing in an extra parameter to the product details page, it is not efficient to find a way to make the change in each call.

Use middleware

It was easy to come up with a way to provide an intermediate layer: Router. Define the method for each jump in the Router, and then call the Router function from the interface you want to use, passing in the corresponding parameters. Like this:

// Router.m
#import "ProductDetailViewController.h"
#import "CartViewController.h"

@implementation Router

+ (UIViewController *)getDetailWithParam:(NSString *)param {
	ProductDetailViewController *detailVC = [[ProductDetailViewController alloc] initWithProId:self.proId];
	return detailVC;
}

+ (UIVIewController *)getCart {
	CartViewController *cartVC = [[CartViewController alloc]init];
	return cartVC;
}

@end
Copy the code

Use this in other interfaces:

#import "Router.m"

UIViewController *detailVC = [[Router instance] jumpToDetailWithParam:param];
[self.navigationController pushViewController:detailVC];
Copy the code
Run the runtime

Each VC will depend on the Router, and the Router will depend on all VCS. How does name break this layer of circular references? OC has a magic touch: Runtime.

- (UIViewController *)getViewController:(NSString *)stringVCName {
	Class class = NSClassFromString(stringVCName);
	UIViewController *controller = [[class alloc]init];
	if (controller == nil) {
		NSLog("No class found: %@",stringVCName);
		controller = [[RouterError sharedInstance] getErrorController];
	}
	return controller;
}
Copy the code

So the picture above looks something like this:

UIViewController *controller = [[Router shaedInstance] getViewController:@"ProductDetailViewController"];
[self.navigationController pushViewController:controller];
Copy the code
How to Pass parameters

I’m sure many of you have figured out how to pass parameters in this way. For example, the product details page should have at least one productID. Don’t worry, we can manipulate the above method a bit, passing in a dict as an argument:

- (UIViewController *)getViewController:(NSString *)stringVCName  {
	Class class = NSClassFromString(stringVCName);
	UIViewController *controller = [[class alloc]init];
	return controller;
}

- (UIViewController *)getViewController:(NSString *)stringVCName witParam:(NSDictionary *)paramdic {
	UIViewController *controller = [self getViewController:stringVCName];
	if(controller ! = nil) { controller = [self controller:controller withParam:paramdict andVCname:stringVCName]; }else {
		NSLog(@"No class found: %@",stringVCName); // EXCEPTION Push Normal Error VC controller = [[RouterError sharedInstance] getErrorController]; }returncontroller; } /** This method is used to initialize parameters (the default controller initialization method is initViewControllerParam. The initialization method can be customized, provided the VC must implement it. To be more flexible, you can also add a parameter, actionName, passed as a parameter. But then you need to modify the method). @param Controller Obtained instance vc. vam Paramdic Instantiation parameter @param vcName Controller name @returnAfter initialization VC */ - (UIViewController *)controller:(UIViewController *)controller withParam:(NSDictionary *)paradic andVCname:(NSString *)vcName { SEL selector = NSSelectorFromString(@"initViewControllerParam:");
	if(! [controller respondsToSelector: selector]) {/ / if no definition initialization parameter method, direct return, there is no need to set parameters in down way NSLog (@"Target class: %@ Undefined: %@ method", the controller, @"initViewControllerParam:");
		returncontroller; } // Add key information to initialization parameters to facilitate route verification in the controllerif (paradic == nil) {
		paramdic = [[NSMutableDictionary alloc] init];
		[paradic setValue:vcName forKey:@"URLKEY"];
		SuppressPerformSlelctorLeakWarning([controller performSelector:selector withObject:paramdic]);
	} else {
		[paramdic setValue:vcName forKey:@"URLKEY"];
	}
	SuppressPerformSelecorLeakWarning([controller performSelector:selector withObject:paramdic]);
	return controller;
}
Copy the code

By default, we have an initViewControllerParam method in the business controller, and then we can manually trigger this method in the router with respondsToSelector, passing in paramdic. Of course, if you want to be more flexible, pass the initViewControllerParam initialization method to the router as an actionName parameter. Something like this:

- (UIViewController *)controller:(UIViewController *)controller withParam:(NSDictionary *)paramdic andVCName:(NSString *)vcName actionName:(NSString *)actionName { SEL selector = NSSelectorFromString(actionName); . The following is the same code}Copy the code

So this is basically modularity. Basically over 100 lines of code solves communication and high decoupling between complex business modules.

conclusion

Modular implementation method in iOS development summary is relatively easy to achieve, mainly OC itself is a dynamic language. Object types are determined at run time, and calling methods are implemented in OC as sending messages. This increases the number of manipulable possibilities. This approach is likely to work well in most apps and address most business needs.