IOS Component Development

WTModule component communication

background

In terms of componentized development, I went through several different companies, and I happened to be in different stages of development. The first Hong Kong listed company, mobile business line a lot, mainly is the instant messaging function, similar to the microblogging community functions, enterprise ERP functions, video education function, to various lines of business independently, call each other only by provide static library or framework for each other, with each upgrade needs to be integrated, more troublesome, many problems, The feedback time to solve the problem is also very long. After that, the company’s background business was reconstructed, and the mobile terminal was followed by componentized development, which went through the whole process. The second company, a well-known domestic Internet enterprise, has completed the componentization work when joining the development team. Each business staff develops on their own business line, and each business line is tested independently. Working on such business line can be more dedicated to the completion of personal business, with higher efficiency. The current company has no componentization, serious business coupling and frequent use of global variables. As A result, business A is often modified, resulting in bugs in business B. At this stage, componentization begins.

Personal thought: When to do component development

  • Project management: more than 2 business lines of the project need to be separated independently. With the separation of services, the corresponding business components are naturally independent.
  • Personnel management: With the increase of personnel, too many people modify the same piece of code, resulting in a higher possibility of bugs. At this time, personnel and their maintenance functions need to be reassigned.
  • Testing dimension: As the volume of business on a project increases, it becomes difficult to do unit testing without componentization. For each small function modification, the test needs to test the App, which seriously increases the test workload.
  • To sum up: When your App businesses are cross-coupled, the bug rate is hard to drop, and testing does a lot of repetitive work every day. When modifying project impact among developers, you need to consider componentization.

How do you do component development

Use CocoaPods for independent component development CocoaPods publishes private POD components

components





Architecture diagram. PNG

Common components:

  • Buried point component
  • Common Components (Aggregate utility classes)
  • Start the component
  • Performance Monitoring Component
  • Locate the component
  • Image processing component
  • UIKit encapsulates and extends components
  • Business life cycle and communication components

Network components:

  • Basic network components are encapsulated based on AFNetworking, providing JSON to Model, and caching functions
  • DNS Accelerator

Persistent component

  • Encapsulate components based on FMDB

Third-party service components are adapted based on third-party SDK

  • Sharing components
  • Push the component

Underlying business components

  • User component, save User information, login, logout state

Component lifecycle management

  • The lifecycle of the component should be consistent with the lifecycle of the App.




    Component lifecycle. PNG

The source code

#WTAppDelegate.h @interface WTAppDelegate : UIResponder<UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @end #WTAppDelegate.m #import "WTAppDelegate.h" #import "WTModuleLifecycle+AppDelegate.h" @implementation WTAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { return [[WTModuleLifecycle instance] application:application didFinishLaunchingWithOptions:launchOptions]; } - (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *) notificationSettings {/ / registered remote notifications [application registerForRemoteNotifications]; } - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [[WTModuleLifecycle instance] application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [[WTModuleLifecycle instance] application:application didFailToRegisterForRemoteNotificationsWithError:error]; } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { [[WTModuleLifecycle instance] application:application didReceiveRemoteNotification:userInfo]; } - (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification { [[WTModuleLifecycle instance] application:application didReceiveLocalNotification:notification]; } - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler { return [[WTModuleLifecycle instance] application:application continueUserActivity:userActivity restorationHandler:restorationHandler]; } - (void)applicationDidEnterBackground:(UIApplication *)application { [[WTModuleLifecycle instance] applicationDidEnterBackground:application]; } - (void)applicationDidBecomeActive:(UIApplication *)application { [[WTModuleLifecycle instance] applicationDidBecomeActive:application]; } - (void)applicationWillResignActive:(UIApplication *)application { [[WTModuleLifecycle instance] applicationWillResignActive:application]; } - (void)applicationWillTerminate:(UIApplication *)application { [[WTModuleLifecycle instance] applicationWillTerminate:application]; } - (void)applicationDidReceiveMemoryWarning:(UIApplication *)application { [[WTModuleLifecycle instance] applicationDidReceiveMemoryWarning:application]; } - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { return [[WTModuleLifecycle instance] application:application openURL:url sourceApplication:sourceApplication annotation:annotation]; } - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id>  *)options { return [[WTModuleLifecycle instance] application:app openURL:url options:options]; } - (void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void(^)(BOOL succeeded))completionHandler { [[WTModuleLifecycle instance] application:application performActionForShortcutItem:shortcutItem completionHandler:completionHandler]; } - (void)applicationWillEnterForeground:(UIApplication *)application { [[WTModuleLifecycle instance] applicationWillEnterForeground:application]; }Copy the code

Intercomponent communication

Current situation: Mogujie’s solution is very popular at present. Through routing protocol, the jump between pages in all apps can be realized. But we didn’t choose to do that.

  • First of all, we define an App: it has the same life cycle as App, can compile independently, has independent functions, and provides a unified interface externally.

