Summary: Dynamic hot fixes are serious bugs that can crash your app and cause it to be too slow to submit a new version to the App Store for review. In this case, you can use JSPatch to write native iOS apps in JavaScript.
JSPatch lets you write native iOS apps in JavaScript. Simply by introducing a very small engine into your project, you can use JavaScript to call any native interface of Objective-C and gain the advantages of the scripting language: dynamically adding modules to your project, or dynamically fixing bugs by replacing your project’s native code.
Daily updates:
www.jianshu.com
(a) First at the terminal
pod search JSPatch
Copy the code
Then you can pod yourself into the project you need to introduce, or download to the local and add yourself to the project search results below
-> JSPatch (0.1.4) JSPatch bridge Objective-C and JavaScript. You can call any Objective C class and method in JavaScript by just including a small engine. Pod 'JSPatch', '~> 0.1.4' -homepage: https://github.com/bang590/JSPatch - Source: https://github.com/bang590/JSPatch.git - Versions: 0.1.4 0.1.3, 0.1.2,, while 0.1, 0.0.3, hundreds of 0.0.1 [master repo] - 0.1.4 0.1.3, 0.1.2,, while 0.1, 0.0.3, hundreds, 0.0.1 [master-1 repo] -subspecs: -Jspatch /Core (0.1.4) -Jspatch /Extensions (0.1.4) macxy:~ xy$Copy the code
Normally we will execute the “.js” file on the server side and every time the program starts, we can perform a comparison between the local JS file and the server-side JS file to see if any changes have been made and I won’t go into the details here
- (BOOL) application: (UIApplication *) application didFinishLaunchingWithOptions: (launchOptions NSDictionary *) {/ / load the engine [JPEngine startEngine]; NSString *sourcePath = [[NSBundle mainBundle] pathForResource:@"demo" ofType:@" JS "]; NSString *script = [NSString stringWithContentsOfFile:sourcePath encoding:NSUTF8StringEncoding error:nil]; [JPEngine evaluateScript:script]; Get update JS [NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://cnbang.net/test.js"]] queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; // Execute js [JPEngine evaluateScript:script]; }Copy the code
This is the general process to describe the application of js substitution;
How to access the OC API when oc interacts with JS
/ / button event defineClass (' YFJSPatchMainViewController, {/ / in which class method buttonTouch: Function (button) { Function (parameters). / / jump to the tableView var tableViewCtrl = YFJSPatchTsetViewController alloc (). The init () self.navigationController().pushViewController_animated(tableViewCtrl, YES) } })Copy the code
I’m sure you all understand that so how do we use it like what I’ve written up here is to find it
YFJSPatchMainViewController in this classCopy the code
- (void) buttonTouch (UIbutton) BTN {} this method starts with nothing written. After executing the js file, this method will execute js and push; The principle is to want oneself to use ability so good wish you happyCopy the code
1. require
Before using an Objective-C class, require(‘className ‘) needs to be called:
require('UIView')
var view = UIView.alloc().init()
Copy the code
You can import multiple classes at once, separated by commas:
require('UIView, UIColor')
var view = UIView.alloc().init()
var red = UIColor.redColor()
Copy the code
Or call require() when it’s in use:
require('UIView').alloc().init()
Copy the code
2. Call the OC method
Calling a class method
var redColor = UIColor.redColor();
Copy the code
Call instance method
var view = UIView.alloc().init();
view.setNeedsLayout();
Copy the code
Parameter passing
Pass parameters as in OC:
var view = UIView.alloc().init();
var superView = UIView.alloc().init()
superView.addSubview(view)
Copy the code
Property
Getting/modifying a Property is equal to calling the getter/setter method of the Property.
view.setBackgroundColor(redColor);
var bgColor = view.backgroundColor();
Copy the code
Method name conversion
Multi-parameter method names are separated by _ :
var indexPath = require('NSIndexPath').indexPathForRow_inSection(0, 1);
Copy the code
Select * from ‘OC’ where ‘OC’ contains’ _ ‘; select * from ‘OC’ where ‘OC’ contains’ _ ‘;
// Obj-C: [JPObject _privateMethod];
JPObject.__privateMethod()
Copy the code
3. defineClass
API
defineClass(classDeclaration, [properties,] instanceMethods, classMethods)
@param classDeclaration: string, class name/parent class name and Protocol @param Properties: New property, string array, can be omitted @param instanceMethods: @param classMethods: classMethods to be added or overridden
Override method
1. Define an existing OC method in defineClass to override it. The method name rule is the same as the call rule, separated by _ :
// OC
@implementation JPTestObject
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
}
@end
Copy the code
// JS defineClass("JPTableViewController", { tableView_didSelectRowAtIndexPath: function(tableView, indexPath) { ... }})Copy the code
2. Use the double underscore __ to represent the underscore _ in the original OC method name:
// OC
@implementation JPTableViewController
- (NSArray *) _dataSource {
}
@end
Copy the code
// JS
defineClass("JPTableViewController", {
__dataSource: function() {
},
})
Copy the code
Select * from OC where ORIG precedes method name;
// OC
@implementation JPTableViewController
- (void)viewDidLoad {
}
@end
Copy the code
// JS defineClass("JPTableViewController", { viewDidLoad: function() { self.ORIGviewDidLoad(); }})Copy the code
Override class method
The third argument of defineClass() is the class method to be added or overridden, and the rules are the same as above for overridden instance methods:
// OC
@implementation JPTestObject
+ (void)shareInstance
{
}
@end
Copy the code
// JS defineClass("JPTableViewController", {// instance method}, {// class method shareInstance: function() {... }})Copy the code
Override Category method
Overriding a Category method is the same as overriding a normal method:
@implementation UIView (custom)
- (void)methodA {
}
+ (void)clsMethodB {
}
@end
Copy the code
defineClass('UIView', {
methodA: function() {
}
}, {
clsMethodB: function() {
}
});
Copy the code
Super
Using the self.super() interface to represent the super keyword, call the super method:
// JS defineClass("JPTableViewController", { viewDidLoad: function() { self.super().viewDidLoad(); }})Copy the code
Property
Obtains/modifies the Property defined by the OC
Get/modify a Property defined in OC by calling a getter/setter:
// OC
@interface JPTableViewController
@property (nonatomic) NSArray *data;
@property (nonatomic) NSString *shareURL;
@property (nonatomic) NSString *shareTitle;
@end
@implementation JPTableViewController
@end
Copy the code
// JS
defineClass("JPTableViewController", {
viewDidLoad: function() {
var data = self.data(); //get property value
self.setData(data.toJS().push("JSPatch")); //set property value
var sel = self;
self.bridge().registerHandler_handler('h5ToNativeShareDialog', block('NSDictionary *',function(data,responseCallback) {
sel.setShareURL(data.objectForKey('url'));
sel.setShareTitle(data.objectForKey('title'));
}));
})
Copy the code
Dynamic New Property
We can add a property to the class in the second argument of defineClass(), in the format of an array of strings, as used in the OC Property interface:
defineClass("JPTableViewController", ['data', 'totalCount'], { init: function() { self = self.super().init() self.setData(["a", "B "]) // Add new Property (id data) self.setTotalCount(2) return self}, viewDidLoad: Function () {var data = self.data(); var totalCount = self.data();Copy the code
Private member variables
Use valueForKey() and setValue_forKey() to get/modify private member variables:
// OC
@implementation JPTableViewController {
NSArray *_data;
}
@end
Copy the code
// JS
defineClass("JPTableViewController", {
viewDidLoad: function() {
var data = self.valueForKey("_data") //get member variables
self.setValue_forKey(["JSPatch"], "_data") //set member variables
},
})
Copy the code
Adding new methods
We can optionally add methods to a class that are undefined by OC, but all arguments are of type ID:
// OC
@implementation JPTableViewController
- (void)viewDidLoad
{
NSString* data = [self dataAtIndex:@(1)];
NSLog(@"%@", data); //output: Patch
}
@end
Copy the code
// JS
var data = ["JS", "Patch"]
defineClass("JPTableViewController", {
dataAtIndex: function(idx) {
return idx < data.length ? data[idx]: ""
}
})
Copy the code
If the new method belongs to the interface of the Protocol, specify the implemented Protocol in the class declaration parameter of defineClass, as described below.
Protocol
It is possible to define a class that implements certain Protocol interfaces, written as OC:
defineClass("JPViewController: UIViewController<UIScrollViewDelegate, UITextViewDelegate>", {
})
Copy the code
When you add a method defined in Protocol and the class does not implement a method, the parameter type is automatically changed to the type defined in Protocol instead of id:
@protocol UIAlertViewDelegate <NSObject> ... - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex; . @endCopy the code
defineClass("JPViewController: UIViewController <UIAlertViewDelegate>", {
viewDidAppear: function(animated) {
var alertView = require('UIAlertView')
.alloc()
.initWithTitle_message_delegate_cancelButtonTitle_otherButtonTitles(
"Alert",
self.dataSource().objectAtIndex(indexPath.row()),
self,
"OK",
null
)
alertView.show()
}
alertView_clickedButtonAtIndex: function(alertView, buttonIndex) {
console.log('clicked index ' + buttonIndex)
}
})
Copy the code
Special types
Struct
JSPatch supports CGRect/CGPoint/CGSize/NSRange struct types, which are represented by JS objects:
// Obj-C UIView *view = [[UIView alloc] initWithFrame:CGRectMake(20, 20, 100, 100)]; [view setCenter: CGPointMake (10, 10)]; [view sizeThatFits:CGSizeMake(100, 100)]; CGFloat x = view.frame.origin.x; NSRange range = NSMakeRange(0, 1);Copy the code
// JS
var view = UIView.alloc().initWithFrame({x:20, y:20, width:100, height:100})
view.setCenter({x: 10, y: 10})
view.sizeThatFits({width: 100, height:100})
var x = view.frame().x
var range = {location: 0, length: 1}
Copy the code
For details about other Struct types, see Adding Struct Type Support
Selector
Using strings to represent selectors in JS:
//Obj-C
[self performSelector:@selector(viewWillAppear:) withObject:@(YES)];
Copy the code
//JS
self.performSelector_withObject("viewWillAppear:", 1)
Copy the code
nil
Null and undefined in JS represent OC nil. If NSNull is null, use NSNull instead. If null is null, use null.
//Obj-C
@implemention JPTestObject
+ (BOOL)testNull(NSNull *null) {
return [null isKindOfClass:[NSNull class]]
}
@end
Copy the code
//JS
require('JPTestObject').testNull(nsnull) //return 1
require('JPTestObject').testNull(null) //return 0
Copy the code
False = null; false = null;
var url = ""; var rawData = NSData.dataWithContentsOfURL(NSURL.URLWithString(url)); if (rawData ! = null) {} The _formatOCToJS method converts undefined,null,isNil to false in the jspatch.js source code.Copy the code
5. NSArray / NSString / NSDictionary
NSArray/NSString/NSDictionary does not automatically convert to the corresponding JS type, use them just like normal NSObject:
//Obj-C
@implementation JPObject
+ (NSArray *)data
{
return @[[NSMutableString stringWithString:@"JS"]]
}
+ (NSMutableDictionary *)dict
{
return [[NSMutableDictionary alloc] init];
}
@end
Copy the code
// JS
require('JPObject')
var ocStr = JPObject.data().objectAtIndex(0)
ocStr.appendString("Patch")
var dict = JPObject.dict()
dict.setObject_forKey(ocStr, 'name')
console.log(dict.objectForKey('name')) //output: JSPatch
Copy the code
To convert NSArray/NSString/NSDictionary to the corresponding JS type, use the.tojs () interface:
// JS
var data = require('JPObject').data().toJS()
//data instanceof Array === true
data.push("Patch")
var dict = JPObject.dict()
dict.setObject_forKey(data.join(''), 'name')
dict = dict.toJS()
console.log(dict['name']) //output: JSPatch
Copy the code
6. Block
Block transfer
Block (paramTypes, function); / / use block(paramTypes, function); / / use block(paramTypes, function);
// Obj-C
@implementation JPObject
+ (void)request:(void(^)(NSString *content, BOOL success))callback
{
callback(@"I'm content", YES);
}
@end
Copy the code
// JS
require('JPObject').request(block("NSString *, BOOL", function(ctn, succ) {
if (succ) log(ctn) //output: I'm content
}))
Copy the code
The types of arguments in the block are strings. Write the types of the arguments in the block, separated by commas. An NSObject like AN NSString star, an NSArray star and so on can be represented by an ID, but a block object should be represented by an NSBlock star.
}}}}}}}}}}}}}}}}}}}}}}}}}}}}}
// Obj-C
@implementation JPObject
typedef void (^JSBlock)(NSDictionary *dict);
+ (JSBlock)genBlock
{
NSString *ctn = @"JSPatch";
JSBlock block = ^(NSDictionary *dict) {
NSLog(@"I'm %@, version: %@", ctn, dict[@"v"])
};
return block;
}
+ (void)execBlock:(JSBlock)blk
{
}
@end
Copy the code
// JS var blk = require('JPObject').genBlock(); Series ({v: "0.0.1}"); // Output: I'm JSPatch, version: 0.0.1Copy the code
To return this block from OC to OC, we need to wrap it with block() again, because BLK is already a normal JS function, just like the JS function we defined above:
// JS var blk = require('JPObject').genBlock(); Series ({v: "0.0.1}"); Output: I'm JSPatch, version: 0.0.1 require('JPObject'). ExecBlock (block("id", BLK)); //output: I'm JSPatch, version: 0.0.1 require('JPObject').Copy the code
If you pass a block object from JS to OC, it will become a JS function. If you pass a block object from JS to OC, you need to wrap it with the block() interface.
Use the self variable in the block
You can’t use the self variable inside a block. You need to use a temporary variable to hold it before entering the block:
defineClass("JPViewController", { viewDidLoad: function() { var slf = self; require("JPTestObject").callBlock(block(function(){ //`self` is not available here, use `slf` instead. slf.doSomething(); }); }}Copy the code
limit
When passing a block from JS to OC, there are two restrictions:
A. A maximum of six block parameters are supported. B. The block parameter type cannot be double/NSBlock/struct.
There is also no support for JS wrapped blocks being passed to OC and then passed back to JS to be called (see issue #155 for this reason) :
- (void)callBlock:(void(^)(NSString *str))block {
}
Copy the code
defineClass('JPTestObject', { run: function() { self.callBlock(block('NSString*', function(str) { console.log(str); })); }, callBlock: function(BLK) {// this block is passed from JS to OC and cannot be called. blk("test block"); }});Copy the code
7. __weak / __strong
You can declare a weak variable in JS with __weak(), mainly to avoid circular references.
For example, to avoid circular references caused by blocks in OC, we often write:
- (void)test {
__weak id weakSelf = self;
[self setCompleteBlock:^(){
[weakSelf blabla];
}]
}
Copy the code
In JS we can write:
var weakSelf = __weak(self)
self.setCompleteBlock(block(function(){
weakSelf.blabla();
}))
Copy the code
To change weakSelf into a strong variable when using it, use the __strong() interface:
var weakSelf = __weak(self)
self.setCompleteBlock(block(function(){
var strongSelf = __strong(weakSelf)
strongSelf.blabla();
}))
Copy the code
8. GCD
Dispatch_async_main () dispatch_sync_main() dispatch_async_global_queue()
// Obj -c dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), ^{ // do something }); dispatch_async(dispatch_get_main_queue(), ^{ // do something });Copy the code
/ / JS dispatch_after (1.0, function(){ // do something }) dispatch_async_main(function(){ // do something }) dispatch_sync_main(function(){ // do something }) dispatch_async_global_queue(function(){ // do something })Copy the code
9. Pass the id* parameter
If you need to pass an ID * argument like NSError ** in this interface in NSURLConnection:
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse **)response error:(NSError **)error;
Copy the code
In this case, we pass a pointer to an NSObject. We can change the object to which the pointer points in the method, and we can call the object to which the new object points. For this argument, we first need to introduce the JPMemory extension, and then pass and retrieve it as follows:
-
Use malloc(sizeof(id)) to create a pointer
-
Pass the pointer as an argument to the method
-
Method call, using pval() to get the pointer to the new object
-
Call releaseTmpObj() to release the object
-
Use free() to free the pointer
Here’s an example:
//OC
- (void)testPointer:(NSError **)error {
NSError *err = [[NSError alloc]initWithDomain:@"com.jspatch" code:42 userInfo:nil];
*error = err;
}
Copy the code
//JS //malloc() pval() free() is provided by JPMemory extension require('JPEngine').addExtensions(['JPMemory']) var pError = malloc(sizeof("id")) self.testPointer(pError) var error = pval(pError) if (! error) { console.log("success") } else { console.log(error) } releaseTmpObj(pError) free(pError)Copy the code
If, on the other hand, you wanted to replace the -testpointer: method above in JS and build an NSError object to assign to the pointer passed in, you could write:
defineClass('JPClassName', {
testPointer: function(error){
var tmp = require('NSError').errorWithDomain_code_userInfo("test", 1, null);
var newErrorPointer = getPointer(tmp)
memcpy(error, newErrorPointer, sizeof('id'))
}
);
Copy the code
10. Constants, enumerations, macros, global variables
Constants/enumerations
Constants/enumerations in Objective-C cannot be used directly in JS, but can be used directly in JS with concrete values:
//OC
[btn addTarget:self action:@selector(handleBtn) forControlEvents:UIControlEventTouchUpInside];
Copy the code
/ / UIControlEventTouchUpInside values is 1 < < 6 BTN. AddTarget_action_forControlEvents (self, "handleBtn", 1 < < 6);Copy the code
Or redefine a global variable with the same name in JS:
//js
var UIControlEventTouchUpInside = 1 << 6;
btn.addTarget_action_forControlEvents(self, "handleBtn", UIControlEventTouchUpInside);
Copy the code
There are some constant strings that need to be typed in OC using NSLog to see what its value is:
//OC
[[NSAttributedString alloc].initWithString:@"str" attributes:@{NSForegroundColorAttributeName: [UIColor redColor]];
Copy the code
NSForegroundColorAttributeName in the code above is a static string constants, don’t see the value in the source code, can use first NSLog hit its value to write directly on the JS:
//OC
NSLog(@"%@", NSForegroundColorAttributeName) //output 'NSColor'
Copy the code
Nsattributedstring.alloc ().initWithstring_attributes (" invalid ", {'NSColor': uicolor.redcolor ()});Copy the code
The macro
For macro value
Macros in Objective-C also cannot be used directly in JS. If the macro is a value, you can define the same global variable in JS instead. If the macro is a program, you can expand the macro in JS instead:
#define TABBAR_HEIGHT 40
#define SCREEN_WIDTH [[UIScreen mainScreen] bounds].size.height
[view setWidth:SCREEN_WIDTH height:TABBAR_HEIGHT];
Copy the code
//JS
view.setWidth_height(UIScreen.mainScreen().bounds().height, 40);
Copy the code
If the value of a macro is some underlying value, such as CGFLOAT_MIN, it can be supported by returning it in a class or instance method or by adding an extension:
@implementation JPMacroSupport
+ (void)main:(JSContext *)context
{
context[@"CGFLOAT_MIN"] = ^CGFloat() {
return CGFLOAT_MIN;
}
}
@end
Copy the code
require('JPEngine').addExtensions(['JPMacroSupport'])
var floatMin = CGFLOAT_MIN();
Copy the code
Modify the macro value
JSPatch does not support changing the value of a macro. To do so, you need to replace all methods that use the macro. Such as:
#define VIEW_HEIGHT 40 @implementation JPMethodDemo + (void)func { UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, VIEW_HEIGHT)]; . } @endCopy the code
//JS var VIEW_HEIGHT_NEW = 20; defineClass('JPMethodDemo', { func: function() { var view = UIView.alloc().initWithFrame({x:0, y:0, width:100, height:VIEW_HEIGHT_NEW}); . }});Copy the code
The global variable
A static global variable defined in a class cannot be retrieved from JS. To retrieve this variable in JS, you need to have a class method or instance method return it in OC:
static NSString *name;
@implementation JPTestObject
+ (NSString *)name {
return name;
}
@end
Copy the code
Var name = jpTestObject.name (Copy the code
11. Swift
When you overwrite the Swift class with defineClass(), the class name should be the project name. If you want to override the ViewController method in JS, you need to write:
defineClass('demo.ViewController', {})
Copy the code
The same is true for invoking classes already defined in SWIFT:
require('demo.ViewController')
Copy the code
Some points to note:
-
Only calls to Swift classes that inherit from NSObject are supported
-
The Swift class inherits from NSObject, and its methods and properties that inherit from its parent class can be called in JS. Other custom methods and properties also need the dynamic keyword.
-
If the parameter/attribute type of the method is Swift specific (such as Character/Tuple), the method and attribute cannot be called through JS.
-
The Swift project has added a new class to JSPatch, just like OC, which can be used normally.
12. Load the dynamic library
For the built-in dynamic library of iOS, if the original APP has not been loaded, you can use the following methods to load the dynamic library. Take SafariServices. Framework as an example:
var bundle = NSBundle.bundleWithPath("/System/Library/Frameworks/SafariServices.framework");
bundle.load();
Copy the code
Once loaded, you can use the SafariServices. Framework.
13. Debugging
You can use console.log() to print an object, which acts like NSLog() and will be typed directly into the XCode console.
Console.log () supports any arguments, but does not support concatenation of NSLog(@”num:%f”, 1.0) like NSLog:
var view = UIView.alloc().init(); var str = "test"; var num = 1; console.log(view, str, num) console.log(str + num); // Concatenate the string directly in JSCopy the code
You can also debug JS breakpoints using Safari’s debugging tools.
Recommend a 👇 :
If you want to advance together and keep up with the latest interview news, you can add networking group 642363427