I'm working with JavaScriptCore on an iOS Swift project. I want to test my app now with XCodes internal XCTest framework. Now when I call the method that works with the JavaScriptCore class it generates a JSContext + JSVM, hands me what I need and that's it. But as I call this method in a loop the JS garbage collector seems not to empty but keep all calls open which causes the test to freeze at around 6k persistent allocations of the VM: JS garbage collector with around 364MB. This still happens even though I wrote a manual deinitiliaze method.
Below is the code inside my test class:
let vc = TestTwoViewController()
let hexValues = ["00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "0A", "0B", "0C", "0D", "0E", "0F", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "1A", "1B", "1C", "1D", "1E", "1F", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "2A", "2B", "2C", "2D", "2E", "2F", "30", "31", "32", "33", "34", "35", "36", "37", "38", "39", "3A", "3B", "3C", "3D", "3E", "3F", "40", "41", "42", "43", "44", "45", "46", "47", "48", "49", "4A", "4B", "4C", "4D", "4E", "4F", "50", "51", "52", "53", "54", "55", "56", "57", "58", "59", "5A", "5B", "5C", "5D", "5E", "5F", "60", "61", "62", "63", "64", "65", "66", "67", "68", "69", "6A", "6B", "6C", "6D", "6E", "6F", "70", "71", "72", "73", "74", "75", "76", "77", "78", "79", "7A", "7B", "7C", "7D", "7E", "7F", "80", "81", "82", "83", "84", "85", "86", "87", "88", "89", "8A", "8B", "8C", "8D", "8E", "8F", "90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "9A", "9B", "9C", "9D", "9E", "9F", "A0", "A1", "A2", "A3", "A4", "A5", "A6", "A7", "A8", "A9", "AA", "AB", "AC", "AD", "AE", "AF", "B0", "B1", "B2", "B3", "B4", "B5", "B6", "B7", "B8", "B9", "BA", "BB", "BC", "BD", "BE", "BF", "C0", "C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "CA", "CB", "CC", "CD", "CE", "CF", "D0", "D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "DA", "DB", "DC", "DD", "DE", "DF", "E0", "E1", "E2", "E3", "E4", "E5", "E6", "E7", "E8", "E9", "EA", "EB", "EC", "ED", "EE", "EF", "F0", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "FA", "FB", "FC", "FD", "FE", "FF"]
for i in 1...2 {
var timestamps = [String]()
println("Starting loop \(i)")
for n in hexValues {
println("current hex value: \(n)")
let value = vc.checkForValidHexValue(n)
var start = NSDate.timeIntervalSinceReferenceDate()
vc.workWithJSBridge(value.description)
var intervall = NSDate.timeIntervalSinceReferenceDate() - start
timestamps.append(intervall.description)
}
vc.workWithJSBridge(nil)
println("Finished loop \(i)")
which calls:
func workWithJSBridge(value: String?) -> String {
if (value != nil) {
jsi = JSBridge(methName: method, val: value)
return jsi!.inputValue!
} else {
jsi = nil
return "Deinitilized JS Bridge"
}
}
which then goes into my JavaScript file:
var methodName: String?
var inputValue: String?
var jsSource: String?
var jsvm: JSVirtualMachine?
var ctx: JSContext?
var test: JSValue?
init(methName: String?, val: String?) {
let homeDir: String? = NSBundle.mainBundle().resourcePath
let jsFileName: String? = "/RefScenLibJS.js"
let jsFilePath = homeDir!+jsFileName!
var error: NSError?
jsSource = String(contentsOfFile: jsFilePath, encoding: NSUTF8StringEncoding, error: &error)
methodName = methName
inputValue = val
callMethod(methodName!)
}
deinit {
println("Manually deinitialize JSBridge")
}
public func callMethod(methodName: String) {
if methodName == "getValueForInput" {
createJSEnvironment()
checkForValue()
} else if methodName == "getCalcValue" {
createJSEnvironment()
calcTestValue()
} else {
println("No method found with name: \(methodName)")
}
}
func createJSEnvironment() {
// create javascript virtual machine and context and evaluate javascript
jsvm = JSVirtualMachine()
ctx = JSContext(virtualMachine: jsvm)
ctx!.exceptionHandler = { ctx, exception in println("JS Error: \(exception)") }
ctx!.evaluateScript(jsSource)
}
func calcTestValue() {
// get function(s)
let getValueForInputFunction = ctx!.objectForKeyedSubscript(testTwo)
// use function(s)
if (!getValueForInputFunction.isUndefined() && inputValue != nil) {
// call function with parameter
let valFromJS = getValueForInputFunction.callWithArguments([inputValue!])
inputValue = valFromJS.description
} else {
println("Function not found")
}
}
Here is an image of Instruments, showing the allocations:
I hope I could make it clear where my problem is and I hope someone knows how I could get rid of it...
Thanks in advance, Max
So this was kind of my own problem as I was initializing a new JavaScript VM everytime I called the function from my test. I got around that by initializing one VM at the start of my JSBridge class and voila every call got handled perfectly. But still there seems to be no way to manually deinitialize a JavaScript VM from JavaScriptCore. I pushed this (before I fell into the answer) to the WebKit team as a bug, maybe someone will look into this?! Here's the link for the WebKit Bug Tracker: https://bugs.webkit.org/show_bug.cgi?id=145433