From the above definition, we will see that the routing protocol does not apply to our component definition. Analysis: page routing breaks down the independence between components, although accessed through a unified protocol. But there’s a problem with that. A subinterface of component A that accesses the subinterface of component B. This is somewhat contradictory to our understanding of components. The subinterface of component A needs to invoke the subinterface of component B through the interface provided by component B.

Our implementation

  1. The registered component is held by WTModuleLifecycle as a medium
  2. Communication between components is realized through unified API calls

code

#import "WTModuleMessager.h" #import <objc/message.h> #import "WTModuleLifecycle.h" @interface WTModuleMessager() @property (nonatomic, strong) NSMutableArray *services; @end @implementation WTModuleMessager + (instancetype)instance { static dispatch_once_t oncePredicate; static WTModuleMessager *instance; dispatch_once(&oncePredicate, ^{ instance = [[self alloc] init]; }); return instance; } - (instancetype)init { self = [super init]; if (self) { _services = [NSMutableArray array]; } return self; } + (id)performAPI:(NSString *)moduleName methodName:(NSString *)methodName withParams:(NSDictionary *)params { Class targetClass = [WTModuleMessager serviceClassFromString:moduleName]; SEL targetSelector = [WTModuleMessager selectorFromString:methodName hasCallBack:NO]; if (! [WTModuleMessager validateClass: targetClass selector: targetSelector]) {NSAssert (targetClass, @ "% @ name is not standard," moduleName); } // id targetOpenService = [[targetClass alloc]init]; id targetOpenService = [[WTModuleLifecycle instance] moduleInstanceByName:NSStringFromClass(targetClass)]; if ([targetOpenService respondsToSelector:targetSelector]) { return ((id (*)(id, SEL, id))objc_msgSend)(targetOpenService, targetSelector, params); } else {NSAssert(targetClass, @"%@ can't find corresponding class method ",methodName); return nil; } } + (BOOL)performAPI:(NSString *)moduleName methodName:(NSString *)methodName withParams:(NSDictionary *)params block:(WTModuleMessagerCallback)block { Class targetClass = [WTModuleMessager serviceClassFromString:moduleName]; SEL targetSelector = [WTModuleMessager selectorFromString:methodName hasCallBack:YES]; if (! [WTModuleMessager validateClass: targetClass selector: targetSelector]) {NSAssert (targetClass, @ "% @ name is not standard," moduleName); } id targetOpenService = [[targetClass alloc]init]; if ([targetOpenService respondsToSelector:targetSelector]) { [[WTModuleMessager instance].services addObject:targetOpenService]; return ((BOOL (*)(id, SEL, id, WTModuleMessagerCallback))objc_msgSend)(targetOpenService, targetSelector, params,[WTModuleMessager callbackBlock:block openService:targetOpenService]); } else {NSAssert(targetClass, @"%@ can't find corresponding class method ",methodName); return NO; } } + (WTModuleMessagerCallback)callbackBlock:(WTModuleMessagerCallback)callbackBlock openService:(NSObject *)openService { WTModuleMessagerCallback block = ^(NSError *error, id result){ [[WTModuleMessager instance].services removeObject:openService]; callbackBlock(error,result); }; return [block copy]; } + (BOOL)validateClass:(Class)targetClass selector:(SEL)targetSelector { if (targetClass && targetSelector) { return YES; } return NO; } + (Class)serviceClassFromString:(NSString *)moduleName { NSArray *pathArray = [moduleName componentsSeparatedByString:@"/"]; NSMutableString *serviceClassMS = [[NSMutableString alloc] initWithString:@"WT"]; [serviceClassMS appendString:[NSString stringWithFormat:@"%@%@", [[pathArray.firstObject substringToIndex:1] capitalizedString], [pathArray.firstObject substringFromIndex:1]]]; [serviceClassMS appendString:@"Module"]; return NSClassFromString(serviceClassMS); } + (SEL)selectorFromString:(NSString *)methodName hasCallBack:(BOOL)hasCallBack { NSArray *pathArray = [methodName componentsSeparatedByString:@"/"]; NSMutableString *serviceClassMS = [[NSMutableString alloc] init]; [serviceClassMS appendString:[NSString stringWithFormat:@"%@%@", [[pathArray.firstObject substringToIndex:1] lowercaseString], [pathArray.firstObject substringFromIndex:1]]]; NSString *selectorString = nil; if (hasCallBack) { selectorString = [NSString stringWithFormat:@"%@:block:",serviceClassMS]; } else { selectorString = [NSString stringWithFormat:@"%@:",serviceClassMS]; } return NSSelectorFromString(selectorString); }Copy the code

Componentized base code Github address