JS in iOS
Categories: Study
Recently I mainly studied the content of JS in iOS. I intend to elaborate from 4 parts: Why cannot purely do front-end, JSCore principle and communication mechanism, OC underlying Runtime principle, How to arbitrarily modify iOS running results via JS. Aiming to let front-end and iOS development students understand cross-end development principles better, and at the same time understand what unexpected things they can do when combined.
Why cannot purely do front-end?
Since graduation and work, experiencing mobile terminal H5 to RN development, to last year’s Weex development and recent iOS development, I increasingly found:
Only relying on front-end technology is difficult to meet mobile terminal user needs or experience requirements
Why is the effect bad if purely doing front-end?
Maybe H5 students feel deeply. Sometimes need to do a terminal photo upload function. Implementing via JS often greatly discounts the effect, and it is also difficult to achieve the smooth experience required by the business side. If at this time Native student says I wrote a Bridge, just call Bridge.uploadImg() method in the client to directly use Native upload function. Hearing this sentence you will definitely breathe a sigh of relief and write code easily.
Also replacing terminal Webview from UIWebView to WKWebView, the effect achieved will also be faster and better than H5 smooth scrolling optimized by yourself for a long time.
So how do trendy front-end students generally handle mobile terminal requirements?
They generally leverage Native terminal capabilities, for example using Weex or RN to develop pages, letting them also have Native effects. If there are pages in WeChat or Alipay, can also upgrade to Mini Programs becoming like built-in programs. In the client, can also leverage terminal container optimization, letting it offline our H5 pages, and provide many Native functions via bridge to expand capabilities.
Here the bridge leveraged is actually Bridge, letting both no longer be an isolated island, but mutually helping. I understand it can do these things:

Next, give the above solution through JSCore principle and communication mechanism section.
JSCore Principle and Communication Mechanism
What is JSCore?
Everyone knows that browser kernel modules are mainly composed of rendering engine and JS engine. JSCore is a kind of JS engine.
Apple encapsulated WebKit’s JS engine with OC, providing a set of JS running environment and bridge for conversion between Native and JS data types, commonly used for mutual calling between OC and JS code. This also means it can execute JS independently from rendering.
JSCore mainly includes the following classes, protocols, class structures:

How does JSCore run?
Can understand how each module runs through the following JSCore framework structure diagram and the above description.

From the above figure we can see such a process:
In Native applications, we can open multiple threads to asynchronously execute our different requirements, which means we can create multiple JSVirtualMachine virtual machines (runtime resource providers), independent of each other without affecting, so we can execute different JS tasks in parallel.
In a JSVirtualMachine, multiple JSContexts (JS execution environment context) can also be associated, and data transfer communication with Native is carried out through JSValue (value object). At the same time, through JSExport (protocol), methods and properties of classes in Native that comply with this parsing can be converted to JS interfaces for calling.
JS and OC Data Type Interchange
From the previous section, we know JSValue can be used for barrier-free data conversion between JS and OC. Main principle is JSValue provides the following methods to facilitate conversion of various types for both sides.

