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

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.

Related

Extension can't access generic parameters at runtime

I´m using AWS Cognito, in the doc it says I have to add this function.
But I´m getting this error:
Extension of a generic Objective-C class cannot access the class's generic parameters at runtime
extension AWSTask {
public func continueWithExceptionCheckingBlock(completionBlock:#escaping (_ result: Any?, _ error: Error?) -> Void) {
self.continue({(task: AWSTask) -> Any? in
if let exception = task.exception {
print("Fatal exception: \(exception)")
kill(getpid(), SIGKILL);
}
let result: AnyObject? = task.result
let error: NSError? = task.error as NSError?
completionBlock(result, error)
return nil
})
}
}
For those who have this issue in Swift 5, try adding #objc modifier to the method. Please find the example below:
extension NSLayoutAnchor {
#objc func constrainEqual(_ anchor: NSLayoutAnchor<AnchorType>, constant: CGFloat = 0) {
let constraint = self.constraint(equalTo: anchor, constant: constant)
constraint.isActive = true
}
}
Without context I can't say if you are actually doing what the error message prompts you with, but there is an open bug report describing this issue (where no offending code is actually used):
SR-2708: Extending ObjC generics in Swift 3 does not compile
ObjC:
#interface MySet<T : id<NSCopying>> : NSObject
#end
Swift:
class Foo { }
struct Bar { }
extension MySet {
func foo() -> Foo { return Foo() }
func bar() -> Bar { return Bar() }
}
Both of the extension methods result in "Extension of a generic Objective-C class cannot access the class's generic parameters at
runtime". However, neither really does anything like that (at least
not explicitly).
If you read the comments to the bug report, you'll see that a user named 'Vasili Silin' describes having this issue when attempting to extend AWSTask, so you might have to consider alternative approaches until this bug is resolved.
I just got the same error and solve it this way :
extension yourClass where T == yourType {}

Swift 3 Passing a closure via selectors to objc functions

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)
}

Objective-C calling parameterized Swift method crashes Swift compiler

I have a simple Swift extension on NSManagedObject, in which I have a parametrized method for finding a single object - the signature looks like:
public class func findFirst<T:NSManagedObject>(inContext context : NSManagedObjectContext? = .None) -> T?
I'm trying to call this from Objective-C, but it seems like it cannot be seen. If I create a non-parameterized version I can see and call it just fine from Objective-C:
public class func findFirstUntypedWithPredicate(predicate:NSPredicate?, inContext context : NSManagedObjectContext? = .None) -> NSManagedObject?
Is there any way for ObjectiveC to be able to reach the parameterized version of the call?
I would use Self like so:
public class func findFirst(inContext context : NSManagedObjectContext? = .None) -> Self?
using the technique found here:
How can I create instances of managed object subclasses in a NSManagedObject Swift extension?
However, that causes the Swift compiler to segfault when compiling the code (Xcode 6.3.1, or Xcode 6.4 beta 2).
Edit: Here's a link with the full source of the framework I'm trying to build, including bonus Swift compiler crashes caused by templated methods:
https://www.dropbox.com/s/fixaj9ygdoi4arp/KiGiCoreData.zip?dl=0
Generic methods are not visible from Objective-C. However you can use
the ideas from How to use generic types to get object with same type to define a findFirst() class method
which returns Self? (the Swift equivalent of instancetype) without
being generic:
// Used to cast `AnyObject?` to `Self?`, `T` is inferred from the context.
func objcast<T>(obj: AnyObject?) -> T? {
return obj as! T?
}
extension NSManagedObject
{
class func entityName() -> String {
let classString = NSStringFromClass(self)
// The entity is the last component of dot-separated class name:
let components = split(classString) { $0 == "." }
return components.last ?? classString
}
// Return any matching object, or `nil` if none exists or an error occurred
class func findFirst(context : NSManagedObjectContext, withPredicate pred : NSPredicate?) -> Self? {
let name = entityName()
let request = NSFetchRequest(entityName: name)
request.predicate = pred
var error : NSError?
let result = context.executeFetchRequest(request, error: &error)
if let objects = result {
return objcast(objects.first)
} else {
println("Fetch failed: \(error?.localizedDescription)")
return nil
}
}
}
This can be used from Swift
if let obj = YourEntity.findFirst(context, withPredicate: nil) {
// found
} else {
// not found
}
and from Objective-C:
YourEntity *obj = [YourEntity findFirst:context withPredicate:nil];

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>

