How to store this Objective-C block inside a Swift variable? - objective-c

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]

Related

Is it possible that Bridging-header turns (void (^)(NSError *))block (ObjC) into block: () throws -> () (Swift)?

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.

Using Obj-C completion block in Swift

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

Convert Swift 2 closure to Objective-C block

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

How to invoke an Objective-C block obtained at runtime?

I'm trying to write a mock of HKHealthStore. In the stubbed executeQuery: I need to call the result handler block of a HKSampleQuery instance passed to it. The block is private so I need to get it at runtime. This is what I have so far:
- (void)executeQuery:(HKQuery *)query {
NSAssert([query isKindOfClass:HKSampleQuery.class], #"Mock executeQuery: not implemented yet for other query types than HKSampleQuery");
HKSampleQuery *sampleQuery = (HKSampleQuery *)query;
NSMutableArray<HKObject *> *queryResults = [NSMutableArray new];
for (HKObject *o in self.storedObjects) {
if ([sampleQuery.predicate evaluateWithObject:o]) {
[queryResults addObject:o];
}
}
SEL selector = NSSelectorFromString(#"resultHandler");
Method m = class_getInstanceMethod(HKSampleQuery.class, selector);
IMP imp = method_getImplementation(m);
typedef void(*resultHandler_t)(id, SEL, void(^)(HKQuery*, NSArray*, NSError*));
resultHandler_t f = (resultHandler_t)imp;
// here, I need to invoke the result handler block with sampleQuery, queryResults and nil as arguments
}
Note the selector name is "resultHandler" even though the parameter of the initializer of HKSampleQuery is called "resultsHandler".
Is there any way to invoke the block with appropriate arguments?
You're not doing what you think you are doing. You are getting the implementation of the method resultHandler (the getter method of the property resultHandler). The block you want is the value of the property resultHandler, which is the return value of running the getter method. In other words, you need to run the getter and get the result, not get the getter itself.
Simplest way to call the method and get the return value (since in this case the return value is a regular object pointer type) would be
typedef void (^resultHandler_t)(HKSampleQuery *query, NSArray *results, NSError *error);
resultHandler_t f = [sampleQuery performSelector:#selector(resultHandler)];
f(sampleQuery, queryResults, nil);
Alternately, if you declare (but not implement) the instance method or property resultHandler in a dummy category of HKSampleQuery, you can then access the property directly like resultHandler_t f = sampleQuery.resultHandler;

swift init not visible in objective-C

I'm trying to create init functions in Swift and create instances from Objective-C. The problem is that I don't see it in Project-Swift.h file and I'm not able to find the function while initializing. I have a function defined as below:
public init(userId: Int!) {
self.init(style: UITableViewStyle.Plain)
self.userId = userId
}
I even tried putting #objc(initWithUserId:) and I keep getting the same error again. Is there anything else I'm missing? How do I get the constructor visible to Objective-C code?
I read the below for this:
https://developer.apple.com/library/ios/documentation/swift/conceptual/swift_programming_language/Initialization.html
https://developer.apple.com/library/ios/documentation/swift/conceptual/buildingcocoaapps/interactingwithobjective-capis.html
How to write Init method in Swift
How to define optional methods in Swift protocol?
The issue you're seeing is that Swift can't bridge optional value types -- Int is a value type, so Int! can't be bridged. Optional reference types (i.e., any class) bridge correctly, since they can always be nil in Objective-C. Your two options are to make the parameter non-optional, in which case it would be bridged to ObjC as an int or NSInteger:
// Swift
public init(userId: Int) {
self.init(style: UITableViewStyle.Plain)
self.userId = userId
}
// ObjC
MyClass *instance = [[MyClass alloc] initWithUserId: 10];
Or use an optional NSNumber?, since that can be bridged as an optional value:
// Swift
public init(userId: NSNumber?) {
self.init(style: UITableViewStyle.Plain)
self.userId = userId?.integerValue
}
// ObjC
MyClass *instance = [[MyClass alloc] initWithUserId: #10]; // note the #-literal
Note, however, you're not actually treating the parameter like an optional - unless self.userId is also an optional you're setting yourself up for potential runtime crashes this way.
use this one:
var index: NSInteger!
#objc convenience init(index: NSInteger) {
self.init()
self.index = index
}