Execute JS code in iOS
We can execute a piece of JS script in JSCore through evaluateScript. Utilizing this feature we can do some multi-end logic unification things.
// Execute a piece of JavaScript script
-(JSValue *)evaluateScript:(NSString*)script;For example, in business, 3 ends (iOS, Android, H5) have a fairly complex but same principle price calculation logic. General practice is 3 ends write a set with their own languages. Doing so is not only troublesome, low efficiency but also logic is not necessarily unified. Meanwhile using OC to implement complex calculation logic is not as flexible and efficient as JS.
Here we can use Execute JS code feature, extract this logic into a JS method, just pass in specific input parameters, directly return price. In this way, 3 ends can use this logic at the same time, and can also put it to remote end for dynamic update and maintenance.
Roughly implemented like this:
// Execute JS in iOS
JSContext *jsContext = [[JSContext alloc] init];
[jsContext evaluateScript:@"var num = 500"];
[jsContext evaluateScript:@"var computePrice = function(value){ return value * 2 }"];
JSValue*value = [jsContext evaluateScript:@"computePrice(num)"];
int intVal = [value toInt32];
NSLog(@"Calculation result is %d", intVal);Operation result is:
2018-03-16 20:20:28.006282+0800 JSCoreDemo[4858:196086] ========Execute JS code in iOS========
2018-03-16 20:20:28.006517+0800 JSCoreDemo[4858:196086] Calculation result is 1000I think it can also be used in regular validation, animation functions, 3D rendering modeling and other data calculation aspects.
Call JS method in iOS
After talking about executing JS code in iOS, next introduce how to call JS methods in H5 in iOS.
For example, we have a global method called nativeCallJS in H5. We can get this method through execution environment context jsContext[@"nativeCallJS"] and call it, similar to this:
// Html has a JS global method
<script type="text/javascript">
var nativeCallJS = function (parameter) {
alert(parameter);
};
</script> // Run JS method in iOS
JSContext *jsContext = [webView valueForKeyPath:@“documentView.webView.mainFrame.javaScriptContext”];
JSValue*jsMethod = jsContext[@"nativeCallJS"];
jsMethod callWithArguments:@[ @"Hello JS, I am iOS" ]];Finally our running result can see Native executed H5 Alter layer:

