The JSPatch framework is used in iOS to make ES2en ES3en interact with JavaScript code

  • 2020-06-07 05:21:12
  • OfStack

JSPatch is an open source framework on GitHub, which can dynamically use JavaScript to call and replace Objective-ES9en properties and methods in projects through Objective-ES4en's ES5en-ES6en mechanism. The small size of the framework, the simplicity of the code, and the interaction with ES11en-ES12en through the system's JavaScriptCore framework give it a strong advantage in terms of both security and audit risk. Git source address: https: / / github com/bang590 / JSPatch.

1. Start with an official demo

Integrate JSPath into an Xcode project through cocoapods and write the following code in the AppDelegate class:


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // Start initializing the engine 
  [JPEngine startEngine];
  // read js file 
  NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
  NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
  // run js file 
  [JPEngine evaluateScript:script];
  self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
  self.window.rootViewController = [[ViewController alloc]init];
  [self.window addSubview:[self genView]];
  [self.window makeKeyAndVisible];
  return YES;
}

- (UIView *)genView
{
  UIView * view= [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 320)];
  view.backgroundColor = [UIColor redColor];
  return view;
}

Add an js file to the project and write as follows:


 require('UIView, UIColor, UILabel')
  // The class to replace the function 
  defineClass('AppDelegate', {
      // Replace function 
        // To replace the name of the function 
        genView: function() {
          var view = self.ORIGgenView();
          view.setBackgroundColor(UIColor.greenColor())
          var label = UILabel.alloc().initWithFrame(view.frame());
          label.setText("JSPatch");
          label.setTextAlignment(1);
          view.addSubview(label);
          return view;
      }
  });

Running the project, you can see that the genView method has been replaced with the method in the js file, and the original red view has been changed to green.

2. Use JavaScript code to modify or add methods to Objective-ES43en

The JSPatch engine supports the invocation of JavaScript code in 3 methods, respectively using JavaScript string for code running, reading the local JavaScript file for code running and obtaining the network JavaScript file for code running. For example, if you want to pop up a warning box in your project with the JavaScript code, insert the following code in the Objective-ES53en code:


- (void)viewDidLoad {
  [super viewDidLoad];
  //  ' \' Character is used to wrap a line 
  [JPEngine evaluateScript:@"\
   var alertView = require('UIAlertView').alloc().init();\
   alertView.setTitle('Alert');\
   alertView.setMessage('AlertView from js'); \
   alertView.addButtonWithTitle('OK');\
   alertView.show(); \
   "];
}

Developers can also dynamically add methods to the ES57en-ES58en class file. For example, the ViewController class is written as follows:


- (void)viewDidLoad {
  [super viewDidLoad];
  self.view.backgroundColor = [UIColor whiteColor];
  [JPEngine startEngine];
  NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"];
  NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil];
  [JPEngine evaluateScript:script];
  [self performSelectorOnMainThread:@selector(creatView) withObject:nil waitUntilDone:nil];
}

JavaScript file code is as follows:


 require('UIView, UIColor, UILabel')
  defineClass('ViewController', {
      // replace the -genView method
        creatView: function() {
          var view = UIView.alloc().initWithFrame({x:20, y:20, width:100, height:100});
          view.setBackgroundColor(UIColor.greenColor());
          var label = UILabel.alloc().initWithFrame({x:0, y:0, width:100, height:100});
          label.setText("JSPatch");
          label.setTextAlignment(1);
          view.addSubview(label);
        self.view().addSubview(view)
      }
  });

Other than the above code, no other methods are written in the ViewController.m file. Run the project and you can see that the program does not crash. ViewController executes the creatView method.

In the example above, we found that you can do 1 out of 10 interesting things with JSPatch. For iOS, the release of an application through the official channel, AppStore, has to go through a manual audit, sometimes a very long audit cycle, and if the developer leaves a small bug in the process of writing the code, once the application is launched, it will be 10 points harder to change the bug. With JSPatch, we can imagine how much it would be if we could locate a problematic method for online applications and use the JS file to modify the method. In fact, the main purpose of JSPatch is to implement hotfix for online applications with minimal problems.

3. Basic method of interaction between JavaScript and ES85en-ES86en

To use JSPatch for Objective-C style writing, you need to follow some rules for JavaScript and ES93en-ES94en interactions.

1. Use the ES98en-ES99en class in JavaScript files

When writing JavaScript code, if you want to use the Objective-ES104en class, you must first use the require reference. For example, if you want to use the UIView class, you need to use the following reference:


