Swift 3 Passing a closure via selectors to objc functions - objective-c

I want to pass a closure through another function via a selector. Here is what I am trying to do ideally:
#objc private func aFunction(_ firstParam: String, onComplete: (String) -> Void) {
//..some internal codes
onComplete("Done.")
}
func functionCaller(_ selectorString: String, paramString: String, onComplete: (String) -> Void) {
let selector : Selector = NSSelectorFromString(selectorString)
self.perform(selector, with: printString, with: onComplete)
}
functionCaller("aFunction:onComplete:", paramString: "anotherParameter", onComplete: { (_ myString String) -> Void in
print(myString)
})
Here the problem is when you try to compile this, Swift gives an error called "Segmentation Fault: 11"
I found the problematic line which is:
self.perform(selector, with: printString, with: onComplete)
when I change last with: onComplete parameter to a String (also changed related functions params) it is working. As far as I understand that the problem is sending closure via self.perform call doesn't work because the first function is an '#objc' marked function (I put this because otherwise perform selector did not work on Swift 3).
So any idea how can I pass a function or closure into a '#objc' marked function via performing selector?

Try to use Any instead of String in your function
func functionCaller(_ selectorString: String, paramString: String, onComplete: (Any) -> Void) {
let selector : Selector = NSSelectorFromString(selectorString)
let complete : Any = onComplete("complete")
self.perform(selector, with: complete)
}
functionCaller("aFunction:onComplete:", paramString: "anotherParameter", onComplete: { (_ myString ) -> Void in
let string = myString as! String
print(string)
})
I tested it and works in Swift 3

let completionHandler: (Any) -> Void = { value in
let js = "try {(callBackKey)('(value)'); delete window.(callBackKey);} catch(e){};"
(jsContext as? TYWKWebView)?.evaluateJavaScript(js, completionHandler: nil)
}
let handlerDict: [String: Any] = [TYJSBridge.COMPLETION_HANDLER_KEY: completionHandler]
let _ = jsInterfaceObject.perform(sel, with: parameters, with: handlerDict)
}

Related

Swift to Objective-C call with trailing closure calls wrong method