Utilizing this feature we can let iOS get some H5 information to process something he wants to process, for example, first expose information in global, obtain version number used, running environment information, terminal active processing logic (clear cache, control running) and other things by calling method.
Call iOS method in JS
Actually for front-end students, the most used should be this, calling some terminal capabilities through JS to make up for deficiencies on H5.
Here need to combine with webview related feature @"documentView.webView.mainFrame.javaScriptContext", pass the method called by H5 to JSCore context with Block named jsCallNative(method name).
For example, a button click callback in our H5 is to call a client method, and output input parameters in the method, roughly implemented like this:
// Html button click calls an OC method
<button type="button"
onclick="jsCallNative('Hello iOS', 'I am JS');">Call OC Code</button> //Block passed to JavaScript context named "jsCallNative"
JSContext *jsContext = [webView valueForKeyPath:
@"documentView.webView.mainFrame.javaScriptContext"];
jsContext[@"jsCallNative"] = ^() {
NSArray*args = [JSContext currentArguments];
for (JSValue *obj in args) {
NSLog(@"%@", obj);
}
};Final output is like this:
2018-03-16 20:51:25.590749+0800 JSCoreDemo[4970:219245] ========Call iOS method in JS========
2018-03-16 20:51:25.591155+0800 JSCoreDemo[4970:219245] Hello iOS
2018-03-16 20:51:25.591370+0800 JSCoreDemo[4970:219245] I am JSThis feature truly allows H5 to enjoy many terminal features, such as Native way jump, Native underlying capabilities (vibration, recording, photographing), scanning code, getting device information, sharing, setting navigation bar, calling Native encapsulated components etc. Here you can relate to Hybrid development mode.
Expose iOS method properties to JS via JSExport
This feature may not be well known to H5 students, but for Native students, I think it is very useful.
Via JSExport can conveniently expose iOS object properties methods to JS environment, making it as convenient as JS object to use.
For example, we have a Person class in OC, containing two properties and a method. Here expose fullName method using JSExport protocol, so it can be directly called in JS.
@protocol PersonProtocol <JSExport>
-(NSString *)fullName;
@end
@interface Person : NSObject <PersonProtocol>
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString*lastName;
@end
@implementation Person
@synthesize firstName, lastName;
(NSString *)fullName {
return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}
@end // Expose iOS method properties to JS via JSExport
Person *person = [[Person alloc] init];
jsContext[@"p"] = person;
person.firstName = @"Fei";
person.lastName = @"Zhu";
NSLog(@"========Expose iOS method properties to JS via JSExport========");
[jsContext evaluateScript:@"log(p.fullName());"];
[jsContext evaluateScript:@"log(p.firstName);"];Final running result is:
2018-03-16 20:51:17.437688+0800 JSCoreDemo[4970:219193] ========Expose iOS method properties to JS via JSExport========
2018-03-16 20:51:17.438100+0800 JSCoreDemo[4970:219193] Fei Zhu
2018-03-16 20:51:17.438388+0800 JSCoreDemo[4970:219193] undefinedWhy is p.firstName undefined after running? Because it is not exposed to Native environment here, so it cannot be obtained.
Here we can utilize more on programming convenience, allowing OC and JS to call each other directly.
Handle JS exception in iOS
Slightly mature companies will have exception monitoring systems for front-end pages. Finding JS execution exceptions can directly notify development to prevent online accidents.
Through exceptionHandler in JSCore can solve this problem very well. When JS runs abnormally, it will callback Block set in exceptionHandler of JSContext. In this way, we can upload our errors to monitoring platform in Block callback.
For example this example, I run a function returning a+1. Usually we can see error Can't find variable: a in Chrome console. Running here will be same:
// When JavaScript runtime exception occurs
// Will callback Block set in exceptionHandler of JSContext
JSContext *jsContext = [[JSContext alloc] init];
jsContext.exceptionHandler = ^(JSContext *context, JSValue*exception) {
NSLog(@"JS Error: %@", exception);
};
[jsContext evaluateScript:@"(function errTest(){ return a+1; })();"];Finally output error is:
2018-03-17 11:28:07.248778+0800 JSCoreDemo[15007:632219] ========Handle JS exception in iOS========
2018-03-17 11:28:07.252255+0800 JSCoreDemo[15007:632219] JS Error: ReferenceError: Can't find variable: aJS and Terminal Mutual Communication
Recently submitted a [《More enhanced about
Specific implementation and effect are:
![]() | ![]() |
Specific ideas visible in [WEEX-233][iOS].
JSPatch
Let us think deeper about whether the above idea can be extended again. Can we directly interfere with iOS code running via JS? The answer is yes. Below I want to organize my understanding of JSPatch.
Suppose iOS wants to fix bug without releasing version
Suppose online APP has a piece of code with bug causing crash. Maybe Native crash will be much more serious than H5 problem. The former can release immediately, the latter may need to be modified and submitted to Apple Store for several days to go online, and then update rate may not go up, very troublesome.
Here can use JSPatch similar solution, issue a piece of code to overwrite original problematic method, so this bug can be fixed quickly.
Can see the above process through a simple example.
Wrote a blue Hello World with OC. We can issue a piece of JS code to change original blue font to red and modify text. After deleting JS code issuing code, can restore original blue Hello World.
| Before JS Issue | After JS Issue | After JS Deletion |
![]() | ![]() | ![]() |
Main code roughly as follows:
// A code showing blue Hello World
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self simpleTest];
}
- (void)simpleTest {
self.label.text = @"Hello World";
self.label.textColor = [UIColor blueColor];
}
@end // A piece of JS overwrite code complying with JSPatch rules
require('UIColor');
defineClass('ViewController', { simpleTest : function() {
self.label().setText("Your blue Hello World was changed to red by me");
var red = UIColor.redColor();
self.label().setTextColor(red);
},
})How is this done here? First need to introduce JSPatch:
JSPatch is an iOS dynamic update framework. By introducing JSCore, can use JS to call any native interface, can dynamically update modules for projects, replace native code to dynamically fix Bug.
That is JS passes string to OC, OC calls and replaces OC method via Runtime interface.
Why can call any native interface via JS? First can understand OC underlying Runtime principle.
Runtime
About 95% of OC language is C related writing method. Why didn’t Apple directly use C to write iOS at that time? One big reason is OC dynamic nature. Have a very powerful Runtime (A set of C language API, bottom layer implemented based on it). Core is Message Dispatch. Runtime will make different reactions according to whether message receiver can respond to the message.
Maybe above is obscure. Simply put OC method implementation and calling pointer relationship are decided at runtime, not compile time. In this way, we can do something at runtime to change original implementation, achieving purpose of hot fix.
OC method calling is not like JS language, directly array.push(foo) function call is ok. It is called through message mechanism. For example following insert foo into 5th place in array:
[array insertObject:foo atIndex:5];At bottom layer it is more obscure than this implementation. It sends message with selector through objc_msgSend method:
objc_msgSend(array, @selector(insertObject:atIndex:), foo, 5);Send message to object at runtime, how is message mapped to method?
Simply put, an object’s class saves method list, is a dictionary, key is selectors, IMPs are value. An IMP points to implementation of method in memory. Relationship between selector and IMP is decided at runtime, not compile time.
- (id)doSomethingWithInt:(int)aInt{}
id doSomethingWithInt(id self, SEL _cmd, int aInt){}By looking at Runtime Source Code, found following commonly used methods