require('UIView')

It is also possible to reference multiple Objective-ES111en classes once:


require('UIView, UIColor, UILabel')

There is another more convenient way to write it, which is to refer to it directly when you use it:


require('UIView').alloc().init()

2. Call the ES119en-ES120en method in the JavaScript file

When calling Objective-ES124en methods, there are two types: one is to call class methods, and the other is to call object methods of the class.

Call class methods: Class methods are called by means of class name dot. The format is similar to the following. Parameters are passed in parentheses:


UIColor.redColor()

Invoke instance method: Invoke instance method of class by means of object dot. The format is as follows.


view.addSubview(label)

For the multi-parameter method in Objective-C, it is converted to JavaScript and the position of the parameter segmentation is divided by _. The parameters are all put into parentheses and separated by commas. Examples are as follows:

view.setBackgroundColor(UIColor.colorWithRed_green_blue_alpha(0,0.5,0.5,1))
Attribute variables of the ES140en-ES141en class can only be accessed using the getter and setter methods in JavaScript, as shown below:


 require('UIView, UIColor, UILabel')
  // The class to replace the function 
  defineClass('AppDelegate', {
      // Replace function 
        // To replace the name of the function 
        genView: function() {
          var view = self.ORIGgenView();
          view.setBackgroundColor(UIColor.greenColor())
          var label = UILabel.alloc().initWithFrame(view.frame());
          label.setText("JSPatch");
          label.setTextAlignment(1);
          view.addSubview(label);
          return view;
      }
  });
0

Tip: If the _ symbol is already included in the original ES148en-ES149en method, use __ instead in JavaScript.

3. Operate and modify the Objective-ES155en class in JavaScript

The biggest use of JSPatch is to dynamically manipulate and modify classes at application runtime.

Methods to override or add a class:

In JavaScript, defineClass is used to define and modify methods in a class, written in the following format:


/*
classDeclaration: The class name of the method to add or override   string   If the class does not exist   A new class is created 
instanceMethods: Instance methods to add or override  {}
classMethods: Class methods to add or override  {}
*/
defineClass(classDeclaration, instanceMethods, classMethods)

Examples are as follows:


defineClass('ViewController', {
      // replace the -genView method
        newFunc: function() {
          // Write instance method 
          self.view().setBackgroundColor(UIColor.redColor())
        }
  
      },{

        myLoad:function(){
          // Write class methods 
        }

      }
      )

If you want to call the original method after overriding a method in a class, you need to use the ORIG prefix, as shown below:


 require('UIView, UIColor, UILabel')
  // The class to replace the function 
  defineClass('AppDelegate', {
      // Replace function 
        // To replace the name of the function 
        genView: function() {
          var view = self.ORIGgenView();
          view.setBackgroundColor(UIColor.greenColor())
          var label = UILabel.alloc().initWithFrame(view.frame());
          label.setText("JSPatch");
          label.setTextAlignment(1);
          view.addSubview(label);
          return view;
      }
  });
3

For the super keyword call method in ES175en-ES176en, self.super () can be used in JavaScript, for example:


 require('UIView, UIColor, UILabel')
  // The class to replace the function 
  defineClass('AppDelegate', {
      // Replace function 
        // To replace the name of the function 
        genView: function() {
          var view = self.ORIGgenView();
          view.setBackgroundColor(UIColor.greenColor())
          var label = UILabel.alloc().initWithFrame(view.frame());
          label.setText("JSPatch");
          label.setTextAlignment(1);
          view.addSubview(label);
          return view;
      }
  });
4

Similarly, JSPatch can also add temporary attributes for classes to pass parameters between methods. Use set_Prop_forKey() to add attributes and getProp() to get attributes. Note that the attributes added by JSPatch cannot be accessed using setter and getter methods of ES190en-ES191en, as follows:


 require('UIView, UIColor, UILabel')
  // The class to replace the function 
  defineClass('AppDelegate', {
      // Replace function 
        // To replace the name of the function 
        genView: function() {
          var view = self.ORIGgenView();
          view.setBackgroundColor(UIColor.greenColor())
          var label = UILabel.alloc().initWithFrame(view.frame());
          label.setText("JSPatch");
          label.setTextAlignment(1);
          view.addSubview(label);
          return view;
      }
  });
5

