How to use Objective-C function with a block input in Swift - ios

I'm using a Objective-C SDK. The function I want to use take a parameter that
delegates the delegate for handling user consent
- (void) startWithDelegate: (id<SharingDelegate>) delegate;
The SharingDelegate requires me to implement 2 functions, one of them has a block input
(void) getUserConsent: (void (^)(BOOL)) consentHandler deviceN: (NSString *) deviceN;
Can anyone tell me how to implement this function in Swift? what is the signature?

If it is a method (hard to tell because your display of it is garbled), I take it that it would be something like this:
func getUserConsent(consentHandler:Bool -> (), deviceN: String) {
// body goes here
}

Related

Calling block from Objective C collection in Swift

I have a Swift object that takes a dictionary of blocks (keyed by Strings), stores it and runs block under given key later at some point depending on external circumstances (think different behaviours depending on the backend response):
#objc func register(behaviors: [String: #convention(block) () -> Void] {
// ...
}
It's used in a mixed-language project, so it needs to be accessible from both Swift and Objective-C. That's why there's #convention(block), otherwise compiler would complain about not being able to represent this function in Objective-C.
It works fine in Swift. But when I try to invoke it from Objective-C like that:
[behaviorManager register:#{
#"default": ^{
// ...
}
}];
The code crashes and I get following error:
Could not cast value of type '__NSGlobalBlock__' (0x...) to '#convention(block) () -> ()' (0x...).
Why is that, what's going on? I thought #convention(block) is to specifically tell the compiler that Objective C blocks are going to be passed, and that's exactly what gets passed to the function in the call.
That's why there's #convention(block), otherwise compiler would
complain about not being able to represent this function in
Objective-C
For the sake of consistency: commonly you use #convention attribute the other way around - when there is an interface which takes a C-pointer (and implemented in C) or an Objective-C block (and implemented in Objective-C), and you pass a Swift closure with a corresponding #convention as an argument instead (so the compiler actually can generate appropriate memory layout out of the Swift closure for the C/Objective-C implementation). So it should work perfectly fine if it's Objective-C side where the Swift-created closures are called like blocks:
#interface TDWObject : NSObject
- (void)passArguments:(NSDictionary<NSString *, void(^)()> *)params;
#end
If the class is exposed to Swift the compiler then generates corresponding signature that takes a dictionary of #convention(block) values:
func passArguments(_ params: [String : #convention(block) () -> Void])
This, however, doesn't cancel the fact that closures with #convention attribute should still work in Swift, but the things get complicated when it comes to collections, and I assume it has something with value-type vs reference-type optimisation of Swift collections. To get it round, I'd propose to make it apparent that this collection holds a reference type, by promoting it to the [String: AnyObject] and casting later on to a corresponding block type:
#objc func takeClosures(_ closures: [String: AnyObject]) {
guard let block = closures["One"] else {
return // the block is missing
}
let closure = unsafeBitCast(block, to: ObjCBlock.self)
closure()
}
Alternatively, you may want to wrap your blocks inside of an Objective-C object, so Swift is well aware of that it's a reference type:
typedef void(^Block)();
#interface TDWBlockWrapper: NSObject
#property(nonatomic, readonly) Block block;
#end
#interface TDWBlockWrapper ()
- (instancetype)initWithBlock:(Block)block;
#end
#implementation TDWBlockWrapper
- (instancetype)initWithBlock:(Block)block {
if (self = [super init]) {
_block = block;
}
return self;
}
#end
Then for Swift it will work as simple as that:
#objc func takeBlockWrappers(_ wrappers: [String: TDWBlockWrapper]) {
guard let wrapper = wrappers["One"] else {
return // the block is missing
}
wrapper.block()
}

Why I always get NO when performSelector:withObject:#YES in iOS, which is different in macOS?

