-
Notifications
You must be signed in to change notification settings - Fork 2.2k
performSelectorInOC 使用文档
JavaScript 语言是单线程的,在 OC 使用 JavaScriptCore 引擎执行 JS 代码时,会对 JS 代码块加锁,保证同个 JSContext 下的 JS 代码都是顺序执行。所以使用 JSPatch 替换的方法都会在这个锁里执行,无法并行执行,这导致如果主线程和子线程同时运行了 JSPatch 替换的方法,子线程就会卡住主线程。
对此可以使用 .performSelectorInOC(selector, arguments, callback)
方法,在 JS 使用这个接口调用 OC 方法时,可以脱离 JavaScriptCore 的锁,在释放了 JavaScriptCore 锁后才在 OC 执行这个方法,这样这个方法就可以与其他线程的 JS 方法并行执行。
obj.performSelectorInOC(selector, arguments, callback)
@param selector
: 调用的方法 selector 字符串
@param arguments
: 方法参数,数组
@param callback
: OC方法执行后的回调,第一个参数值为OC方法返回值
obj
是执行主体,NSObject 对象。
defineClass('JPClassA', {
methodA: function() {
//run in mainThread
},
methodB: function() {
//run in childThread
var limit = 20;
var data = self.readData(limit);
var count = data.count();
return {data: data, count: count};
}
})
上述例子中若在主线程和子线程同时调用 -methodA
和 -methodB
,而 -methodB
里 self.readData(limit)
这句调用耗时较长,就会卡住主线程方法 -methodA
的执行,对此可以让这个调用改用 .performSelectorInOC()
接口,让它在 JavaScriptCore 锁释放后再执行,不卡住其他线程的 JS 方法执行:
defineClass('JPClassA', {
methodA: function() {
//run in mainThread
},
methodB: function() {
//run in childThread
var limit = 20;
return self.performSelectorInOC('readData', [limit], function(ret) {
var count = ret.count();
return {data: ret, count: count};
});
}
})
这时在 OC 执行 -methodB 的过程如下,加锁表示 JavaScriptCore 执行 JS 代码时加的锁:
加锁 -> 执行JS代码 (var limit = 20) -> 去锁 -> 调用 readData 方法 -> 加锁 -> 调用 callback -> 去锁
上述调用是同步的,上面两种写法的执行顺序和结果完全一样。
若需要多次使用 .performSelectorInOC()
接口,可以无限嵌套:
methodB: function() {
var slf = self;
return self.performSelectorInOC('readData', [limit], function(ret) {
return slf.performSelectorInOC('methodC', [ret], function(methodCRet){
return slf.performSelectorInOC('methodD', [], function(methodDRet){
return methodDRet;
})
})
});
}
此接口除了用于解决主线程卡顿问题,还可以用于解决因 JavaScriptCore 锁导致的死锁问题。下面举个死锁的例子:
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
ClassA* obj = [[ClassA alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//A线程
@synchronized(obj) { //X锁
sleep(3);
[obj methodA]; //methodA被JS替换,调用会进JS,请求JSCore的锁
}
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//B线程
[obj methodA]; //methodA被JS替换,调用会进JS,请求JSCore的锁
});
}
@end
@implementation ClassA
- (void)methodA
{
}
- (void)methodB
{
@synchronized(self) { //X锁
int a = 0;
}
}
@end
defineClass('ClassA', {
methodA: function() {
self.methodB() //调用到OC,
},
})
上述调用顺序:
A线程和B线程都在等待对方的锁释放,导致死锁。对此可以改成 performSelectorInOC 的方式调用解决死锁问题:
defineClass('ClassA', {
methodA: function() {
return self.performSelectorInOC('methodB', [], function(ret){});
},
})
调用顺序和锁的关系就变成:
JSPatch 和 UIWebView 都使用了 JavaScriptCore,在 JSPatch 里第一次初始化 UIWebView (在原生 OC 代码没有初始化过 UIWebView),就会出现 JavaScriptCore 冲突的问题,导致意想不到的结果。遇到这个问题同样可以用 .performSelectorInOC
接口解决:
defineClass("JPViewController", {
viewDidLoad: function() {
var slf = self;
return UIWebView.alloc().performSelectorInOC('initWithFrame:', [self.view().bounds()],function(webView){
var url = NSURL.alloc().initWithString("http://www.apple.com");
webView.loadRequest(NSURLRequest.requestWithURL(url));
slf.view().addSubview(webView);
});
}
}
- 只能在 defineClass() 的方法里使用。
- 调用时必须 return,否则无法调用到。