Through above methods can do many unexpected things, such as dynamic variable control, dynamically adding methods to an object, can forward message to desired object, even can dynamically exchange implementation of two methods.
JSPatch && Runtime
Precisely because of OC language dynamic nature, all method calls/class generation above are carried out at runtime via OC Runtime. Can get class and method via string of class name and method name, and instantiate and call:
Class class = NSClassFromString("UIViewController");
id viewController = [[class alloc] init];
SEL selector = NSSelectorFromString("viewDidLoad");
[viewController performSelector:selector];Can also replace a class method with new implementation:
static void newViewDidLoad(id slf, SEL sel) {}
class_replaceMethod(class, selector, newViewDidLoad, @"");Can also register a new class, add method for class:
Class cls = objc_allocateClassPair(superCls, "JPObject", 0);
objc_registerClassPair(cls);
class_addMethod(cls, selector, implement, typedesc);JSPatch utilizes exactly these good features to implement its hot fix function.
How JS calls OC in JSPatch
Here how JS in JSPatch is connected with arbitrary modification of OC code? Principle roughly as follows:
- JSPatch calls through Require in implementation. Create a variable with same name on JS global scope. Variable points to an object. Object property __clsName saves class name. Meanwhile indicates this object is an OC Class. By calling
require(“UIView"), we can useUIViewto call corresponding methods on it.
UIView = require(“UIView");
var _require = function(clsName) {
if (!global[clsName]) {
global[clsName] = {__clsName: clsName}
}
return global[clsName]
}- Before JSCore execution, OC method calls are all completed through adding Object(JS) prototype method __c(methodName). If called directly, need JS to traverse all methods of current class, also loop to find parent class methods until top layer, undoubtedly very time consuming. Through uniqueness of
__c()meta function, can forward to a specified function to execute every time it is called, very elegant.

Object.prototype.__c = function(methodName) {
return function(){
var args = Array.prototype.slice.call(arguments)
return _methodFunc(self.__obj, self.__clsName, methodName,
args, self.__isSuper)
}
}- After handling JS interface problem, next only need to leverage previous JSCore knowledge to achieve message mutual transmission between JS and OC. That is bridge function established via JSContext in _c function, call corresponding method using Runtime interface to complete call:
- Forwarding in JS
var _methodFunc = function(instance, clsName, methodName, args, isSuper) {
....
var ret =_OC_callC(clsName, selectorName, args)
return_formatOCToJS(ret)
}- Processing in OC
context[@"_OC_callC"] = ^id(NSString *className, NSString*selectorName, JSValue *arguments) {
return callSelector(className, selectorName, arguments, nil, NO);
};Feelings
Above is roughly a principle of how to arbitrarily modify OC running results via JS. Although most functions of JSPatch are disabled, the idea of JS operating OC inside is really great.
《JS in iOS》 is actually a sharing at Xiangshenghui last week. Organize it into an article, share with students interested in cross-end. Meanwhile try to use down-to-earth way, hope students with only client base or front-end base can also understand. At the same time can also Clone Demo Source Code in Github to test.
Due to still in iOS foundation supplementing stage, some places may not be understood in place, welcome to discuss together.