Given the following Objective-C methods
#import Foundation;
NS_ASSUME_NONNULL_BEGIN
#interface TestClass : NSObject
// Variant 1. Notice that arg1 is nullable, and that all closure arguments are nullable.
+ (void)test:(NSString *_Nullable)arg1
action:(nullable void (^)(void))action;
// Variant 2. Notice that arg2 is non null, there is an additional, nullable, closure argument
+ (void)test:(NSString *)title
action:(nullable void (^)(void))action
action2:(nullable void (^)(void))action2;
#end
NS_ASSUME_NONNULL_END
I find that when I attempt to call the first method from Swift with fully specified args, it actually calls the second variant when I use a trailing closure
// Calls first variant (This is an erroneous claim, hence Matt's answer)
TestClass.test("arg1", action: {})
// Calls second variant
TestClass.test("arg2") {}
I was expecting Variant 1 to be called in both cases. I'm unclear if I'm doing something wrong or not. I also seem to have missed the fact that Swift could provide generate arguments at all when calling Obj-C methods and am struggling to find the relevant documentation on it.
If I replace the Obj-C TestClass with the equivalent
class TestClass {
class func test(_ arg1: String?, action: (() -> ())? = nil) {
}
class func test(_ arg1: String!, action: (() -> ())? = nil, action2: (() -> ())? = nil) {
// Should not get here
assert(false)
}
}
Then I get a compiler warning about ambiguous use of 'test' in both calls.
Tested on Xcode 12.3 and 12.4.
Obviously, I expect Variant 1 to be called in both cases.
Actually, I find that Variant 2 is called in both cases.
First generate the Swift interface for your Objective-C interface. You get this:
open class func test(_ arg1: String?, action: (() -> Void)? = nil)
open class func test(_ title: String, action: (() -> Void)?, action2: (() -> Void)? = nil)
We have now eliminated the Objective-C component from the story and can just use these methods in our testing:
typealias VoidVoid = () -> Void
func test(_ arg1: String?, action: VoidVoid? = nil) { print("1") }
func test(_ title: String, action: VoidVoid?, action2: VoidVoid? = nil) { print("2") }
func f() {
test("arg1", action: {})
test("arg2") {}
}
If we call f(), the console prints "2" twice. I don't get any compile error, but it does appear that the first variant is unreachable unless you omit both function arguments.
In matt’s answer, he shared his experience where the compiler would resolve both Swift calls to the second Objective-C variant of the method (the one with the action2 parameter). We should note that this behavior is unique to the fact that the two methods have a different nullability for the first argument, being an nullable in the first variant, and not nullable in the second variant.
Switch the nullability of the first parameter of the two methods, and the behavior changes, favoring the first variant. Likewise, if both of the renditions use the same nullability, then, again, the first variant will be used. Consider:
NS_ASSUME_NONNULL_BEGIN
#interface TestClass : NSObject
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action;
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action
action2:(nullable void (^)(void))action2;
#end
NS_ASSUME_NONNULL_END
That this translates to the following Swift interface:
open class TestClass : NSObject {
open class func test(_ title: String?, action: (() -> Void)? = nil)
open class func test(_ title: String?, action: (() -> Void)?, action2: (() -> Void)? = nil)
}
Now, both Swift calls will call the first variant, not the second:
TestClass.test("foo", action: {}) // variant 1
TestClass.test("foo") {} // variant 1
In your example (where the first variant made the first argument nullable, but the second variant did not), it is resolving to the second variant because we passed a non-optional string.
Given that (hopefully) the first variant is just an Objective-C convenience method to the second variant, it probably does not matter which is called, but the moral of the story is that the resolution to the appropriate Objective-C method is subject to very subtle considerations that may not be obvious at a glance.
At the risk of premature optimization, if you really are concerned about ensuring that Swift always call the second Objective-C variant regardless of the nullability/optionality of the first parameter in these two variations, one could make the intent explicit with NS_REFINED_FOR_SWIFT as discussed in Improving Objective-C API Declarations for Swift. For example:
NS_ASSUME_NONNULL_BEGIN
#interface TestClass : NSObject
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action NS_REFINED_FOR_SWIFT;
+ (void)test:(NSString * _Nullable)title
action:(nullable void (^)(void))action
action2:(nullable void (^)(void))action2 NS_REFINED_FOR_SWIFT;
#end
NS_ASSUME_NONNULL_END
And then manually declare your own explicit Swift interface:
extension TestClass {
#inlinable
static func test(_ title: String? = nil, action: (() -> Void)? = nil) {
__test(title, action: action, action2: nil)
}
#inlinable
static func test(_ title: String? = nil, action: (() -> Void)?, action2: (() -> Void)? = nil) {
__test(title, action: action, action2: action2)
}
}
That will always call the second variant of the Objective-C method. This eliminates method resolution behaviors that might be otherwise not be obvious, making your intent explicit.

How can I declare a function parameter that can be a string or a function, in Kotlin?

