Today, with the rapid development of front-end, JavaScript is more and more widely used in mobile terminals. As iOS developers, interacting with JavaScript is also a small aspect, especially some web embedded pages. UIWebView and WKWebView are like a black box to the JavaScript language, and when things go wrong it’s hard to debug using Objective-C and Swift.

Recently, I encountered a problem in this aspect, which is no problem in all kinds of browsers and Android, but there was a Vue component data binding exception in UIWebView. I spent a whole morning with my front-end colleagues, and the investigation process will not be discussed, the processing result is to change a Vue data binding writing method. The cause of this Bug and the final solution are just like metaphysics to me, so this article is not to discuss this, of course, some friends who know about it hope to leave a message to explain it to me.

The Bug is that a top Tabs, say five, opens the first page by default, and clicks Tabs to refresh the status.

The first page has a Vue component called title, the correct value is 138XXX, the initial page is normal, after switching the page 2-5 title display is 138XXX+ the correct value.

Then switch back to 1, the original page, and the value is 138XXX138XXX.

The difference is one-way binding and bidirectional binding, but the title value has not been changed.

In the process of investigation, my front-end colleague could not debug through tools like Chrome, and I could not get JavaScript methods and variables at will to help him debug, so I could only guess the reason and change the embedded code bit by bit, which was very troublesome. So I sort out how to use JavaScriptCore to clean up this article.

Safari > develop > XXX iPhone > inspect allows you to debug WebView easily, and is basically similar to Chrome.

Of course, as an aspiring iOSer, learning to interact with JavaScript isn’t just about being a front-end console, so the following is useful.

This article involves the explanation of the place is not much, notes quite detailed, directly on the code, suitable for use as a manual.

Interaction of properties

// Create the js runtime environment
let context = JSContext()!

// Catch a running exception
context.exceptionHandler = { (js, exception) in
    print(exception! .toObject()) }// Execute the js code
let value = context.evaluateScript("2 + 3")!
print(value.toObject()) / / 5

// Define js variables
context.evaluateScript("var array = [1, 2 ,3]")

// Get the js variable
let array = context.objectForKeyedSubscript("array")!
print(array) / / 1, 2, 3

// Determine the jsValue type
if array.isArray {
    print("Array is an array") // array is an array
}
else if array.isObject {
    print("Array is an object")}else if array.isString {
    print("Array is a string")}else if array.isUndefined {
    print("Array not defined")}// Get variable attributes
let arrayLenght = array.objectForKeyedSubscript("length")!
print(arrayLenght.toInt32()) / / 3

let arrayLenght2 = array.forProperty("length")
print(arrayLenght.toInt32()) / / 3

// Get the element in the js array
// When the array is accessed out of bounds, it will automatically expand the array length and will not crash, but the element may be undefined or null at the same time
print(array.atIndex(1)) / / 2
print(array.objectAtIndexedSubscript(2)) / / 3
print(array.objectAtIndexedSubscript(3)) // undefined

// Inserts elements into the js array
array.setObject("this", atIndexedSubscript: 4) / / 1, 2, 3, and this
array.setValue("js", at: 6) / / 1, 2, 3, and this, js
array.setObject("is", atIndexedSubscript: 5) / / 1, 2, 3, and this, is, js

// jsValue to array
var arr = array.toArray()!
arr[3] = "swift"

// swift array to jsValue
let jsArr = JSValue(object: arr, in: context)!

// The swift array is passed js
context.setObject(jsArr, forKeyedSubscript: "arr" as (NSCopying & NSObjectProtocol)!let element = context.evaluateScript("arr[2]")!
print(element) / / 3
Copy the code

Method calls and arguments

Let context = JSContext()! / * * * * * * * * * * * * * * * * * * * * * * * * * swift call js method is * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / / define js run method let jsRunCode = "" "var run = Function (animal) {var STR = animal + 'running' console.log(STR) return STR} "" Context. EvaluateScript (jsRunCode) / / get the run method let runFunc = context. ObjectForKeyedSubscript (" run ")! Call (withArguments: [" pigs "])! EvaluateScript ("run(' ox ')") : print(result1) // run(result2 = context.evaluatescript ("run(' ox ')")! / / print (result2) cattle are run / * * * * * * * * * * * * * * * * * * * * * * * * * js calls swift block * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / / / closures js object creation person = {(name: String, age: Int) -> JSValue in let obj = JSValue(newObjectIn: context)! obj.setObject(name, forKeyedSubscript: "name" as NSCopying & NSObjectProtocol) obj.setValue(age, forProperty: Context.setobject (person(" Max ", 18), forKeyedSubscript: Let blockCode = """ var growUp = function(person) {person.age += 1 return person } var type = typeof person var result = growUp(person) """ context.evaluateScript(blockCode) let type = context.evaluateScript("type")! let result = context.evaluateScript("result")! print(type) // object print("name = \(result.objectForKeyedSubscript("name")) and age = \(result.forProperty("age"))" ) / / name = Optional (Max) and age = Optional (19) / * * * * * * * * * * * * * * * * * * * * * * * * * js call swift method * * * * * * * * * * * * * * * * * * * * * * * * * * * * * / Func swiftResponser(animal: String) -> String {return animal + "flying"} JsFlyCode = "" function fly(animal){return swiftResponser(animal)} "" EvaluateScript (jsFlyCode); // Pass the swift method let block to js: @convention(block) (JSValue) -> String = { animal in return swiftResponser(animal: animal.toString()!) } let funcName = "swiftResponser" as (NSCopying & NSObjectProtocol)! context.setObject(unsafeBitCast(block, to: AnyObject.self), forKeyedSubscript: FuncName) // Execute swift in js let result3 = context.evaluatescript ("fly(' bird ')")! Print (result3) // Bird is flyingCopy the code

Class interaction

class ViewController: UIViewController {

    override func viewDidLoad(a) {
        super.viewDidLoad()
        // Create the js runtime environment
        let context = JSContext()!
        
        let language = Language(name: "Swift", age: 4)
        context.setObject(language, forKeyedSubscript: "language" as NSCopying & NSObjectProtocol);
        context.evaluateScript("language.name = 'JavaScript'")
        context.evaluateScript("language.age = 25")
        context.evaluateScript("language.descriptions()") // // name = JavaScript and age = 25
        
        print(language.name)
        print(language.age)
    }
}

// Implement JSExport with @objc
// The properties and methods in the protocol have the ability to interact with JS
@objc protocol JSLanguageModelProtocol: JSExport {

    var name: String { get set }
    var age: Int { get set }

    func descriptions(a)
}

// Declare the class that needs to interact with js
// Must inherit from NSObject
class Language: NSObject.JSLanguageModelProtocol {

    var name: String
    var age: Int

    init(name: String, age: Int) {self.name = name
        self.age = age
    }

    func descriptions(a) {
        print("name = \ [self.name) and age = \ [self.age)")}}Copy the code