I have some iOS code as follows:
//iOS
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:#selector(handler:) withObject:#YES];
}
- (void)handler:(BOOL)arg { //always NO
if(arg) {
NSLog(#"Uh-hah!"); //won't log
}
}
I know I shouldn't write like this. It's wrong since #YES is an object, I should receive an id as argument and unbox it in handler:, like:
- (void)handler:(id)arg {
if([arg boolValue]) {...}
}
As a wrong code, for any other object of whatever class instead of #YES, I always get arg == NO.The problem is, why ON EARTH bool arg is always NO?
I did some research and here is what I've learned:
in iOS, BOOL is actually _Bool(or macro bool) in C (_Bool keyword)
in macOS, BOOL is actually signed char
If I create an identical macOS code, I'll get different result, like:
//macOS
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:#selector(handler:) withObject:#YES]; //#YES's address: say 0x00007fffa38533e8
}
- (void)handler:(BOOL)arg { //\xe8 (=-24)
if(arg) {
NSLog(#"Uh-hah!"); //"Uh-huh!"
}
}
It makes sense since BOOL is just signed char, the argument is cast from the lowest byte of #YES object's address.
However, this explanation won't apply to iOS code. I thought any non-zero number would be cast to true(and the address itself must be non-zero).Bu why I got NO? *
-[NSObject performSelector:withObject:] is only supposed to be used with a method that takes exactly one object-pointer parameter, and returns an object-pointer. Your method takes a BOOL parameter, not an object-pointer parameter, so it cannot be used with -[NSObject performSelector:withObject:].
If you are always going to send the message handler: and you know the method has a BOOL parameter, you should just call it directly:
[self handler:YES];
If the name of the method will be determined dynamically at runtime, but you know the signature of the method will always be the same (in this case exactly one parameter of type BOOL, returning nothing), you can call it with objc_msgSend(). You must cast objc_msgSend to the appropriate function type for the underlying implementing function of the method before calling it (remember, the implementing function for Objective-C methods have the first two parameters being self and _cmd, followed by the declared parameters). This is because objc_msgSend is a trampoline that calls into the appropriate implementing function with all the registers and stack used for storing the arguments intact, so you must call it with the calling convention for the implementing function. In your case, you would do:
SEL selector = #selector(handler:); // assume this is computed dynamically at runtime
((void (*)(id, SEL, BOOL))objc_msgSend)(self, selector, YES);
By the way, if you look at the source code for -[NSObject performSelector:withObject:], you will see that they do the same thing -- they know that the signature of the method must be one parameter of type id and a return type of id, so they cast objc_msgSend to id (*)(id, SEL, id) and then call it.
In the rare case where the signature of the method will also vary dynamically and is not known at compile-time, then you will have to use NSInvocation.
Let's consider what happened in your case when you used -[NSObject performSelector:withObject:] with a method of the wrong signature. Inside, they call objc_msgSend(), which is equivalent to calling the underlying implementing function, with a function pointer of the type id (*)(id, SEL, id). However, the implementing function of your method actually has type void (id, SEL, BOOL). So you are calling a function using a function pointer of a different type. According to the C standard (C99 standard, section 6.5.2.2, paragraph 9):
If the function is defined with a type that is not compatible with the
type (of the expression) pointed to by the expression that denotes the
called function, the behavior is undefined.
So basically what you are seeing is undefined behavior. Undefined behavior means anything can happen. It could return one thing on one system and another thing on another, as you're seeing, or it could crash the whole program. You can't rely on any specific behavior.
The issue is with the handler's declaration. The param type of a handler in this case should be id (Objective C) or Any (Swift).
- (void)handler:(id)arg {
if(arg) { // Would be same as object passed
NSLog(#"Uh-hah!");
}
The answer is in question only..
in iOS,BOOL declaration is:
'typedef bool BOOL' and you have also mentioned this..
so for iOS it will take the default value as false. so your log will never print.

Method parameters are nil when called using IMP in Release configuration

Our Swift application needed some lower-level C/Objective-C code, so we added a Dynamic Library to make integration with the application easier.
The library has a single, shared instance of a controller, but the style of the callbacks doesn't work well for closures, so we went with a protocol. However since multiple classes need to use this controller it would need to have multiple delegates. So each class registers itself as a delegate and when a protocol method is called it iterates through each delegate, gets the IMP for the selector, and calls it.
On debug builds this worked fine, it was only until we used the Release configuration that we noticed that the parameters to these functions were nil in the implementation of the protocol methods, even if they were not nil when called.
This is how our protocol methods are called:
- (void) delegateCall:(SEL)sel withObject:(id)object {
for (id delegate in self.delegates) {
if ([delegate respondsToSelector:sel]) {
IMP imp = [delegate methodForSelector:sel];
void (*func)(__strong id,SEL,...) = (void (*)(__strong id, SEL, ...))imp;
func(delegate, sel, object);
}
}
}
Let's use the example protocol method: - (void) blah:(NSNumber * _Null_unspecified)aNumber;
If we call [self delegateCall:#selector(blah:) withObject:#32];, the object will be nil in the implementation of blah:
func blah(_ aNumber: NSNumber) {
if aNumber == nil {
print("The number is nil somehow?!?!?!") // <-- Release
} else {
print("The number is: \(aNumber.intValue)") // <-- Debug, prints 32
}
}
If we use call the method in code on the delegates (rather than using IMP) the issue does not happen:
for (id delegate in self.delegates) {
[delegate blah:#32];
}
Having never tried casting an IMP instance to a function with variadic arguments, I can't say for sure how it would/should work (it would probably involve parsing a va_list, for instance), but since you know that you have one and only one parameter, I think you should be able to solve this particular problem by just eliminating your use of variadic arguments when you cast your IMP instance to a function pointer:
- (void) delegateCall:(SEL)sel withObject:(id)object {
for (id delegate in self.delegates) {
if ([delegate respondsToSelector:sel]) {
IMP imp = [delegate methodForSelector:sel];
void (*func)(__strong id, SEL, id) = (void (*)(_strong id, SEL, id))imp;
func(delegate, sel, object);
}
}
}
Since you know that the argument is already an id, this should be a perfectly safe replacement.
As to why your original implementation works in a debug build but not in a release build, I can only guess; it might be related to the fact that release builds typically strip all symbols during link, and the runtime might be able to take advantage of the symbols, if present, in order to guess the correct argument ordering when invoking? Perhaps the compiler uses the wrong calling convention in a release configuration when generating the call to a function declared with a fixed argument footprint but invoked with variadic arguments? I'd be interested in further information if anyone has a more definitive answer to the debug/release question.
See the discussion on calling conventions here for a possible alternative using reinterpret_cast, if in fact your problem is due to a calling convention mismatch.

How to declare blocks using Swift in iOS?

I am trying to convert my Objective C source code to swift, I have searched alot to change syntax for Blocks.
Here is my Objective C code that I want to switch to Swift :
class.h file :
#property (nonatomic, copy) void (^tapBlock)(CGFlagsCell *);
+ (NSString *)cellIdentifier;
Class.m file :
self.tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapCell)];
- (void)didTapCell {
self.tapBlock(self);
}
In Swift, blocks, functions and closures are the same thing, they have the same signature and are interchangeable.
So this would give you something like this
var tapBlock: CGFlagsCell -> Void
You can add parenthesis around the parameter (optional if there is only one, recommended if there are multiple input parameters) and around the return type (optional):
var tapBlock: (CGFlagsCell) -> (Void)
Define a closure a variable of your class :
var tapBlock: (parameterTypes) -> (returnType)
Swift Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks Objective-C

What type should I use?

I have a result coming from a method that is either of kind TWTweetComposeViewControllerResult or SLComposeViewControllerResult.
I need to pass this to a method. Something like
[self doSomething:result];
How do I declare this method?
- (void) doSomething:(SLComposeViewControllerResult) result ?
- (void) doSomething:(TWTweetComposeViewControllerResult) result ?
- (void) doSomething:(NSNumber *) result ?
Make two different methods that handle each. Each enum is a different type and should be treated as such. You can also forward the result on, using a boolean if you are just indicating success, or your own custom enum if you need more information.
- (void)doSomethingSL:(SLComposeViewControllerResult) result
{
// made up, idk what the result enum would be be
[self doSomething:(result == SLComposeSuccess)];
}
- (void)doSomethingTweet:(TWTweetComposeViewControllerResult) result
{
// made up, idk what the result enum would be be
[self doSomething:(result == TWTweetSuccess)];
}
- (void)doSomething:(BOOL)success
{
}
If you are still convinced that you want to handle them in a uniform way and ignore types, you could always cast the results to an int in the method and forward them on.
Both SLComposeViewControllerResult and TWTweetComposeViewControllerResult are both enums, with 0 meaning cancelled and 1 meaning done.
So any of these should be OK:
- (void) doSomething:(SLComposeViewControllerResult) result;
- (void) doSomething:(TWTweetComposeViewControllerResult) result;
- (void) doSomething:(NSInteger) result;
[edit] Note this comment in TWTweetComposeViewController.h:
// This class has been deprecated in iOS 6. Please use SLComposeViewController (in the Social framework) instead.
So you should just use the SLComposeViewControllerResult version.

Resources