Regarding the compliance of adding a protocol to a class, and the way of compliance in ES197en-ES198en 1, as follows:


 require('UIView, UIColor, UILabel')
  // The class to replace the function 
  defineClass('AppDelegate', {
      // Replace function 
        // To replace the name of the function 
        genView: function() {
          var view = self.ORIGgenView();
          view.setBackgroundColor(UIColor.greenColor())
          var label = UILabel.alloc().initWithFrame(view.frame());
          label.setText("JSPatch");
          label.setTextAlignment(1);
          view.addSubview(label);
          return view;
      }
  });
6

4. Several common types of JavaScript interaction with Objective-C

1. The structure

In the Objective-ES210en code, we often use structs. The native supported constructs in JSPatch are as follows: CGPoint, CGSize, CGRect, NSRange. And these structures are often used in interface operations.

For the CGRect type, JavaScript is created using the following code:


  var view = require('UIView').alloc().init()
  view.setFrame({x:100,y:100,width:100,height:100})

For the CGPoint type, JavaScript is created using the following code:


view.setCenter({x:200,y:200})

For the CGSize type, JavaScript is created using the following code:


 require('UIView, UIColor, UILabel')
  // The class to replace the function 
  defineClass('AppDelegate', {
      // Replace function 
        // To replace the name of the function 
        genView: function() {
          var view = self.ORIGgenView();
          view.setBackgroundColor(UIColor.greenColor())
          var label = UILabel.alloc().initWithFrame(view.frame());
          label.setText("JSPatch");
          label.setTextAlignment(1);
          view.addSubview(label);
          return view;
      }
  });
9

For the NSRange type, JavaScript is created using the following code:


  var range = {location: 0, length: 1}

2. Selector Selector

For the method selector Selector in ES243en-ES244en, create it as a string in JavaScript, for example:


self.performSelector_withObject("func:", 1)

3. About empty objects

In JavaScript, both null and undefined correspond to nil in ES255en-ES256en, and NSNull null object in ES258en-ES259en is replaced by nsnull in JavaScript.

4. block interaction in ES265en-ES266en and JavaScript

There are two ways for JavaScript and Objective-ES273en to interact with block: one is to call ES276en-ES277en in JavaScript file, and the other is to pass the function block in JavaScript file as block parameter to ES281en-ES282en.

It is easy to use block10 from Objective-C in the JavaScript file, since there is no concept of block in JavaScript, ES291en-ES292en is automatically converted into a function, as shown below:

Objective - C:


typedef void(^block)(NSString * str);
@interface ViewController ()
@end
@implementation ViewController
-(block)getBlock{
  block block = ^(NSString * str){NSLog(@"%@",str);};
  return block;
}
@end

JavaScript:


defineClass("ViewController", {
      viewDidAppear: function(animated) {
       var func = self.getBlock()
        func("123")
      }
      })

Passing func as the parameter block to ES307en-ES308en in JavaScript file is a little more complicated and needs to be wrapped with block() method, for example:

Objective - C:


@interface ViewController ()
@end
@implementation ViewController

-(void)run:(void(^)(NSString * str))block{
  block(@"123");
}
@end

JavaScript:


defineClass("ViewController", {
      viewDidAppear: function(animated) {
      //run  Method needs to be passed in 1 a block
      self.run(block("NSString*",function(str){console.log(str)}))
      }
      })

When wrapping Func in JavaScript using THE block() method, block(param1,param2) has two parameters. The first parameter sets the type of parameter in func. The second parameter is the func function body.

Note: self Pointers cannot be used in func wrapped in block(). If self is needed, temporary variables need to be converted outside of block, as shown below:


defineClass("ViewController", {
      viewDidAppear: function(animated) {
      //run  Method needs to be passed in 1 a block
      var slf = self
      self.run(block("NSString*",
              function(str){
              console.log(str)
              slf.log(str)
              }))
      }
      })

Per ___, use ___ 340en () and ___ 341en to declare a weak and strong reference object, for example:


 var slf = __weak(self)
 var stgSef = __strong(self)

5. GCD and Enumeration

In JSPatch, the GCD method can be called using the following JavaScript code:


// Blocking the current thread 1 Set the time 
dispatch_after(1.0, function(){ 
})
// Add asynchronous tasks to the primary thread 
dispatch_async_main(function(){ 
})
// Add synchronization tasks for the primary thread 
dispatch_sync_main(function(){ 
})
// Adds tasks to the global queue 
dispatch_async_global_queue(function(){ 
})
JSPatch Can not be used directly in Objective-C But can be passed with the real value of its enumeration. Such as: 

//UIControlEventTouchUpInside The value is 1<<6
btn.addTarget_action_forControlEvents(self, "handleBtn", 1<<6);


Related articles: