preface
Before componentization, APP was developed in a project with a small number of developers and slow business development. Therefore, it is appropriate not to use componentization development in the project. However, as more developers, more code, and more complexity of the business, a single development model presents some drawbacks:
- Serious coupling code [code with no clear constraints and getting bloated]
- Low development efficiency [difficult to maintain and high maintenance cost]
To address these issues, a componentized development strategy emerged with the following benefits:
- Facilitate targeted testing
- R&d personnel maintain different modules [Improve development efficiency and reduce maintenance cost]
There are currently three approaches to componentized development: Url-block, protocol-class and CTMediator target-Action scheme. This paper mainly focuses on the CTMediator target-Action scheme used in my project, and will analyze its advantages and disadvantages from the source code.
A, preparation,
The following concepts are helpful in understanding the static language nature of Swift.
Q: What is the difference between a dynamic language, a static language, a dynamically typed language, a statically typed language, a compiled language and an interpreted language?
1.1 Compiled languages
A language that requires a compiler to compile source code into machine code before it can be executed. Generally, it needs to go through the two steps of compile and linker. Compilation is to compile the source code into machine code, while linking is to connect the machine code and dependency library of each module to generate executable files.
Advantages:
-
Compilers typically have a precompilation process to optimize code. Because compilation is done only once, no compilation is required at runtime, programs in compiled languages execute efficiently.
-
Can run independently of the locale.
Disadvantages:
- If a module needs to be modified after compilation, it needs to be recompiled. The compilation generates machine code according to the corresponding runtime environment, which can be problematic for porting between different operating systems. Different executables need to be compiled depending on the operating system environment in which you are running.
Languages: C, C++, object-C and Swift
1.2 Interpreted language
Interpreted programs do not need to be compiled. Compared with compiled languages, interpreted programs are translated line by line as they run.
Advantages:
- Good platform compatibility, can run in any environment
- Flexible, modify the code when the modification can be quickly deployed
Disadvantages:
- Every run has to be interpreted, and performance is inferior to compiled languages.
Languages: JavaScript, Python, PHP, Ruby
1.3 Dynamic Language
Is a language whose structure can be changed at runtime: existing functions, objects can be deleted, or other structural changes can be made. In layman’s terms, the runtime can change its structure based on certain conditions.
Languages: Object-C, C#, JavaScript, PHP, Python
Object C is a compiled language, but it is also a dynamic language. Thanks to the special run time mechanism, OC code can insert and replace methods at runtime.
1.4 Static Language
A language whose runtime structure is immutable is a static language.
Language: Java, C, Swift
1.5 Dynamically typed languages
Dynamically typed languages and dynamic languages are completely different concepts.
A dynamically typed language is a language that does type checking at runtime, which means data types, and a dynamically typed language which means running changes structure, which means code structure.
The datatypes of dynamically typed languages are not determined at compile time, but rather defer type binding to run time.
Languages: Python, Ruby, JavaScript, Swift, PHP.
1.6 Statically typed languages
Static language data types are determined at compile time, and variables are explicitly typed when writing code.
Language: C, C++, C#, Java, object-C.
Development:
@objc is used to export the Swift API to Objective-C and Objective-C Runtime. If your class inherits from Objective-C (such as NSObject), the @objc flag will be automatically inserted by the compiler. Methods and properties with the @objc flag are not guaranteed to be called at runtime because Swift does static optimization. To be called completely dynamically, you must use the dynamic modifier. Using the dynamic modifier will implicitly add the @objc identifier.
CTMediator source code
Swift, due to the nature of the static, up to now there is no good way of decoupling, is because of the type of static languages is determined at compile time, the importance of the above preparation 】 【 condemned it can’t be used as the above string can get, so now the iOS commonly used decoupling framework is written OC, if want to use decoupling framework, that can only supplement!
CTMediator code structure diagram
Below we will explain the detailed content of CTMediator source code.
2.1 Target-Action working diagram
Target-action takes a Runtime to complete the call.
2.2 Local Call Entry
Here is the source code for CTMediator called locally:
Local component calls: Local component A calls [[CTMediator sharedInstance] performTarget:targetName Action :actionName in one place Params :@{…}] initiates a cross-component call to CTMediator. CTMediator is based on the sent target and action, and then converted to target instance and action through OC Runtime mechanism. Finally, it calls the logic provided by the target business to fulfill the requirements.
First, the Runtime is used for reflection to convert class strings and method strings into class and SEL methods.
/** * @param targetName class object OC class object is to be prefixed with Target_ * @param actionName method name is actually called with Action_ prefix * @param Params parameter * @param shouldCacheTarget whether to cache concatenated class objects * * @return return value a string in JSon format */ - (id)performTarget:(NSString) *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget { // For swift project use, swift must add module name, Swift engineering get Target need to take the name of the module nsstrings * swiftModuleName = params [kCTMediatorParamsKeySwiftTargetModuleName]; // generate target NSString *targetClassString = nil; if (swiftModuleName.length > 0) { targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName]; } else {// Build class string targetClassString = [NSString stringWithFormat:@"Target_%@", targetName]; } NSObject *target = self.cachedTarget[targetClassString]; If (target == nil) {Class targetClass = NSClassFromString(targetClassString); target = [[targetClass alloc] init]; } NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName]; SEL action = NSSelectorFromString(actionString); If (target == nil) {if (target == nil) {if (target == nil) {if (target == nil) { I just return. In practice, you can give a fixed target in advance to be used at this time, Then process this request of [self NoTargetActionResponseWithTargetString: targetClassString selectorString: actionString originParams:params]; return nil; If (shouldCacheTarget) {self.cachedTarget[targetClassString] = target; } / / the object is to response on this method the if ([target respondsToSelector: action]) {return [self safePerformAction: the action target: the target params:params]; } else {// This is where the unresponsive request is handled. If there is no response, try calling the notFound method corresponding to target to handle SEL action = NSSelectorFromString(@"notFound:"); if ([target respondsToSelector:action]) { return [self safePerformAction:action target:target params:params]; } else {// This is also where unresponsive requests are handled. If notFound is not present, this demo will return directly. In practice, you can use the fixed target above mentioned. [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params]; [self.cachedTarget removeObjectForKey:targetClassString]; return nil; }}}Copy the code
If both target and action have values, The [self safePerformAction: Action Target :target params:params] method is called ** *
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
if(methodSig == nil) {
return nil;
}
const char* retType = [methodSig methodReturnType];
if (strcmp(retType, @encode(void)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
return nil;
}
if (strcmp(retType, @encode(NSInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSInteger result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(BOOL)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
BOOL result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(CGFloat)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
CGFloat result = 0;
[invocation getReturnValue:&result];
return @(result);
}
if (strcmp(retType, @encode(NSUInteger)) == 0) {
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:¶ms atIndex:2];
[invocation setSelector:action];
[invocation setTarget:target];
[invocation invoke];
NSUInteger result = 0;
[invocation getReturnValue:&result];
return @(result);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
}
Copy the code
Encapsulate the message and message receiver into an object and execute. Generate the method signature with target-Action, then create the NSInvocation object and invoke.
2.2 Remote Call Entry
Remote application call: Remote applications are made by way of openURL by iOS The system configures the application to find the response URL according to the scheme in info.plist. After receiving the URL directly through the AppDelegate, the application calls the OpenURL method of CTMediator to pass in the received information. Of course,CTMediator can also use the openURL: Options: method of CTMediator to receive option as well, depending on whether the option data is included. After passing the URL, CTMediator parses the URL and routes the request to the corresponding target – Action, the subsequent procedure becomes the local application call procedure above, and finally completes the response.
/* scheme://[target]/[action]? [params] url sample: aaa://targetA/actionB? id=1234&title=title [url query]: id=1234&title=title [url path]: /actionB [url host]: TargetA */ - (id)performActionWithUrl:(NSURL *)url completion:(void (^)(NSDictionary *))completion {// processing of url parameters NSMutableDictionary *params = [[NSMutableDictionary alloc] init]; // NSString *urlString = [url query]; for (NSString *param in [urlString componentsSeparatedByString:@"&"]) { NSArray *elts = [param componentsSeparatedByString:@"="]; if([elts count] < 2) continue; [params setObject:[elts lastObject] forKey:[elts firstObject]]; } // This is written mainly for security reasons, preventing hackers from remotely calling local modules. This is sufficient for most scenarios, but more complex security logic can be done if the requirements are more stringent. NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""]; if ([actionName hasPrefix:@"native"]) { return @(NO); } // The demo handles URL routing very simply, taking the corresponding target name and method name, but this is enough for most of the requirements. If you need to expand, Result = [self performTarget:url.host Action :actionName params:params shouldCacheTarget:NO]; if (completion) { if (result) { completion(@{@"result":result}); } else { completion(nil); } } return result; }Copy the code
Development:
NSClassFromString gets a class by the name of the string, you can get it by Target NSSelectorFromString gets an SEL by the string (the name of an existing method)Copy the code
3, project
From the search results page – another module details page as shown above, spanning two modules, the project uses CTMediator’s target-Action mode. The following screenshots are taken in the order of execution:
Func tableView(_ tableView: UITableView, didSelectRowAt indexPath: indexPath)
3.2 Enter openBrokerDetailScene and start calling CTMediator [Company adopts Viper’s variant architecture]
3.3 Start going to EngineToBroker_viewController and call self.performTarget(“Broker”, Action: “brokerDetailVC”, Params: Params, shouldCacheTarget: false)
The following method is executed by calling self.performTarget(“Broker”, action: *****)
3.4 Finally get the ViewController, back to jump to
This completes the use of CTMediator in Swift.