I'm trying to pass a Swift function (with multiple parameters, defined as a closure) to an Objective-C function. Technically, the function is defined in Objective-C++, but I'm encountering an issue at the header level, so it should be equivalent for this question.
In the Objective-C header, I have this defined as:
#interface MyObjCClass : NSObject
typedef void (^MyCallback)(NSMutableData*, int);
- (void) functionThatTakesACallback: (MyCallback) callback;
In swift, I'm trying to use it in this way:
self.objcclass!.functionThatTakesACallback()
{
(values: NSMutableData, length: Int32) -> Void in
// Do something with this data.
}
The error I get is:
Cannot convert value of type '(NSMutableData, Int32) -> Void' to expected argument type 'MyCallback!'
If I unwrap that a bit, by putting the block directly into the Objective-C function definition (sans typedef):
- (void) functionThatTakesACallback: (void (^)(NSMutableData*, int)) callback;
it gives me the vaguely more helpful:
Cannot convert value of type '(NSMutableData, Int32) -> Void' to expected argument type '((NSMutableData!, Int32) -> Void)!'
So, essentially, is there any way to unwrap this closure to fit the expected type? Or redefine the Objective-C type?
I think you'll need to add nullability attributes to your callback parameter and to the callback argument for your function. There are two ways you could do it.
Add the nullability annotations to each place that needs it.
#interface MyObjCClass : NSObject
typedef void (^MyCallback)(nonnull NSMutableData*, int);
- (void) functionThatTakesACallback: (nonnull MyCallback) callback;
Set the whole file to assume nonnull
NS_ASSUME_NONNULL_BEGIN
#interface MyObjCClass : NSObject
typedef void (^MyCallback)(NSMutableData*, int);
- (void) functionThatTakesACallback: (MyCallback) callback;
#end
NS_ASSUME_NONNULL_END
I actually just managed to fix this, but because the solution is simple but rather non-intuitive given the error message, here is the solution:
The function call from Swift must actually be:
self.objcclass!.functionThatTakesACallback()
{
(values: NSMutableData!, length: Int32!) -> Void in
// Do something with this data.
}
For the parameter values Objective-C is expecting a pointer to an object and that pointer could be nil. In Swift that translates to an optional value. An optional is followed by a ? and should be unwrapped - checked for nil and if there's an object there then return it.
Try:
self.objcclass!.functionThatTakesACallback() {
(values: NSMutableData?, length: Int32) -> Void in
if let values = values { // first values is a local, second values is unwrapped into it
// Do something with this data.
}
}
Related
I have an Objective-C class that declares methods like:
#interface MyClass: NSObject
- (instancetype) initWithDefaultsFoo;
- (instancetype) initWithDefaultsBar;
#end
XCode automatically generates a corresponding Swift interface:
open class MyClass : NSObject {
public init!(defaultsFoo: ())
public init!(defaultsBar: ())
}
But how do I construct MyClass from Swift?
I've tried:
var x = MyClass(defaultsFoo: ()) // ERROR: Editor placeholder in source file.
var y = MyClass(defaultsBar: nil) // ERROR: Nil is not compatible with expected argument type '()'
var x = MyClass(defaultsFoo: ())
should work. The reason why it seemingly didn't was because when XCode autocompleted the call, it filled in a placeholder that looks like () but isn't actually ().
(Note the blue highlighting. Inspecting the file on disk also shows that the placeholder is written out as <#T##()#>.)
Alternatively:
var x = MyClass(defaultsFoo: Void())
also seems to work but is probably uglier.
Basically, what you're encountering is a bug in your Objective-C; no initializer should look like that. Either the initializer should be init and take no parameter or it should be initWith... and take a parameter. Your Objective-C violates the naming conventions, and Swift's translation of it correspondingly goes up in flames.
Hiyas
I have this interface:
typedef void (^RMIteratorCompletionBlock) (void);
#interface RMAsyncIterator<__covariant T> : NSObject
+(RMAsyncIterator<T>*) iteratorWithArray:(NSArray<T>*) array;
-(id) init NS_UNAVAILABLE;
-(id) initWithArray:(NSArray*) array NS_DESIGNATED_INITIALIZER;
-(void) iterateWithWorkerBlock:(void (^) (T object, RMAsyncIterator<T>* iterator)) block withCompletionBlock:(RMIteratorCompletionBlock) completionBlock;
-(void) advance;
-(void) complete;
#end
now, when I use this implementation method:
-(void) iterateWithWorkerBlock:(void (^) (id object, RMAsyncIterator* iterator)) block withCompletionBlock:(RMIteratorCompletionBlock) completionBlock {
I rightfully get a compiler warning about Conflicting parameter types in implementation of iterateWithWorkerBlock:withCompletionBlock:
The thing is.. how the hell are you supposed to write this implementation method? I tried a variety of different implementations, moving the type out into a typedef failed because the type is not visible outside of the interface scope, using T as type in the implementation fails because apparently, the implementation knows nada about its own interface type, using something like
-(void) iterateWithWorkerBlock:(void (^) (<__covariant T> object, RMAsyncIterator* iterator)) block withCompletionBlock:(RMIteratorCompletionBlock) completionBlock {
failed miserably and I really don't know how to implement this method correctly. For now, I simply use id, which gives me this warning, but I'd like to get rid of the warning...
Bueller? Anyone? Thanks
Just use id type
-(void) iterateWithWorkerBlock:(void (^) (id object, RMAsyncIterator* iterator)) block withCompletionBlock:(RMIteratorCompletionBlock) completionBlock {
I have a function written in C that is rather large and currently is only running in the main() function of a C file while I have been using to test it out. Here is it's declaration
void getData(const char* encodedBuffer, int user);
If you would like to see the contents of getData Here is the pastebin for it.
As of now I'm just passing in encodedBuffer which I will have a global variable that is getting updated by the getData function.
I would like to know what the proper way is to turn a C based function like this into a Objective-C protocol method. Right now I'm setting it up like this
iDriver.h (my interface/protocol)
#import <Foundation/Foundation.h>
#protocol iDriver <NSObject>
-(void)getData;
-(void)cancel;
#end
DriverOne.h (Class that actually implements the protocol method)
#import <Foundation/Foundation.h>
#import "iDriver.h"
#interface DriverOne : NSObject <iDriver>
#end
DriverOne.m
#import "DriverOne.h"
#implementation DriverOne
enum Status getData(char* encodedBuffer, int user)
{
// Copy C code which I showed in the link earlier
// to above into this method. I will want it to return a status
// and populate a global variable with the data.
}
My thought is that I shouldn't really run into any issues doing this since Objective-C is a superset of C but just copying the C code into a method like that be problematic?
When porting a C function to an ObjC method, it's just a tweak to the signature. In your case, you'll change:
enum Status getData(char* encodedBuffer, int user)
to something like:
- (enum Status) getData: (char*) encodedBuffer user: (int) user
Objective C methods are always denoted with a - at the beginning (class methods start with a +). If you want to specify the type, you put in parens (otherwise it's assumed to be type id which is generic for "object"). And you have to keywordize your function name. One keyword for each argument.
Then just like C, you can just copy paste that line into your header file:
#protocol iDriver <NSObject>
- (enum Status) getData: (char*) encodedBuffer user: (int) user;
#end
After that just copy the guts inside the method.
It's not clear how familiar you are with the ideas behind OO and instances, but using it might look something like:
DriverOne *driverOneObject = [[DriverOne alloc] init];
char *buffer = malloc(YOUR_BUFFER_SIZE);
enumStatus status = [driverObject getData: buffer user: SomeUserThing];
Coming full circle, is there a reason you want this to be reified (turned into objects)? I'm all for lots of objects myself, but one of the features of ObjectiveC, is that it IS a superset of C. You can just use your function as is from your C prototype. You don't have to wrap it up in an ObjectiveC object, unless you see an advantage to doing so.
If I have the following objects:
#interface Simple : NSObject
#end
#interface Complex : Simple
#end
And another object like:
#interface Test : NSObject
+(void) doSomething:(void (^)(Simple*)) obj;
#end
Everything works if I call the method like:
[Test doSomething:^(Simple * obj) {
}];
When I try instead to call it like:
[Test doSomething:^(Complex * obj) {
}];
The compiler says that:
Incompatible block pointer types sending 'void (^)(Complex *__strong)' to parameter of type 'void (^)(Simple *__strong)'
Because Complex extends Simple, I thought this would work, like in Java.
Is there a way to achieve this somehow?
Unfortunately, this is a limitation of the Blocks API. If you'd like to, you have the option of completely forgoing type safety and declaring the block as:
+(void) doSomething:(void (^)(id)) obj;
Which allows you to set the class of the arguments of the block. But again, this is completely unsafe, type-wise.
Use id instead of Complex * or Simple *. Block parameter types are handled differently than method parameter types (thanks #CodaFi)
I'm writing an Objective-C wrapper for a C library and having problems accessing the references of my ivars.
The C library requires that I specify a function pointer that handles events, I specify it during the initialization of my Objective-C class.
- (id) init {
[super init];
RegisterClient(&handleEvent);
return self;
}
The C library is able to start searching for something and will then call the handleEvent function in case anything happened during the search. The function (basically) looks like this.
int handleEvent(void *Event) {
[delegate didFinishSearching];
return 0;
}
At least I'd wish it looked like this. The problem is that the code won't build because 'delegate' undeclared (first use in function) (of course I have declared it, I can call [delegate didFinishSearching] from any Objective-C method but not from a C function). Older questions from stackoverflow suggest to define an additional variable (e.g. theDelegate) in the header file:
id theDelegate;
#interface Controller : NSObject {
id delegate;
}
#property (assign) id delegate;
#end
Then, whenever I change the value of delegate to a new one, I have to assign this value to theDelegate, too.
Since my C is somewhat rusty, here are my questions:
1) Can I pass the RegisterClient C function a pointer to an Objective-C method instead of a pointer to a function as an argument in order to avoid the C function handleEvent completely?
2) If not: When I create multiple instances of this Objective-C class, will theDelegate be the same for all instances? (After all, it's not declared as an instance variable...)
Objective-C methods are C functions, but they have two hidden parameters at the front, so they won't have the int f(void *) signature.
What you probably want to do is use an libffi closure. That allows you to create a function with exactly the signature that you want, but that also has a pointer to your object passed along with it. See the example in the ffi_prep_closure man page. Your handleEvent function would probably then change to look something like this:
static void handleEventClosure(ffi_cif * cif, void * result, void ** args, void * userdata)
{
// Arguments.
void * Event = *args[0];
// Closed-over data.
id delegate = (id)userdata;
// Execute the method.
[delegate didFinishSearching];
// Smaller than sizeof(long), so use ffi_arg or ffi_sarg (unsigned or signed).
*(ffi_sarg *)result = (ffi_sarg)0;
}
Most of the time C Libraries like the one you describe accept a "userinfo" parameter conveniently size to match a pointer. You can use this to your advantage by passing your object as this "userinfo" parameter.
Then in callbacks, you cast the pointer back to an object and make the calls you need.