JavaScriptCore与JS交互笔记

JavaScriptCore 可以允许开发者在App内执行js脚本,
框架提供在Object-c,swift中执行javascript的能力,同时,允许javascript环境中插入自定义对象

// Classes 主要类
JSContext
JSManagedValue
JSValue
JSVirtualMachine
// 协议
JSExport
javascriptcore.png

上图为类之间的关系架构

  • JSValue
    JSValue 包括一系列方法用于访问其可能的值以保证有正确的 Foundation 类型,OC和JS对象之间的转换也是通过它
    类型转换表:
Object-C Swift Javascript
nil nil undefined
NSNull NSNull null
NSString String String
NSNumber NSNumber Number
BOOL Bool Boolean
NSDictionary Dictionary Object
NSArray Array Array
NSData NSData Date
objc_object AnyObject Object
Class AnyClass Object
NSRange,CGRect,CGPoint,CGSize 同左 Object
block closure Object
  • JSManagedValue
    主要用来保存JSValue对象,解决OC对象中存储js的值,导致的循环引用问题。将JSValue 转为JSManagedValue 类型后,可以通过addManagedReference方法添加到JSVirtualMachine 对象中,这样能够保证你在使用过程中JSValue 对象不会被释放掉,当不再需要使用该JSValue 对象后,通过removeManagedReference移除JSManagedValue 对象的引用,JSValue对象就会随之被释放
  • JSContext
    JSContext代表着一个js的执行环境,可以在此环境中执行js代码,也可以把native 对象,方法,函数暴露给JavaScript,JSContext之间可以互相传递参数
let context = JSContext.init()//初始化,默认生成JSVirtualMachine
let vtMachine = JSVirtualMachine.init()
let context1 = JSContext.init(virtualMachine: vtMachine)//使用JSVirtualMachine初始化
  • JSVirtualMachine
    JSVirtualMachine的实例代表着一个独立的js执行环境,一般情况下不会接触到它。主要在如下两个场景中使用它:
    1、 需要支持js脚本的并发
    每个JSContext都从属于一个JSVirtualMachine,一个JSVirtualMachine可能包含多个JSContext(如图:javascriptcore.png),但是JSVirtualMachine是独立的,无法在JSVirtualMachine之间传递对象。
    JavaScriptCore的API 是线程安全的,你可以在线程中创建执行脚本的JSValue,但是多个线程使用同一个JSVirtualMachine会进入等待状态,所以需要并发执行js,需要为每个线程创建一个独立的JSVirtualMachine
    2、管理js和oc,swift桥接对象的内存
    当你暴露Objective-C 或者 Swift对象给JavaScript时,不应该在对象中持有JavaScript 的值,这会造成循环引用。我们应该使用JSManagedValue

内存管理:
不要在block中直接使用context,或者使用外部JSValue

let context:JSContext = JSContext.init()
context.evaluateScript("var str = '안녕하새요!'")
let simplifyString: @convention(block) () -> String? = {

    // 不产生循环引用
    guard let input = JSContext.current().objectForKeyedSubscript("str")?.toString() else{
        return nil
    }
    
    //循环引用
//    guard let input = context.objectForKeyedSubscript("str")?.toString() else{
//        return nil
//    }

    var mutableString = NSMutableString(string: input) as CFMutableString
    CFStringTransform(mutableString, nil, kCFStringTransformToLatin, false)
    CFStringTransform(mutableString, nil, kCFStringTransformStripCombiningMarks, false)
    return mutableString as String
}
context.setObject(unsafeBitCast(simplifyString, to: AnyObject.self), forKeyedSubscript: "simplifyString" as NSCopying & NSObjectProtocol)
print(context.evaluateScript("simplifyString()"))

或者直接将参数传递给函数

let simplifyString: @convention(block) (String) -> String? = {
input in
....略
}
print(context.evaluateScript("simplifyString('传入参数')"))

使用内存管理辅助对象JSManagedValue,详情看下面 JSExport 协议中代码

  • JSExport 协议
    另一种将OC和Swift类暴露给JS的方法是定义并实现JSExport协议,对于协议中定义的类型,有如下转换关系:
