Capture 'self' (or class members) inside Swift @convention(block)

93 Views Asked by At

Background/Context

JavaScriptCore allows developers to use a JavaScript library inside their Swift/ObjC application. One such method is to directly call a JavaScript function, as demonstrated in the Swift code sample below:

let firstname = "Mickey"
let lastname = "Mouse"

if let functionFullname = self.jsContext.objectForKeyedSubscript("getFullname") {
    // Call the function that composes the fullname.
    if let fullname = functionFullname.call(withArguments: [firstname, lastname]) { 
        print(fullname.toString() ?? "jsDemo1 name not resolved")
    }
}

This calls a JavaScript function, getFullname, as follows:

function getFullname(firstname, lastname) {
    return firstname + " " + lastname;
}

Another option is to "assign" a function in Swift code when calling a JavaScript function, as demonstrated in the Swift code sample below:

// declare swiftJSFnImpl function (called later via JS object)
let swiftJSFnImpl: @convention(block) (String) -> Void = { value in
    print("\nJS Console:", value)
}

// assign swiftJSFnImpl to JS object
let swiftFunctionObject = unsafeBitCast(self.swiftJSFnImpl, to: AnyObject.self)
self.jsContext.setObject(swiftFunctionObject, forKeyedSubscript: "callSwiftFunctionFromJS" as (NSCopying & NSObjectProtocol))
_ = self.jsContext.evaluateScript("callSwiftFunctionFromJS")

// call JS function with
if let fnCallSwiftFromJS = self.jsContext.objectForKeyedSubscript("swiftJSFnImpl") {
    // Call the function that calls a Swift function from Javascript
    _ = fnCallSwiftFromJS.call(withArguments: nil)
}

function callSwiftFunctionFromJS() {
    var value = "hello world";
    mySwiftFunction(value);
}

To use a member of the class, such as self.mySecondFunction or self.name inside of an @convention(block) block, treat the block using SOLID principles, although this approach adds more complexity.

Problem

Given a variable jsContext: JSContext! that is a member of the class, BasicsViewController, the error below occurs when attempting to access a class member inside a @convention(block) block:

Cannot use instance member 'self' within property initializer; property initializers run before 'self' is available

error

Question

Is it possible to capture self or context inside of a Swift block, and if so, how?

Workarounds

There is a workaround to capture self inside of a @convention(block) block by treating the block using SOLID principles, as shown in the following example:

private func getTotalPriceOfProduct(_ product: Product) -> Float? {
    let discountedPrice: @convention(block) (Float, Float) -> Float = { price, discount in
        price * (1 - discount)
    }
    jsHandler.setObject(object: discountedPrice, withName: "discountedPrice")
    
    if let value = jsHandler.callFunction(functionName: "getTotalPriceOfProduct", withData: product, type: Product.self) {
        if value.isNumber {
            return value.toNumber() as? Float
        }
        else {
            print("error while getting total price for \(product.name)")
        }
    }
    return nil
}

For more information on this topic, see the following resources:

0

There are 0 best solutions below