In the following function, I want to pass to it attributes for a html tag. These attributes can be strings (test("id", "123")) or functions (test("onclick", {_ -> window.alert("Hi!")})):
fun test(attr:String, value:dynamic):Unit {...}
I tried to declare the parameter value as Any, the root type in Kotlin. But functions aren't of type Any. Declaring the type as dynamic worked, but
dynamic isn't a type. It just turns off typing checking for the parameter.
dynamic only works for kotlin-js (Javascript).
How can I write this function in Kotlin (Java)? How do function types relate to Any? Is there a type that includes both function types and Any?
You could just overload the function:
fun test(attr: String, value: String) = test(attr, { value })
fun test(attr: String, createValue: () -> String): Unit {
// do stuff
}
You could write:
fun test(attr: String, string: String? = null, lambda: (() -> Unit)? = null) {
if(string != null) { // do stuff with string }
if(lambda != null) { // do stuff with lambda }
// ...
}
And then call the function in the following ways:
test("attr")
test("attr", "hello")
test("attr", lambda = { println("hello") })
test("attr") { println("hello") }
test("attr", "hello", { println("hello") })
test("attr", "hello") { println("hello") }

Cast Objective-C block NSStackBlock into Swift 3

I'm writing a reactive extension for one of the WKWebView navigation delegates. At the moment I wrote the following code:
public typealias NavigationHttpResponse = (WKWebView, WKNavigationResponse, (WKNavigationResponsePolicy) -> Swift.Void)
public var decidePolicyForNavigationResponse: Observable<NavigationHttpResponse> {
let selector = #selector(WKNavigationDelegate.webView(_:decidePolicyFor:decisionHandler:)! as ((WKNavigationDelegate) -> (NavigationHttpResponse) -> ()))
return delegate.methodInvoked(selector)
.map { params in
let webView = params[0] as! WKWebView
let navigationResponse = params[1] as! WKNavigationResponse
let decisionHandler = params[2] as! ((WKNavigationResponsePolicy) -> Swift.Void)
return (webView, navigationResponse, decisionHandler)
}
}
So far so good but when I launch my app add that code is executed, I get a crash when I'm trying to cast the block with the following line:
let decisionHandler = params[2] as! ((WKNavigationResponsePolicy) -> Swift.Void)
In the degub console I get:
Could not cast value of type '__NSStackBlock__' (0x12327d1a8) to '(__C.WKNavigationResponsePolicy) -> ()' (0x12218b8f0).
How can I safely cast from objective-c to Swift?
Or which alternative approach can I take?
Thanks!

Swift ambiguous methods with variadic parameter

I have the following method
static func t(key: String, params: AnyObject...) -> String{
let string = .......
if (params.count == 0){
return string
} else {
return String(format: string, params)
}
}
The problem is that i need to make this method available in Objective C which is impossible with variadic params. What I tried to do is create another method for objective c where params is an array. But then I also need to have third one for when there is no params.
#objc static func t(key: String, params: [AnyObject]) -> String
#objc static func t(key: String) -> String
But now swift complains that method t is ambiguous because 1st and 3rd method can take just the key.
How to make this work? I know I could use different names, but I would like to keep things consistent.
UPDATE:
Another problem is that I can't call String(format:...) correctly once inside the function since params is an array and not a group of params. Any nice way to solve this?
You can do the following:
#objc(C)
public class C : NSObject {
public static func t(key: String, params firstParam: AnyObject, _ params: AnyObject...) -> String {
return t(key, params: [firstParam] + params)
}
#objc
public static func t(key: String, params: [AnyObject]) -> String {
return "TODO"
}
#objc
static func t(key: String) -> String {
return t(key, params: [])
}
}
... calling from Swift:
C.t("")
C.t("", params: 1)
C.t("", params: 1, 2)
C.t("", params: [1, 2, 3])
This is bridged as follows:
SWIFT_CLASS_NAMED("C")
#interface C : NSObject
+ (NSString * _Nonnull)t:(NSString * _Nonnull)key params:(NSArray * _Nonnull)params;
+ (NSString * _Nonnull)t:(NSString * _Nonnull)key;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
#end
And can be messaged as follows:
#import "DarkSide.h"
#import "ObjCInterOp-Swift.h"
#implementation DarkSide
- (instancetype)init
{
self = [super init];
if (self) {
[C t:#"" params:#[]];
[C t:#""];
}
return self;
}
#end
Regarding the update to the question, there is a version of the formatted String initialiser that takes [CVarArgType] rather than CVarArgType...:
func f(format format: String, _ params: [CVarArgType]) -> String {
return String(format: format, arguments: params)
}
f(format: "%d %d %d", [1, 2, 3]) // "1 2 3"
Only CVarArgType cannot be represented in Objective C, which really complicates things. In fact, if you really must use String.init(format: String, arguments: [CVarArgType]) and get to it from Objective C then I don't see at the moment how to avoid casting from AnyObjects to CVarArgTypes along the lines of the following murky code:
#objc(C)
public class C : NSObject {
public static func t(key: String, params: CVarArgType...) -> String {
return t(key, params: params)
}
public static func t(key: String, params: [CVarArgType]) -> String {
return String(format: key, arguments: params)
}
#objc(t:params:)
public static func t_objc(key: String, params: [AnyObject]) -> String {
let args = params.map{
// here you'll probably need to parse `key` so as to
// know what to cast into!
($0 as! NSNumber).integerValue as CVarArgType
}
return t(key, params: args)
}
#objc(t:)
static func t_objc(key: String) -> String {
return t(key)
}
}
My suggestion is to either wean off things like String.init(format: String, arguments: [CVarArgType]), or to implement the base method in Objective C...

Cast closures/blocks

In Objective-C, I often pass around blocks. I use them very often to implement patterns that help avoid storing stuff into instance variables, thus avoiding threading/timing issues.
For example, I assign them to a CAAnimation via -[CAAnimation setValue:forKey:] so I can execute the block when the animation is finished. (Objective-C can treat blocks as objects; you also can do [someBlock copy] and [someBlock release].)
However, trying to use these patterns in Swift together with Objective-C seems to be very difficult. (Edit: and we can see that the language is still in flux: have adapted the code so it works on Xcode6-beta2, previous version worked on Xcode6-beta1.)
For example, I can't convert AnyObject back to a block/closure. The following yields an error from the compiler:
override func animationDidStop(anim: CAAnimation!, finished flag: Bool)
{
let completion : AnyObject! = anim.valueForKey("completionClosure")
(completion as (#objc_block ()->Void))()
// Cannot convert the expression's type 'Void' to type '#objc_block () -> Void'
}
I have found a workaround, but it's pretty ugly, IMHO: in my bridging header, I have:
static inline id blockToObject(void(^block)())
{
return block;
}
static inline void callBlockAsObject(id block)
{
((void(^)())block)();
}
And now I can do this in Swift:
func someFunc(completion: (#objc_block ()->Void))
{
let animation = CAKeyframeAnimation(keyPath: "position")
animation.delegate = self
animation.setValue(blockToObject(completion), forKey: "completionClosure")
…
}
override func animationDidStop(anim: CAAnimation!, finished flag: Bool)
{
let completion : AnyObject! = anim.valueForKey("completionClosure")
callBlockAsObject(completion)
}
It works, but I'd need a new function for every block type that I'd like to use and I'm hacking around the compiler which can't be good either.
So is there a way to solve this in a pure Swift way?
How about a generic Block parameterized with the function type?
class Block<T> {
let f : T
init (_ f: T) { self.f = f }
}
Allocate one of these; it will be a subtype of AnyObject and thus be assignable into dictionaries and arrays. This doesn't seem too onerous especially with the trailing closure syntax. In use:
5> var b1 = Block<() -> ()> { print ("Blocked b1") }
b1: Block<() -> ()> = {
f = ...
}
6> b1.f()
Blocked b1
and another example where the Block type is inferred:
11> var ar = [Block { (x:Int) in print ("Block: \(x)") }]
ar: [Block<(Int) -> ()>] = 1 value {
[0] = {
f = ...
}
}
12> ar[0].f(111)
Block: 111
I like GoZoner's solution - wrap the block in a custom class - but since you asked for the actual "Swift way" to perform the cast between a block and an AnyObject, I'll just give the answer to that question: cast with unsafeBitCast. (I'm guessing that this is more or less the same as Bryan Chen's reinterpretCast, which no longer exists.)
So, in my own code:
typealias MyDownloaderCompletionHandler = #objc_block (NSURL!) -> ()
Note: in Swift 2, this would be:
typealias MyDownloaderCompletionHandler = #convention(block) (NSURL!) -> ()
Here's the cast in one direction:
// ... cast from block to AnyObject
let ch : MyDownloaderCompletionHandler = // a completion handler closure
let ch2 : AnyObject = unsafeBitCast(ch, AnyObject.self)
Here's the cast back in the other direction:
// ... cast from AnyObject to block
let ch = // the AnyObject
let ch2 = unsafeBitCast(ch, MyDownloaderCompletionHandler.self)
// and now we can call it
ch2(url)
Here's yet another solution, allowing us to cast to exchange values with Objective-C. It builds on GoZoner's idea of wrapping the function in a class; the difference is our class is an NSObject subclass, and can thus give the function Objective-C block memory management without any hackery, and can be directly used as an AnyObject and handed over to Objective-C:
typealias MyStringExpecter = (String) -> ()
class StringExpecterHolder : NSObject {
var f : MyStringExpecter! = nil
}
Here's how to use it to wrap a function and pass where an AnyObject is expected:
func f (s:String) {println(s)}
let holder = StringExpecterHolder()
holder.f = f
let lay = CALayer()
lay.setValue(holder, forKey:"myFunction")
And here's how to extract the function later and call it:
let holder2 = lay.valueForKey("myFunction") as StringExpecterHolder
holder2.f("testing")
All you need to do is use reinterpretCast to perform force cast.
(reinterpretCast(completion) as (#objc_block Void -> Void))()
from REPL
1> import Foundation
2> var block : #objc_block Void -> Void = { println("test")}
block: #objc_block Void -> Void =
3> var obj = reinterpretCast(block) as AnyObject // this is how to cast block to AnyObject given it have #objc_block attribute
obj: __NSMallocBlock__ = {}
4> var block2 = reinterpretCast(obj) as (#objc_block Void -> Void)
block2: (#objc_block Void -> Void) =
5> block2()
test
6>