JSPatch link https://github.com/bang590/JSPatch

The goal of this article is to create an OC native object through a JS syntactic call, and call the instance method of this object to print a line of values passed by JS, as shown below.

(function(){
     require("TestObject");
     TestObject.alloc().init().test("---Test_JSPatch---"); }) ();Copy the code

perform

If you have JS base, you can go straight to the text.

Js debugging mode

Js debugging is relatively simple, directly open the browser, call out the developer tools. You can write JS code directly from the console as shown below.

First of all, let’s be clear: all the JS code we write is in accordance with THE JS syntax, can be parsed and executed normally, which is the fundamental basis of JS and native interaction.

Js Basic syntax rules

  • The this: keyword has different meanings in different positions. If the keyword is on the outside, it refers to a global object. In the case of a function, it refers to the object on which the function is called.
  • Object: keyword, representing js Object, similar to NSDictionary in OC. Array represents a JS Array object, similar to OC’s NSArray.
  • Function: the keyword defines a JS function. There are generally two definitions, as shown in the figure above.
    1 : var func1 = function(){}
    2:  function func2(){}
    Copy the code
  • We can use the this keyword to create some global variables, and we can also use it to define some global functions to make our JS syntax conform to rules.

  • Object.defineproperty This can extend objects with extra attributes, sort of like OC’s Category. If object. prototype is passed as the first parameter, it adds an extension to all Object objects
Var obj1 = {} // define an obj1 object obj1.name // Undefined because obj1 has no name attribute DefineProperty (obj1, "name", {value: "syx") object.defineProperty (obj1, "name", {value: "syx"}); Syx var obj2 = {} // Define an obj2 object, obj2.name //obj2 has no name attribute; undefined object.defineProperty (object.prototype, "age", {value: "18"}); Obj1. age //18 obj1 has age property obj2.age //18 obj2 also has age propertyCopy the code
  • (function(){})() : this means that the code inside this function is self-executing, which means that when js is loaded, the code inside this function is executed directly instead of being called again.

The body of the

Js part

TestObject.alloc().init().test("TestJPush");
Copy the code

1) : The require function creates a global object based on the parameters passed to it. TestObject = = {“__clasName”: “TestObject”}.

var global = this; // This assignment makes global look the same as this, just to make it easier to read global.require =functionGlobal [clsName] = {__clsName: clsName}; (clsName){// Define a global require function. }Copy the code

Alloc must have a property called alloc in order for testObject. alloc to be correct. We use object. defineProperty to extend alloc methods to all objects.

Object.defineProperty(Object.prototype, "alloc", {value : function(){// Extend the alloc property to all objectsfunction
    return __OC_callC(this.__clsName, "alloc"); // this __OC_callC is a method registered with JSContext, which is used for interaction.Copy the code

3):TestObject.alloc returns a function, followed by a (), which executes the __OC_callC function in the function body Object, i.e. {__clsName: “TestObject”}. 4): the following init and alloc methods are the same implementation, the complete JS code is as follows

var global = this;       
global.require = function(clsName){// define a global require function global[clsName] = {__clsName: clsName}; } Object.defineProperty(Object.prototype,"alloc", {value : function(){// Add extension methodsreturn __OC_callC(this.__clsName, "alloc"// Call the OC method,callC represents the class method called}}); Object.defineProperty(Object.prototype,"init", {value : function() {return __OC_callI(this, "init", null); //callI means instance method}}); Object.defineProperty(Object.prototype,"test", {value : function() {return __OC_callI(this, "test"."Test_JSPatch") // Also calls an instance method with a string argument}}); (function(){
     require("TestObject");
     TestObject.alloc().init().test("TestJPush"); }) ();Copy the code

OC part

Keep an eye on this: The code for TestObject is testObject.alloc ().init(). OC calls alloc and generates an object (OC’s object) back to JS. Js then calls init(). Js then passes the object to OC via init.

1: The code of the TestObject is as follows.

@interface TestObject : NSObject
- (void)test:(NSString *)para;
@end
@implementation TestObject
- (void)test:(NSString *)para{
    NSLog(@"% @",para);
}
@end
Copy the code

2: js calls __OC_callC/__OC_callI without error, then we need to register these two methods. We use JSContext to register these two methods as follows

self.context = [[JSContext alloc] init];
_context[@"__OC_callC"] = ^( NSString *clsName, NSString *selector){// Register an __OC_callC method that takes two arguments, the Class name (in this case, TestObject) and the selector(in this case, alloc) Class CLS = NSClassFromString(clsName); SEL sel = NSSelectorFromString(selector); void *result = invokeMethod(cls, sel, nil ,false); // Generate our TestObject as result through CLS and sel calls.returnOCObjectToJsObject((__bridge id)result, clsName); // Make a simple wrapper around the generated OC object, like @{@"_obj":OCObject, clsName:"TestObject"}}; _context[@"__OC_callI"] = ^(id obj, NSString *selector, id arg){// Call the instance method, the first argument passed in js is the TestObject * returned by __OC_callC abovetest = obj[@"_obj"]; // We directly fetch the object we needif(arg ! = nil && ! [arg isKindOfClass: NSNull. Class]) {/ / whether the third uncle is nil,testThe () call is one argument selector = [NSString stringWithFormat:@"% @.",selector];
    }
    SEL sel = NSSelectorFromString(selector);
    void *result = invokeMethod(test, sel, arg, true); // Execute methodif (result){
        return OCObjectToJsObject((__bridge id)result, NSStringFromClass(test.class));
    } 
    return OCObjectToJsObject(test, NSStringFromClass(test.class)); // return result}; Invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invocation invokeMethod(id cls ,SEL selector, NSString *arg, bool isInstace){ NSMethodSignature *signature;if(isInstace) {/ / instance method signature = [[CLS class] instanceMethodSignatureForSelector: selector]; }else{/ / class method signature = [CLS methodSignatureForSelector: selector]; } NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; [invocationsetTarget:cls];
    [invocation setSelector:selector];
    if(arg && ! [arg isKindOfClass: NSNull. Class]) {/ / call the argument begins with the subscript 2, 0 is the Target, the first is the selector [invocationsetArgument:&arg atIndex:2];
    }
    [invocation invoke];
    const char *returnType = [signature methodReturnType];
    NSString *type = [NSString stringWithCString:returnType encoding:NSUTF8StringEncoding];
    if ([type isEqualToString:@"@"]){
        void *result;
        [invocation getReturnValue:&result];
        return result;
    }else{
        return(__bridge void *)(cls); }} // Make a simple wrapper around the Object. NSDictionary *OCObjectToJsObject(id object, NSString *clsName){return@ {@"_obj":object, @"clsName":clsName};
}
Copy the code

3: The whole OC code is very basic, not to do too much explanation here, annotations are in the code

thinking

1: We manually define a global method in js for the method we need to call like alloc. If we have a lot of methods, wouldn’t we register a lot of methods?

Alloc () ¶ Alloc () ¶ Alloc () ¶ Alloc () ¶ Alloc () ¶ Alloc () ¶ The shortcomings of this method have been pointed out by the author, and the specific optimization scheme has been given, which the author called the most enjoyable part of writing this library is to do this optimization