Object-C / Swift JS
属性 JS中的Getter 和 Setter
实例方法 JS中的function
类方法 JS中的global object中的function

映射关系示例:

// swift对象
protocol MyPointExports : JSExport {
    
    var x : Double? {get set}  // 存储属性
    var y : Double? {get set}  // 存储属性
    
    func dzscription() // 实例方法
    init(x : Double, y : Double)  // 初始化方法
    static func makePoint(x : Double, y : Double) -> MyPoint // 类方法
}

class MyPoint : MyPointExports{

    var x : Double? 
    var y : Double?
    
    func dzscription(){print("MyPoint")}
    
    required init(x : Double, y : Double){
        self.x = x
        self.y = y
    }
    
    static func makePoint(x : Double, y : Double) -> MyPoint{
        return MyPoint.init(x: x, y: y)
    }
} 
// 对应的js对象调用方式
point.x;
point.x = 10;
// 实例方法 -> 函数
point.description();
// 初始化方法 -> constructor语法
var p = MyPoint(1, 2);
// 类方法 -> constructor对象上的方法
var q = MyPoint.makePointWithXY(0, 0);

JSExport协议使用示例:

// 声明一个协议
protocol JSExportProtocol : JSExport {
  
    var jsValue : JSValue {get set}
}
// 具体类遵守协议
class JSExportObject: NSObject, JSExportProtocol {
    
    var manageValue : JSManagedValue?
    // 只暴露jsValue, manageValue不暴露给js,因为没有在协议中声明
    var jsValue: JSValue{
        set{
            self.jsValue = newValue
// JSManagedValue引用,防止循环引用
            manageValue = JSManagedValue.init(value: self.jsValue)
            JSContext.current().virtualMachine.addManagedReference(manageValue, withOwner: self)
        }
        get{
            return self.jsValue
        }
    }
    
    override init() {
        super.init()
    }
    
}

class MDJSBase: NSObject {

    let context:JSContext = JSContext.init()
    var managedValue : JSManagedValue?
    var exportObject : JSExportObject = JSExportObject.init()
    
    func md_executeJS() {
        
//        let jsValue = JSValue.init(object: ["key":"value"], in: context)
//        exportObject.jsValue = jsValue
        context.evaluateScript(
                "var str = 'string';" +
                "function printDict(obj){ " +
                "this.obj = obj;" +  //js引用OC对象
                "obj.jsValue = str;" +//OC引用js对象
                "return obj.jsValue;}"
                )
        guard let funcValue = context.objectForKeyedSubscript("printDict") else{
            fatalError()
        }
        print(funcValue.call(withArguments: [exportObject]))
    }
}

以上主要内容来自

概念讲完了,接下来做些实践,实践参考JavaScriptCore Tutorial for iOS: Getting Started

Simulator Screen.png

实现一个查询IP地址归属地的功能,Native发起请求,交给JS解析,返回结果,并更新UI,代码有打注释,这里就不赘述了

示例解析代码:

// 一 JSExort协议 已经将IPInfo对象暴露给js,直接在js中对象解析
let mapFunction = context.objectForKeyedSubscript("mapToNative")
guard let ipInfo = mapFunction?.call(withArguments: [parsedJson]).toObject() as? IPInfo else {
      print("Unable to parse JSON to Native Object")
       return nil
      }
// 1  block 类型转换成泛型
let factoryBlock = unsafeBitCast(IPInfo.ipInfoFactory, to: AnyObject.self)
// 2
context.setObject(factoryBlock, forKeyedSubscript: "ipInfoFactory" as (NSCopying & NSObjectProtocol)!)
let factory = context.evaluateScript("ipInfoFactory")
// 3 调用native Block
 guard
     let ipInfo = factory?.call(withArguments: [parsedJson]).toObject() as? IPInfo else {
     print("Error while processing ipInfo.")
     return nil    
     }

源码Github地址 点击这里 有缘人可以个赏个star

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容