To read the JSPatch code, you need to have some javascript knowledge. See my previous article JSPatch analysis (I): Calling oc methods with JS

purpose

Invoke OC’s native methods by executing JS scripts using JSPatch.

The test code

Js code

(function(){
    var obj = require("TestJSPatch");
    var clsObj = obj.alloc();
    clsObj.init().testBlock(block("NSString *".function(ctn){
        console.log(ctn); })); }) ()Copy the code

Objective – c code

@interface TestJSPatch : NSObject
- (void)testBlock:(void(^)(NSString *))block;
@end

@implementation TestJSPatch
- (void)testBlock:(void (^)(NSString * _Nonnull))block{
    NSLog(@"---%s---", __func__);
    block(@"123");
}
@end
Copy the code

Regular expressions used by JSPatch

Since JSPacth uses regular expressions, I’ve always heard of regular expressions, but I haven’t systematically studied them. I’ve always had a knot in my heart, so I took the opportunity to learn regular expressions well. This section provides an interpretation of regular expressions used by JSPatch and can be skipped if you are familiar with regular expressions.

JSPatch uses re: (? <! \\)\.\s*(\w+)\s*\(Copy the code

JSPatch handles the code

Before treatment: (function(){
    var obj = require("TestJSPatch");
    var clsObj = obj.alloc();
    clsObj.init().testBlock(block("NSString *".function(ctn){
        console.log(ctn); })); })() : we can see that.alloc() has been replaced with.__c("alloc"() Why do you do this? () Why do you do this? (function(){try{(function(){
    var obj = require("TestJSPatch");
    var clsObj = obj.__c("alloc") (); clsObj.__c("init")().__c("testBlock")(block("NSString *".function(ctn){
        console.__c("log")(ctn); })); }) ()Copy the code

The re used

  1. \s represents any whitespace character, including Spaces, tabs, and newlines
  2. \w represents any letter, number, downline (_)
  3. * represents any number (zero, one, or more)
  4. Plus means at least one
  5. Since (and. Are special characters, so to represent them itself requires an escape, \.\ c

6.() This can be used to express groups. Let me give you an example

\w is matched with alphanumeric underscores, which I'll simply call letters. My example is just lettersexpression: This is is my cat cat
reg: \w+\s  "+"Means that at least one \w matches, followed by a blankresult: 
This    is      is     my      cat      cat   

reg: (\w+)\s(\w+) \ result: This is my cat cat () : (\w+)\s(\w+1And the next one is a group2.Keep reading if you don't understandreg:(\w+)\s(\w+)\s\1The \1That means take the first group of () parentheses, so what this expression really means is"Letter space letter space letter"The form, and the last letter is the same value as the beginning letter, matches of the form A, B, aresult: 
is is is 

reg: (\w+)\s(\w+)\s\2This is my cat cat. This is my cat catCopy the code
  1. (? Exp) this is the same grouping, but the parentheses are not cached, so use the example above
expression: This is is my cat cat 
reg: (? :\w+)\s(\w+)\s\1The first parenthesis is not cached, so the first group starts with the following parenthesis, which is also abbresult:
This is is      my cat cat
Copy the code

8 (? <! X)exp matches any exp that is not preceded by x

x(? =y) --> x <=y)x --> x ! Y) --> x is not a y (? <! Y)x --> x is not a yCopy the code

The meaning of regular

(? <! \\)\.\s*(\w+)\s*\( (? <! \\)\. Matches the symbol ".", but this symbol is not preceded by "\" \s* matches any number of Spaces (\w+) matches any alphanumeric underscore, and this is the first group \(matches parentheses. The match is.alloc(something like thatCopy the code

The basic principle of

Use JSContext to let JS and OC interact.

  1. In oc, to call an instance method of an object, you simply create the object and call the method. JsPatch simply puts the trigger side on the JS side.
  2. Js calls funcA, which is registered using JSContext, so when I call funcA, I go into oc’s native environment.
  3. FuncA passes the name of the class and the name of the selector to the OC, so that the oc has a class and performs a method with a selector
  4. For example, to call NSObject’s alloc method, call funcA(“NSObject”,”alloc”) from the JS invocation and receive the class name and selector from the JSContext, then the NSInvocation is available.

Source code execution process analysis

Js Knowledge Supplement

We want to write JS code to follow the rules of JS, to ensure that the run without error, there is the following code:

  1. The first line is an error because there is no object, just like in OC, there is no object globally
  2. This [“NSObject”] = {__className:”NSObject”} this[“NSObject”] = {__className:”NSObject”
  3. Because I have registered, there will be no error when printing later.

JSPatch. Js part

The require function
Our code:require("TestJSPatch") 

var _require = function(clsName) {
    if (!global[clsName]) {
      global[clsName] = {  // Register TestJSPatch as a global object.
        __clsName: clsName
      }
    } 
    return global[clsName]  // Returns the registered object with which to call the method
  }

  global.require = function() {    // Global is the top assigned this, this is the entire JS runtime environment
    var lastRequire
    for (var i = 0; i < arguments.length; i ++) {   Arguments can be taken from the argument list or arguments. This is similar to the oc method $0 $1, which is the built-in argument. Since this method can register more than one global object directly, it takes arguments directly from arguments
      arguments[i].split(', ').forEach(function(clsName) {
        lastRequire = _require(clsName.trim())
      })
    }
    return lastRequire
  }
Copy the code
___c(“alloc”)
Our codevar obj = require("TestJSPatch");
          var clsObj = obj.__c("alloc"); JSPatch codevar _customMethods = {
    __c: function(methodName) {
      var slf = this   // This is who called this function, so this is who
      
      if (slf instanceof Boolean) {    // Determine if the caller's type is Boolean
        return function() {
          return false}}if (slf[methodName]) {           // Get the caller's methodName property. This methodName is the parameter that's passed around. In our case, it's "alloc".
        return slf[methodName].bind(slf);
      }

      if(! slf.__obj && ! slf.__clsName) {// Check whether the __obj attribute exists
        throw new Error(slf + '. ' + methodName + ' is undefined')}if (slf.__isSuper && slf.__clsName) {
          slf.__clsName = _OC_superClsName(slf.__obj.__realClsName ? slf.__obj.__realClsName: slf.__clsName);
      }
      var clsName = slf.__clsName    // This __clsName is "TestJSPatch"
      if (clsName && _ocCls[clsName]) {
        var methodType = slf.__obj ? 'instMethods': 'clsMethods'
        if (_ocCls[clsName][methodType][methodName]) {
          slf.__isSuper = 0;
          return _ocCls[clsName][methodType][methodName].bind(slf)
        }
      }

      return function(){      // Finally return a function
        var args = Array.prototype.slice.call(arguments)
        return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper)
      }
    }
  }
  
  // This code is executed automatically when the JSPatch engine is loaded. This code adds a __c attribute to all JS objects.
  for (var method in _customMethods) {
    if (_customMethods.hasOwnProperty(method)) {
      Object.defineProperty(Object.prototype, method, {value: _customMethods[method], configurable:false.enumerable: false}}})Copy the code

Similarly, if obj.__c is executed without error, then obj must have an __c attribute. As the comments in the code above make clear, the JSPatch engine adds an __c attribute to all JS objects and returns a function to ensure that obj.__c does not return an error. The last () is to execute this function. Executing this function will execute the _methodFunc function

_methodFunc
Our codevar obj = require("TestJSPatch");
          var clsObj = obj.__c("alloc") ();// Execute this method
          
var _methodFunc = function(instance, clsName, methodName, args, isSuper, isPerformSelector) {
    var selectorName = methodName
    if(! isPerformSelector) { methodName = methodName.replace(/__/g."-")
      selectorName = methodName.replace(/_/g.":").replace(/-/g."_")
      var marchArr = selectorName.match(/:/g)
      var numOfArgs = marchArr ? marchArr.length : 0
      if (args.length > numOfArgs) {
        selectorName += ":"}}// Call the method
    var ret = instance ? _OC_callI(instance, selectorName, args, isSuper):
                         _OC_callC(clsName, selectorName, args)
    return _formatOCToJS(ret)
  }
         
Copy the code

You can see that the final call is _OC_callI/_OC_callC. These two methods are the ones registered with JSContext, and the parameters are clsName and selectorName that we passed. The OC can generate an object based on these two parameters and return the result.

OC part

_OC_callI
Context [@"_OC_callI"] = ^id(JSValue *obj, NSString *selectorName, JSValue *arguments, BOOL isSuper) { return callSelector(nil, selectorName, arguments, obj, isSuper); }; context[@"_OC_callC"] = ^id(NSString *className, NSString *selectorName, JSValue *arguments) { return callSelector(className, selectorName, arguments, nil, NO); };Copy the code
callSelector

I won’t post the code for this method here, but the NSInvocation invocation is the most used method that returns the value.

Call summary

1: [JPEngin startEngin] : when executed, all objects are registered with a __C method. Object. DefineProperty (Object. Prototype, method, {value: _customMethods[method], 64x :false, Enumerable: 64x;} false}) }

Var obj = require(“TestJSPatch”); The value is a JSON and is {__clsName:”TestJSPatch”}, this obj={__clsName: “TestJSPatch”}

3: obj. Alloc: Will call into obj. __c (” alloc “), is {__clsName: “TestJSPatch”}. __c (” alloc “) call The argument to the __c function is alloc, and the argument SLF is the caller of the function {__clsName:”TestJSPatch”}, which returns a function. function(){ var args = Array.prototype.slice.call(arguments) return _methodFunc(slf.__obj, slf.__clsName, methodName, args, slf.__isSuper) }

4: obj.alloc() –> obj.__c(“alloc”)(). We get the argument to the function args, where we didn’t pass an argument, and the function calls _methodFunc, and the argument is slf.__obj = {} and the argument is an object, indicating whether it’s an object method or an instance method called, slf.__clsName=”TestJSPatch”, methodName=”alloc”, args=[], slf.__isSuper=false

5: __OC_callI(intnstance, selectorName, args, __isSuper) or _OC_callC(clsName, selectorName, args)

6: NSInvocation of OC generates the object