JSPatch is an open source framework on GitHub. It can use Objective-C run-time mechanism to dynamically use JavaScript to call and replace Objective-C properties and methods in projects. Its framework is compact, its code is concise, and it interacts with Objective-C through the system's JavaScriptCore framework, which gives it a strong advantage in security and audit risk. Git source code address: /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 initialization engine [JPEngine startEngine]; //Read the js file NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@"js"]; NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil]; //Run the js file [JPEngine evaluateScript:script]; = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds]; = [[ViewController alloc]init]; [ addSubview:[self genView]]; [ makeKeyAndVisible]; return YES; } - (UIView *)genView { UIView * view= [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 320)]; = [UIColor redColor]; return view; }
Add a js file to the project and write it as follows:
require('UIView, UIColor, UILabel') //The class to replace the function defineClass('AppDelegate', { //Replace function //The name of the function to be replaced genView: function() { var view = (); (()) var label = ().initWithFrame(()); ("JSPatch"); (1); (label); return view; } });
When 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 modified to green.
2. Use JavaScript code to modify or add methods to Objective-C
The JSPatch engine supports 3 methods to make JavaScript code calls, namely, use JavaScript strings to run code, read local JavaScript files to run code, and obtain network JavaScript files to run code. For example, if you want a warning box to pop up in your project through JavaScript code, insert the following code into the Objective-C code:
- (void)viewDidLoad { [super viewDidLoad]; // The '\' character is used to perform line breaks [JPEngine evaluateScript:@"\ var alertView = require('UIAlertView').alloc().init();\ ('Alert');\ ('AlertView from js'); \ ('OK');\ (); \ "]; }
Developers can also dynamically add methods to Objective-C class files, for example, write them as follows in the ViewController class:
- (void)viewDidLoad { [super viewDidLoad]; = [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]; }
The JavaScript file code is as follows:
require('UIView, UIColor, UILabel') defineClass('ViewController', { // replace the -genView method creatView: function() { var view = ().initWithFrame({x:20, y:20, width:100, height:100}); (()); var label = ().initWithFrame({x:0, y:0, width:100, height:100}); ("JSPatch"); (1); (label); ().addSubview(view) } });
Apart from the above code, no other method is written in the file. When running the project, you can see that the program does not crash. The ViewController executes the creatView method.
Through the example above, we found that using JSPatch can do something very interesting. For iOS applications, the release of applications through the official channel AppStore must pass manual review. Sometimes this review cycle will be very long. If some small vulnerabilities are left when the developer writes the code, once the application is online, it will be very difficult to modify this bug. With JSPatch, we can imagine how cool it would be if we could locate the method with problematic online applications and use JS files to modify this method. In fact, the main purpose of JSPatch is also to achieve hotfix with minimal problems with online applications.
3. Basic methods for interaction between JavaScript and Objective-C
To use JSPatch to write Objective-C style methods, you need to follow some rules for JavaScript to interact with Objective-C.
1. Use Objective-C class in JavaScript files
When writing JavaScript code, if you need to use Objective-C classes, you must first make a request reference to this class. For example, if you need to use the UIView class, you need to make the following reference before use:
require('UIView')
You can also reference multiple Objective-C classes at once:
require('UIView, UIColor, UILabel')
There is also a simpler way to write it directly when using it:
require('UIView').alloc().init()
2. Make a call to the Objective-C method in a JavaScript file
When calling the Objective-C method, there are two types: one is to call the class method and the other is to call the object method of the class.
Calling class methods: Calling class methods through class name dots. The format is similar to the following, and the parameters are passed in brackets:
()
Calling the instance method: Calling the instance method of the class through object dotting, the format is as follows, and the parameters are passed in brackets:
(label)
For the multi-parameter method in Objective-C, it is converted into JavaScript to split the parameters in _. All parameters are placed in the following brackets and divided by commas. The example is as follows:
(UIColor.colorWithRed_green_blue_alpha(0,0.5,0.5,1))
For the property variables of the Objective-C class, you can only use getter and setter methods to access in JavaScript. The example is as follows:
("JSPatch")
Tip: If the original Objective-C method already contains the _ symbol, use __ instead in JavaScript.
3. Operate and modify Objective-C classes in JavaScript
The biggest application of JSPatch is to dynamically operate and modify classes when the application is run.
Methods to rewrite or add classes:
Use defineClass in JavaScript to define and modify methods in classes, and the writing format is as follows:
/* classDeclaration: The class name to be added or overridden. String. If this class does not exist, a new class will be created. instanceMethods: The instance method to be added or overridden {} classMethods: The class method to be added or overridden {} */ defineClass(classDeclaration, instanceMethods, classMethods)
Examples are as follows:
defineClass('ViewController', { // replace the -genView method newFunc: function() { //Writing example method ().setBackgroundColor(()) } },{ myLoad:function(){ //Writing class methods } } )
If you want to call the original method after rewriting the method in the class, you need to use the ORIG prefix, the example is as follows:
defineClass('ViewController', { // replace the -genView method viewDidLoad: function() { //Writing example method () } } )
For methods called by the super keyword in Objective-C, you can use() to call them in JavaScript, for example:
defineClass('ViewController', { // replace the -genView method viewDidLoad: function() { //Writing example method ().viewDidLoad() } } )
Similarly, JSPatch can also add temporary properties to the class, which is used to pass parameters between methods, use set_Prop_forKey() to add properties, and use getProp() to get properties. Note that the properties added by JSPatch cannot be accessed using the Objective-C setter and getter methods, as follows:
defineClass('ViewController', { // replace the -genView method viewDidLoad: function() { //Writing example method ().viewDidLoad() self.setProp_forKey("JSPatch", "data") }, touchesBegan_withEvent(id,touch){ ("data") ().setBackgroundColor(()) } } )
Regarding the compliance of adding protocols to classes, the compliance method in Objective-C is consistent with the following:
defineClass("ViewController2: UIViewController <UIAlertViewDelegate>", { viewDidAppear: function(animated) { var alertView = require('UIAlertView') .alloc() .initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles( "Alert", "content", self, "OK", null ) () }, alertView_clickedButtonAtIndex:function(alertView, buttonIndex) { ('clicked index ' + buttonIndex) } })
4. Several common types of interaction between JavaScript and Objective-C
1. Structure
In Objective-C code, we often use structures. There are several natively supported structures in JSPatch: CGPoint, CGSize, CGRect, and NSRange. And these structures are often used when performing interface operations.
For CGRect type, JavaScript is created using the following code:
var view = require('UIView').alloc().init() ({x:100,y:100,width:100,height:100})
For CGPoint types, JavaScript is created using the following code:
({x:200,y:200})
For CGSize type, JavaScript is created using the following code:
var size = {width:200,height:200} ({x:100,y:100,width:,height:})
For NSRange types, JavaScript is created using the following code:
var range = {location: 0, length: 1}
2. Selector
For the method selector Selector in Objective-C, it is created in JavaScript using strings, for example:
self.performSelector_withObject("func:", 1)
3. About empty objects
In JavaScript, null and undefined both correspond to nil in Objective-C and NSNull empty objects in Objective-C. NSSnull is used instead in JavaScript.
4. Interaction of blocks in Objective-C and JavaScript
There are two ways to block interaction with Objective-C in JavaScript. One is to call blocks in Objective-C in JavaScript file, and the other is to pass function blocks in JavaScript file as block parameters to Objective-C.
It is very simple to use blocks in Objective-C in JavaScript files, because there is no concept of blocks in JavaScript, Objective-C will be automatically converted into functions. The example is as follows:
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 = () func("123") } })
Passing func as parameter block to Objective-C in JavaScript files is more complicated, and it needs to be wrapped using the 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) { //The run method needs to pass a block (block("NSString*",function(str){(str)})) } })
When wrapping Func in JavaScript using the block() method, block(param1,param2) has two parameters. The first parameter sets the parameter type in func. If there are multiple parameters, use commas to separate it; the second parameter is the func function body.
Note: You cannot use self pointer in func wrapped by block(). If you need to use self, you need to convert temporary variables outside the block. The example is as follows:
defineClass("ViewController", { viewDidAppear: function(animated) { //The run method needs to pass a block var slf = self (block("NSString*", function(str){ (str) (str) })) } })
In JavaScript, use __weak() and __strong to declare weak and strong reference objects, for example:
var slf = __weak(self) var stgSef = __strong(self)
5. About GCD and Enumeration
In JSPatch, you can use the following JavaScript code to call the GCD method:
//block the current thread for a certain period of timedispatch_after(1.0, function(){ }) //Add asynchronous tasks to the main threaddispatch_async_main(function(){ }) //Add synchronization tasks for the main threaddispatch_sync_main(function(){ }) //Add tasks to the global queuedispatch_async_global_queue(function(){ }) JSPatchCannot be used directlyObjective-CEnumeration defined in,But it can be passed with the true value of its enumeration。For example: //The value of UIControlEventTouchUpInside is 1<<6btn.addTarget_action_forControlEvents(self, "handleBtn", 1<<6);