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
}
}
Related
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
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 am new to iOS and am trying to tackle learning Swift (and a bit of Objective C) by converting a library I found to the former. So far I'm doing alright, but am having trouble understanding one part; how this #define is supposed to work:
#define RunSafeBlock(block, ...) block ? block(__VA_ARGS__) : nil
Here it is with more context:
#import <AFNetworking.h>
// How would I write this in Swift?
#define RunSafeBlock(block, ...) block ? block(__VA_ARGS__) : nil
#interface Client ()
#end
#implementation Client
- (void)requestWithBodyBlock:(void (^)(id<AFMultipartFormData> formData))bodyBlock completion:(ClientRequestCompletion)completion {
[self validateAccessToken:^(NSError *error) {
if (error) {
// What does RunSafeBlock do?
RunSafeBlock(completion, nil, error);
return;
}
// ...
}];
}
#end
The AFNetworking parts are not really relevant, but in this example we're performing a request and validating an access token. If validateAccessToken returns an error, we pass everything to RunSafeBlock and exit out. Here we are in Swift:
import AFNetworking
// #define RunSafeBlock(block, ...) block ? block(__VA_ARGS__) : nil
class Client {
func requestWithBodyBlock(bodyBlock: (formData: AFMultipartFormData) -> Void, completion: ClientRequestCompletion) {
self.validateAccessToken({(error: NSError) -> Void in
if error != nil {
// RunSafeBlock(completion, nil, error)
return
}
// ...
})
}
}
I would really appreciate some guidance. Thanks in advance!
This macro is doing a null-check.
In Swift, unless you declare a variable as Optional, it cannot be null, so you do not have to do this check in Swift (and if it was Optional, you'd use optional unwrapping).
So just go ahead and call the completion.
(It seems the same reasoning would also apply to error. Don't you get a compile error there?)
(Also consider using the Swift version of AlamoFire).
I have a preprocessor macro that I use like this:
#implementation MyClass
- (void)methodA
{
MyLog("Hello, method A!");
}
- (void)methodB
{
MyLog("Hello, method %#!", #"B");
}
#end
And after macro expansion it looks like:
#implementation MyClass
- (void)methodA
{
; NSLog([NSString stringWithFormat:#"%s - %#", __func__, "Hello, method A!"]); ;
}
- (void)methodB
{
; NSLog([NSString stringWithFormat:#"%s - %#", __func__, "Hello, method %#!"], #"B"); ;
}
#end
This will result in these being printed for their respective methods:
-[MyClass methodA] - Hello, method A!
-[MyClass methodB] - Hello, method B!
I want to change this to a set of Objective-C or Swift methods, which I can call the same way and would give me the same result. I don't want to manage an object, so these should be class/static methods. Is it possible to tell that I'm in a particular class/method and only use a particular log prefix while in there?
If not, is there any other way to use methods to mimic the behavior I achieved with the macros?
Generally the Swift tool you want here is #function. For example:
func MyLog(msg: String, function: StaticString = #function) {
print("\(function) - \(msg)")
}
#function, when used as a default parameter value, evaluates to the function that at the calling site. Since it's a default value, you don't have to pass a function parameter.
This is not compatible with ObjC, however. For that, you'll still need macros. If you want to forward along an ObjC macro to Swift, you'd do something like:
#define MYLog(message) [Log log:message function:#(__FUNCTION__)]
You'd then need to write MyLog this way:
struct Log {
static func log(msg: String, function: String = #function) {
// ...
}
}
It has to be in a struct to be accessible to ObjC (or it could be an enum), and you have to make function of type String rather than StaticString because ObjC can't generate StaticString.
Build like this, in Swift, you would call:
Log.log("my message")
and in ObjC you would call
MyLog("my message")
I produce (by macro expansion) a similar result.
In GERuntimeConstants.h, i define
extern void QuietLog(NSString *format, ...);
in GERuntimeConstants.m, i provide QuietLog as :
void QuietLog(NSString *format, ...) {
if (format == nil) {
printf("nil\n");
return;
}
// Get a reference to the arguments that follow the format parameter
va_list argList;
va_start(argList, format);
// Perform format string argument substitution, reinstate %% escapes, then print
NSString *s = [[NSString alloc] initWithFormat:format arguments:argList];
printf("%s\n", [[s stringByReplacingOccurrencesOfString:#"%%" withString:#"%%%%"] UTF8String]);
va_end(argList);
}
in GEMacros, i define
#define __MPLOGWITHFUNCTION(s, ...) \
QuietLog(#"%s : %#",__FUNCTION__,[NSString stringWithFormat:(s), ##__VA_ARGS__])
#define MPLOG(...) __MPLOGWITHFUNCTION(__VA_ARGS__)
I include GEMacros.h in my .pch
anywhere i want to log , i would have a single line of code that looks like the following example :
- (void)cleanupWithError:(NSString *)status message:(NSString *)message {
MPLOG(#"*** Cleaning up , status[%#], message[%#]",status,message);
and this statement translates to this in the console :
-[EHRCall cleanupWithError:message:] : *** Cleaning up , status[INVALID_PARAMETERS], message[No api user with api key [patient01ApiKey]]
So in my code, i use MPLOG as NSLog (always). The definition via _MPLOGWITHFUNCTION is because I have other macros that are tuned to the build. This definition is for logging while working a debug build. For distro MPLOG defines down to ;.
Here is the Objective-C block:
#property (copy) void (^anObjcBlock)();
anObjcBlock = ^{
NSLog(#"Yea man this thing works!!");
};
NSMutableArray *theArrayThatHasTheBlockInItAtIndexZero = [NSMutableArray array];
[theArrayThatHasTheBlockInItAtIndexZero addObject:anObjBlock];
Here's what I did in Swift:
var theBlock: (()->Void)?
theBlock = theArrayThatHasTheBlockInItAtIndexZero[0] as? ()->Void
// Now call the block
theBlock!()
But with this I get runtime error.
Basically, the theBlock = theArrayThatHasTheBlockInItAtIndexZero[0] as? ()->Void statement would make theBlock nil because the as? failed. And when I changed the statement to theBlock = theArrayThatHasTheBlockInItAtIndexZero[0] as! ()->Void, I get a runtime error:
I'm not sure what else to do. This is an empty project, there really is no code in it.
It looks like the issue, in this case, comes from the NSMutableArray.
[NSMutableArray objectAtIndex:] returns id in Objective-C, which gets translated to AnyObject by Swift.
You will get an error if you attempt to cast AnyObject to () ->Void.
A workaround is the following:
// Create your own typealias (we need this for unsafeBitcast)
typealias MyType = #convention(block) () -> Void
// Get the Obj-C block as AnyObject
let objcBlock : AnyObject = array.firstObject! // or [0]
// Bitcast the AnyObject Objective-C block to a "Swifty" Objective-C block (#convention(block))
// and then assign the result to a variable of () -> Void type
let block : () -> Void = unsafeBitCast(objcBlock, MyType.self)
// Call the block
block()
This code works for me.
FUNNY FACT
If you edit your Objective-C code to look like this...
// Typedef your block type
typedef void (^MyType)();
// Declare your property as NSArray of type MyType
#property (strong) NSArray<MyType>* array;
Swift will now report the array type as [MyType]!.
For some reason, generics on NSMutableArray doesn't seem to be picked up by Swift.
Despite that, you'll get a runtime error if you execute:
let block : MyType? = array[0]