I'm confused on something. I have this code in a framework in ObjC.
handler:(void (^)(NSDictionary<NSString *, id> *replyMessage))handler;
I have a function in Swift that has a parameter of type
#escaping ([String: Any]) -> Void
and tries to pass it to the ObjC Framework function. The compiler complains and says cast to
guard let handler = handler as? (([String : Any]?) -> Void) else {
return
}
Why is the compiler asking me to cast it to this other type with an optional dictionary? When I do so the cast fails because it's not of that type, but if I try passing in the function as is, the compiler won't let me and tells me I need to cast it.
Is there something wrong with my ObjC function in order to make Swift happier?
I tried putting _Nonnull in the declaration of the framework before NSDictionary which then the compiler said,
Nullability specific _Nonnull cannot be applied to non-pointer type 'NSDictionary..."
As an aside, with Swift 4.0, I listened to the compiler and forcecasted my handler function
handler as! (([String : Any]?) -> Void)
When I called the function that uses it and it used to work. Now in Xcode 9.3, I get a crash for this.
The Objective-C block has a nullable parameter but your Swift closure has a non-optional (non-nullable) parameter. That's the problem.
Either make the Objective-C block have a non-nullable parameter or make the Swift closure have an optional parameter.
Either do:
handler:(void (^)(NSDictionary<NSString *, id> * _Nonnull replyMessage))handler;
or do:
#escaping ([String: Any]?) -> Void
Related
I have an objc method I'd like to call from swift, but the method exists in a third party framework that may or may not exist at runtime.
What I'd like to do is call it dynamically, so I'm looking into interacting with it using selectors.
The method signature looks like this in ObjC
- (NSString * _Nullable)buildData:(NSError * _Nullable __autoreleasing * _Nullable)error;
Removing the nullable annotations, it's the bog standard "return a thing, or error" pattern that ObjC has had forever.
- (NSString*)buildData:(NSError*)error;
If swift can load this at compile time, then it quite happily translates into
func buildData() throws -> String
However, I'd like to call it dynamically. I've worked out I can do this:
let _target:NSObject = // obtain a reference to the underlying value
var error: NSError? = nil
_target.perform(NSSelectorFromString("buildData:"), with: &error)
The problem is, I can't pass a reference to the perform selector method. XCode gives me a compile error of
'&' used with non-inout argument of type 'Any?'
So my question is, how can I call this method using selectors?
Try this:
var error: NSError? = nil
let _target: NSObject = // obtain a reference to the underlying value
withUnsafeMutablePointer(to: &error) {
let selector: Selector = NSSelectorFromString("buildData:")
let methodIMP: IMP! = _target.method(for: selector)
unsafeBitCast(methodIMP,to:(#convention(c)(Any?,Selector,OpaquePointer)->Void).self)(_target,selector,OpaquePointer($0))
}
More info on using convention(c) for invoking selectors in Swift in my answer here
I have
OBJC:
- (void)doSomething:(void (^)(NSError *))block;
SWIFT:
let test = Test()
test.doSomething(<#T##block: ((Error?) -> Void)!##((Error?) -> Void)!##(Error?) -> Void#>)
I would rather
try? test.doSomething { }
I would like bridging-header to translate the function into
func doSomething(block: () throws -> ()) throws {
try block()
}
Is it possible? Thanks to all!
Your Objective-C method is declaring a parameter which is a block that receives an NSError object. It's basically declaring a callback.
If your method is not asynchronous you should declare it like this:
- (BOOL)doSomething:(NSError **)error;
Now the parameter is a pointer to an NSError* object. If the method fails for some reason, it should set an appropriate error for that parameter like so:
if (error != NULL) {
*error = <an appropriate error>
}
Also note the BOOL return type. According to Cocoa conventions the caller should refer to the return type to determine if the method failed or not, instead of testing for the existence of an NSError* object.
Declaring a method like this will expose it in Swift using the throws mechanics.
Update:
I don't think you can declare a Swift throwing block in Objective-C. If you go the other way around, and declare your desired method signature in Swift you'll see the compiler complains it can't be represented in Objective-C.
Most likely the (NSError **) to throwable convention never got implemented for blocks.
I'm trying to write the following ObjC code in Swift 3:
- (void)scrollViewScroll:(UIScrollView*)scrollView {
// some code
if ([_userDelegate respondsToSelector:_cmd]) {
[_userDelegate scrollViewDidEndDecelerating:scrollView];
}
}
But do not know what to replace _cmd with. I'm trying function, but it doesn't work:
func scrollViewDidScroll(_ scrollView: UIScrollView) {
// some code
if (userDelegate?.responds(to: #function))! {
userDelegate?.scrollViewDidScroll!(scrollView)
}
}
using #selector(scrollViewDidScroll(_:)) works, but is there a way to keep it generic?
Edit: Possible duplicate answer is about getting function name which isn't what I'm asking above
Swift doesn't have selectors.
Objective-C sends messages to objects while Swift calls functions. So checking if object can respond to selector is part of Objective-C and NSObject.
Swift protocol functions are required by default. Swift compiler doesn't let you skip those function implementations. But you can make them optional, and you have to check, if these functions implemented before calling.
In this case, just call function with question mark at the end, like this
if let returnValue = userDelegate?.theOptionalFunction?(arguments) {
// you got value
} else {
// delegate returned nil or delegate function isn't implemented
}
Source: The Swift Programming Language
An optional protocol requirement can be called with optional chaining, to account for the possibility that the requirement was not implemented by a type that conforms to the protocol. You check for an implementation of an optional method by writing a question mark after the name of the method when it is called, such as someOptionalMethod?(someArgument).
In Objective-C, I have a completion block class defined as:
File.h
typedef void (^MYCompletionBlock)(BOOL success, NSDictionary *result, NSError *error);
Then, in a Swift file, I try to use the completion block as follows:
Swift.swift
class MyClass: NSObject{
...
func MyFunction() -> Void {
...
objcMethod(param1, withCompletion: {(MYCompletionBlock) -> Void in
if (success){ // Error:"Use of unresolved identifier 'success'"
}
}
...
}
...
}
But, I keep getting an error: "Use of unresolved identifier 'success'".
I've tried the following as well:
objcMethod(param1, withCompletion: {(success:Bool, result: NSDictionary, error:NSError) -> Void in
if (success){ // Error:"Cannot convert value of type '(Bool, NSDictionary, NSError) -> Void' to expected argument type "MYCompletionBlock!"
}
}
Can somebody help me understand how to correctly specify a Obj-C completion block in Swift?
Given that your closure doesn't specify nullability qualifiers (where they almost certainly are optional), one can safely assume that your Objective-C API has not been audited for nullability. Thus, Swift will treat pointers as implicitly unwrapped optionals. Furthermore, nowadays the NSDictionary is mapped to a [NSObject : AnyObject] Swift dictionary.
Thus, it would be:
obj.objcMethod(param) { (success: Bool, result: [NSObject : AnyObject]!, error: NSError!) in
if success {
// do something
}
}
Or, as Kobi points out, you can let the compiler infer the types:
obj.objcMethod(param) { success, result, error in
if success {
// do something
}
}
Note, you don't have to remember this yourself. You can leverage Xcode's code completion as you enter the code. So, type enough to match the method name and when it matches objcMethod, then hit enter:
When you get to MYCompletionBlock, hit enter again, and it will show you the correct signature:
If this Objective-C method was my own class, I would audit it for nullability. So, for example, let's assume the param is optional, the closure is required, and the result and error were optional, you might define it like so:
NS_ASSUME_NONNULL_BEGIN
typedef void (^MYCompletionBlock)(BOOL success, NSDictionary * _Nullable result, NSError * _Nullable error);
#interface MyObject : NSObject
- (void)objcMethod:(NSDictionary * _Nullable)param1 withCompletionHandler:(MYCompletionBlock)completionHandler;
#end
NS_ASSUME_NONNULL_END
And, if that was the case, your Swift code would call it like so:
obj.objcMethod(param) { (success: Bool, result: [NSObject : AnyObject]?, error: NSError?) in
if success {
// do something
}
}
Or, again, just let the compiler infer the types for you (but this time they'd be inferred as optionals that are not implicitly unwrapped):
obj.objcMethod(param) { success, result, error in
if success {
// do something
}
}
You shouldn't specify types for the completion block parameters, as some types defer between Swift and Objective C (e.g. BOOL is actually ObjCBool in Swift).
This should work:
objcMethod(param1) { (success, result, error) in
if (success){
// Do something
}
}
I'm trying to build an Objective-C block in Swift 2 in order to add it to an NSArray like so :
typealias CompletionBlock = () -> Void
let aBlock:CompletionBlock = {
print("Hello world!")
}
let nsArray = NSMutableArray()
nsArray.addObject(aBlock) // Error
I know it will work just fine with a Swift array, but I need an NSArray here for compatibility with existing Objective-C code. And if I use a swift array the compiler will refuse to cast it to an NSArray because it won't be a [AnyObject] (it will be a [Any]).
The problem here is that a swift closure is not an object contrary to Objective-C blocks which are objects behind the scene (they are instances of NSBlock which is a subclass of NSObject)
So my question is : How do a create an Objective-C block in swift ? I've tried using #convention (block) in the typealias but it doesn't work.
EDIT : As of Swift 3, this is completely unnecessary (and doesn't even work). Adding closures to Objective-C arrays works out of the box in Swift 3. The answer below is valid for Swift 2 only.
I know this is a duplicate but I will still post a refactored answer from swift-closure-as-anyobject and cast-closures-blocks in case anyone lands on this one first.
The solution is to use the unsafeBitCast function to convert the Swift closure to an Objective-C compatible object before adding it to an NSArray and back before using it in Swift.
// The `#convention(block)` is important here in order to get
// Objective-C like memory management
typealias CompletionBlock = #convention(block) () -> Void
let aBlock:CompletionBlock = {
print("Hello world!")
}
let nsArray = NSMutableArray()
let blockObject = unsafeBitCast(aBlock, AnyObject.self)
nsArray.addObject(blockObject)
let closureObject = nsArray[0]
let closure = unsafeBitCast(closureObject, CompletionBlock.self)
closure()