Search code examples
javascriptobjective-cios7javascriptcore

Using setInterval, setTimeout in JavascriptCore Framework for ObjectiveC


I've been experimenting with the new Objective C JavascriptCore Framework.

This is crazy, but it seems that it doesn't have setTimeout or setInterval. Which... I don't understand.

  1. Am I right about this?
  2. Are there alternatives?

I could potentially create the timer in Objective C but most of my library is written in Javascript, and aside from that, it seems just weird not having setInterval or setTimeout!!

I've tried a few alternative methods:

window.setInterval(function(){ /* dosomething */ }, 1000);
setInterval(function(){ /* dosomething */ }, 1000);

var interval;
interval = window.setInterval(function(){ /* dosomething */ }, 1000);
interval = setInterval(function(){ /* dosomething */ }, 1000);

I have no way to monitor what's even happening in the JSVirtualMachine either. All I know is my code stops working when there is a setInterval called.

Any help super appreciated!


Solution

  • A new implementation solving an old question.

    setTimout, setInterval and clearTimeout are not available on the context of JavaScriptCore.

    You need to implement it by yourself. I've created a swift 3 class to solve this problem. Usually, the examples show only the setTimeout function without the option to use clearTimeout. If you are using JS dependencies, there's a big chance that you are going to need the clearTimeout and setInterval functions as well.

    import Foundation
    import JavaScriptCore
    
    let timerJSSharedInstance = TimerJS()
    
    @objc protocol TimerJSExport : JSExport {
    
        func setTimeout(_ callback : JSValue,_ ms : Double) -> String
    
        func clearTimeout(_ identifier: String)
    
        func setInterval(_ callback : JSValue,_ ms : Double) -> String
    
    }
    
    // Custom class must inherit from `NSObject`
    @objc class TimerJS: NSObject, TimerJSExport {
        var timers = [String: Timer]()
    
        static func registerInto(jsContext: JSContext, forKeyedSubscript: String = "timerJS") {
            jsContext.setObject(timerJSSharedInstance,
                                forKeyedSubscript: forKeyedSubscript as (NSCopying & NSObjectProtocol))
            jsContext.evaluateScript(
                "function setTimeout(callback, ms) {" +
                "    return timerJS.setTimeout(callback, ms)" +
                "}" +
                "function clearTimeout(indentifier) {" +
                "    timerJS.clearTimeout(indentifier)" +
                "}" +
                "function setInterval(callback, ms) {" +
                "    return timerJS.setInterval(callback, ms)" +
                "}"
            )       
        }
    
        func clearTimeout(_ identifier: String) {
            let timer = timers.removeValue(forKey: identifier)
    
            timer?.invalidate()
        }
    
    
        func setInterval(_ callback: JSValue,_ ms: Double) -> String {
            return createTimer(callback: callback, ms: ms, repeats: true)
        }
    
        func setTimeout(_ callback: JSValue, _ ms: Double) -> String {
            return createTimer(callback: callback, ms: ms , repeats: false)
        }
    
        func createTimer(callback: JSValue, ms: Double, repeats : Bool) -> String {
            let timeInterval  = ms/1000.0
    
            let uuid = NSUUID().uuidString
    
            // make sure that we are queueing it all in the same executable queue...
            // JS calls are getting lost if the queue is not specified... that's what we believe... ;)
            DispatchQueue.main.async(execute: {
                let timer = Timer.scheduledTimer(timeInterval: timeInterval,
                                                 target: self,
                                                 selector: #selector(self.callJsCallback),
                                                 userInfo: callback,
                                                 repeats: repeats)
                self.timers[uuid] = timer
            })
    
    
            return uuid
        }
    
        func callJsCallback(_ timer: Timer) {
            let callback = (timer.userInfo as! JSValue)
    
            callback.call(withArguments: nil)
        }
    }
    

    Usage Example:

    jsContext = JSContext()
    TimerJS.registerInto(jsContext: jsContext)
    

    I hope that helps. :)