What is the Swift equivalent of respondsToSelector?

I've googled but not been able to find out what the swift equivalent to respondsToSelector: is.
This is the only thing I could find (Swift alternative to respondsToSelector:) but isn't too relevant in my case as its checking the existence of the delegate, I don't have a delegate I just want to check if a new API exists or not when running on the device and if not fall back to a previous version of the api.
As mentioned, in Swift most of the time you can achieve what you need with the ? optional unwrapper operator. This allows you to call a method on an object if and only if the object exists (not nil) and the method is implemented.
In the case where you still need respondsToSelector:, it is still there as part of the NSObject protocol.
If you are calling respondsToSelector: on an Obj-C type in Swift, then it works the same as you would expect. If you are using it on your own Swift class, you will need to ensure your class derives from NSObject.
Here's an example of a Swift class that you can check if it responds to a selector:
class Worker : NSObject
{
func work() { }
func eat(food: AnyObject) { }
func sleep(hours: Int, minutes: Int) { }
}
let worker = Worker()
let canWork = worker.respondsToSelector(Selector("work")) // true
let canEat = worker.respondsToSelector(Selector("eat:")) // true
let canSleep = worker.respondsToSelector(Selector("sleep:minutes:")) // true
let canQuit = worker.respondsToSelector(Selector("quit")) // false
It is important that you do not leave out the parameter names. In this example, Selector("sleep::") is not the same as Selector("sleep:minutes:").
There is no real Swift replacement.
You can check in the following way:
someObject.someMethod?()
This calls the method someMethod only if it's defined on object someObject but you can use it only for #objc protocols which have declared the method as optional.
Swift is inherently a safe language so everytime you call a method Swift has to know the method is there. No runtime checking is possible. You can't just call random methods on random objects.
Even in Obj-C you should avoid such things when possible because it doesn't play well with ARC (ARC then triggers warnings for performSelector:).
However, when checking for available APIs, you can still use respondsToSelector:, even if Swift, if you are dealing with NSObject instances:
#interface TestA : NSObject
- (void)someMethod;
#end
#implementation TestA
//this triggers a warning
#end
var a = TestA()
if a.respondsToSelector("someMethod") {
a.someMethod()
}
Update Mar 20, 2017 for Swift 3 syntax:
If you don't care whether the optional method exists, just call delegate?.optionalMethod?()
Otherwise, using guard is probably the best approach:
weak var delegate: SomeDelegateWithOptionals?
func someMethod() {
guard let method = delegate?.optionalMethod else {
// optional not implemented
alternativeMethod()
return
}
method()
}
Original answer:
You can use the "if let" approach to test an optional protocol like this:
weak var delegate: SomeDelegateWithOptionals?
func someMethod() {
if let delegate = delegate {
if let theMethod = delegate.theOptionalProtocolMethod? {
theMethod()
return
}
}
// Reaching here means the delegate doesn't exist or doesn't respond to the optional method
alternativeMethod()
}
If the method you are testing for is defined as an optional method in a #objc protocol (which sounds like your case), then use the optional chaining pattern as:
if let result = object.method?(args) {
/* method exists, result assigned, use result */
}
else { ... }
When the method is declare as returning Void, simply use:
if object.method?(args) { ... }
See:
“Calling Methods Through Optional Chaining”
Excerpt From: Apple Inc. “The Swift Programming Language.”
iBooks. https://itun.es/us/jEUH0.l
It seems you need to define your protocol as as subprotocol of NSObjectProtocol ... then you'll get respondsToSelector method
#objc protocol YourDelegate : NSObjectProtocol
{
func yourDelegateMethod(passObject: SomeObject)
}
note that only specifying #objc was not enough. You should be also careful that the actual delegate is a subclass of NSObject - which in Swift might not be.
For swift3
If you just want to call the method, run the code below.
self.delegate?.method?()
Functions are first-class types in Swift, so you can check whether an optional function defined in a protocol has been implemented by comparing it to nil:
if (someObject.someMethod != nil) {
someObject.someMethod!(someArgument)
} else {
// do something else
}
In Swift 2,Apple introduced a new feature called API availability checking, which might be a replacement for respondsToSelector: method.The following code snippet comparison is copied from the WWDC2015 Session 106 What's New in Swift which I thought might help you,please check it out if you need to know more.
The Old Approach:
#IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
if dropButton.respondsToSelector("setSpringLoaded:") {
dropButton.springLoaded = true
}
}
The Better Approach:
#IBOutlet var dropButton: NSButton!
override func awakeFromNib() {
if #available(OSX 10.10.3, *) {
dropButton.springLoaded = true
}
}
For swift 3.0
import UIKit
#objc protocol ADelegate : NSObjectProtocol {
#objc optional func hi1()
#objc optional func hi2(message1:String, message2:String)
}
class SomeObject : NSObject {
weak var delegate:ADelegate?
func run() {
// single method
if let methodHi1 = delegate?.hi1 {
methodHi1()
} else {
print("fail h1")
}
// multiple parameters
if let methodHi2 = delegate?.hi2 {
methodHi2("superman", "batman")
} else {
print("fail h2")
}
}
}
class ViewController: UIViewController, ADelegate {
let someObject = SomeObject()
override func viewDidLoad() {
super.viewDidLoad()
someObject.delegate = self
someObject.run()
}
// MARK: ADelegate
func hi1() {
print("Hi")
}
func hi2(message1: String, message2: String) {
print("Hi \(message1) \(message2)")
}
}
Currently (Swift 2.1) you can check it using 3 ways:
Using respondsToSelector answered by #Erik_at_Digit
Using '?' answered by #Sulthan
And using as? operator:
if let delegateMe = self.delegate as? YourCustomViewController
{
delegateMe.onSuccess()
}
Basically it depends on what you are trying to achieve:
If for example your app logic need to perform some action and the delegate isn't set or the pointed delegate didn't implement the onSuccess() method (protocol method) so option 1 and 3 are the best choice, though I'd use option 3 which is Swift way.
If you don't want to do anything when delegate is nil or method isn't implemented then use option 2.
As I started to update my old project to Swift 3.2, I just needed to change the method from
respondsToSelector(selector)
to:
responds(to: selector)
I just implement this myself in a project, see code below. As mentions by #Christopher Pickslay it is important to remember that functions are first class citizens and can therefore be treated like optional variables.
#objc protocol ContactDetailsDelegate: class {
optional func deleteContact(contact: Contact) -> NSError?
}
...
weak var delegate:ContactDetailsDelegate!
if let deleteContact = delegate.deleteContact {
deleteContact(contact)
}
another possible syntax by swift..
if let delegate = self.delegate, method = delegate.somemethod{
method()
}
I use guard let else, so that can do some default stuff if the delegate func is not implemented.
#objc protocol ViewController2Delegate: NSObjectProtocol {
optional func viewController2(controller: ViewController2, didSomethingWithStringAndReturnVoid string: String)
optional func viewController2(controller: ViewController2, didSomethingWithStringAndReturnString string: String) -> String
}
class ViewController2: UIViewController {
weak var delegate: ViewController2Delegate?
#IBAction func onVoidButtonClicked(sender: AnyObject){
if (delegate != nil && delegate!.respondsToSelector(Selector("viewController2:didSomethingWithStringAndReturnVoid:"))) {
NSLog("ReturnVoid is implemented")
delegate!.viewController2!(self, didSomethingWithStringAndReturnVoid: "dummy")
}
else{
NSLog("ReturnVoid is not implemented")
// Do something by default
}
}
#IBAction func onStringButtonClicked(sender: AnyObject){
guard let result = delegate?.viewController2?(self, didSomethingWithStringAndReturnString: "dummy") else {
NSLog("ReturnString is not implemented")
// Do something by default
return
}
NSLog("ReturnString is implemented with result: \(result)")
}
}
I guess you want to make a default implementation for delegate. You can do this:
let defaultHandler = {}
(delegate?.method ?? defaultHandler)()
Swift 3:
protocol
#objc protocol SomeDelegate {
#objc optional func method()
}
Object
class SomeObject : NSObject {
weak var delegate:SomeObject?
func delegateMethod() {
if let delegateMethod = delegate?.method{
delegateMethod()
}else {
//Failed
}
}
}
The equivalent is the ? operator:
var value: NSNumber? = myQuestionableObject?.importantMethod()
importantMethod will only be called if myQuestionableObject exists and implements it.