Bridging from Objective C to Swift with PromiseKit - objective-c

Using PromiseKit 2.0 with Swift 1.2, I'm trying to use a PMKPromise that was created in Objective C from Swift.
Objective C code:
#interface FooTest : NSObject
+ (PMKPromise *)promise;
#end
Swift code (I've tried a number of variations, none of which work. This one is closest to the example given at http://promisekit.org/PromiseKit-2.0-Released/):
FooTest.promise().then { (obj: AnyObject?) in
self.obj = obj
}
Compiler error: Cannot invoke 'then' with an argument list of type '((AnyObject?) -> _)'
This doesn't work either:
FooTest.promise().then { (obj: AnyObject?) -> AnyPromise in
return AnyPromise()
}
Similar error: "Cannot invoke 'then' with an argument list of type '((AnyObject?) -> AnyPromise)'"

There are two different promise classes in PromiseKit, one for Swift (Promise<T>) and one for ObjC (AnyPromise). The Swift ones are generic and Objective-C cannot see generic classes, so this is why there are two.
If Foo.promise() is meant to be used in both ObjC and Swift then you are doing the right thing. If however you only intend to use this promise in Swift then I suggest rewriting it as a Promise<T>.
To use an Objective-C AnyPromise (PMKPromise is a deprecated alias for AnyPromise: prefer AnyPromise) in Swift code you must splice it into a an existing chain.
someSwiftPromise().then { _ -> AnyPromise in
return someAnyPromise()
}.then { (obj: AnyObject?) -> Void in
//…
}
There should be a way to start from an AnyPromise, probably I will add this later today:
someAnyPromise().then { (obj: AnyObject?) -> Void in
//…
}
Expect a 2.1 update. [edit: 2.1 pushed with the above then added]

Related

From Swift 3, how to use a type defined as an Obj-C block taking an id * pointer as parameter

My Swift 3.0 project uses an Objective-C library that defines a certain type (AsyncBlock) as an Obj-C block:
typedef BOOL (^AsyncBlock)(id __nullable * __nonnull context);
In Swift 3.0 terms, that should translate into a non optional pointer to an optional value, so I figured I should be able to assign a variable of type AsyncBlock to a closure defined in my swift 3 project as follows (not including bridging header and other details for brevity):
func closure(_ context: UnsafeMutablePointer<Any?>) -> Bool {
return true
}
var myClosureVariable: AsyncBlock = closure
The compiler disagrees: Cannot assign value of type '(UnsafeMutablePointer<Any?>) -> Bool' to type 'AsyncBlock' - what am I doing wrong?
An Obj-C id normally maps to a Swift 3.0 Any, but here we have to use AnyObject (not sure why). Also we need to use an AutoreleasingUnsafeMutablePointer instead of an UnsafeMutablePointer (again, not clear as to the reasons why)
This works:
func closure(_ context: AutoreleasingUnsafeMutablePointer<AnyObject?>) -> Bool {
return true
}
var myClosureVariable: AsyncBlock = closure

Problems trying to port an Objective C completion block into Swift

I'm using a third party library written in objective C with the following method:
- (void)manageServerResponse:(NSURLResponse*)response NSData:(NSData*)data andNSError:(NSError*)error onComplete:(void (^)(NSInteger kindOfError, NSDictionary*jsonResponse))onComplete;
when I port it to swift I do the following:
typealias ResponseCompletedBlock = (NSInteger, NSDictionary?) -> Void
...
let completedResponseMethod : ResponseCompletedBlock = {(kindOfError: NSInteger, jsonResponse: NSDictionary?) -> Void in
self.onComplete(kindOfError, jsonResponse: jsonResponse)}
let responseManager: ResponseManager = ResponseManager.sharedResponseManager() as! ResponseManager
responseManager.manageServerResponse(response,
NSData: data,
andNSError: error,
onComplete: completedResponseMethod)
I'm getting this error:
Cannot invoke 'manageServerResponse' with an argument list of type
'(NSURLResponse?, NSData: NSData?, andNSError: NSError?, onComplete:
ResponseCompletedBlock)'
and if I replace the last sentence for
responseManager.manageServerResponse(response,
NSData: data,
andNSError: error,
onComplete: nil)
everything works, so I assume that the problem is with the block structure, but I've tried to change everything and the error remains.
Can you help?
NSDictionary * is mapped to Swift as [NSObject : AnyObject]!,
therefore the type of the response block should be
typealias ResponseCompletedBlock = (Int, [NSObject : AnyObject]!) -> Void
and consequently
let completedResponseMethod = {(kindOfError: Int, jsonResponse: [NSObject : AnyObject]!) -> Void in
// ...
}
One method to figure out the correct Swift signature of functions is to use the autocompletion in Xcode:
You start typing
responseManager.manageServerResponse(
and Xcode suggests
Per Apple docs:
When you bridge from an NSDictionary object to a Swift dictionary, the resulting dictionary is of type [NSObject: AnyObject].
Also, unless the Objective-C code is annotated for nullability, the Swift dictionary is an implicitly-unwrapped optional. Therefore your typealias should look like this:
typealias ResponseCompletedBlock = (NSInteger, [NSObject : AnyObject]!) -> Void
I'd also recommend changing the NSInteger to Int.
Is the typealias and completedResponseMethod absolutely necessary for your program to function? If not, this would probably solve your problem:
responseManager.manageServerResponse(response, NSData: data, andNSError: error, onComplete: {
$0, $1 in
self.onComplete($0, jsonResponse: $1)
}
As your error suggests, the error was due to a type mismatch. If you use the default local parameters, you'll be able to work around that particular problem.

How to get Objective-C block input parameter in Swift Closure

I need use a Objective-C SDK in my Swift project, the Objc demo is in below
[[AlipaySDK defaultService] payOrder:orderString fromScheme:appScheme callback:^(NSDictionary *resultDic) {
NSLog(#"reslut = %#",resultDic);
}];
It pass a block to payOrder:orderString:: function, but when I call it in Swift, the auto complete help me generate these code
AlipaySDK.defaultService().payOrder(orderString, fromScheme: self.aliAppScheme, callback: { ([NSObject : AnyObject]!) -> Void in
println("Pay Success")
})
in Swift the closure input parameter has no name, in Objc it named resultDict, but in Swift I don't know how to get it pointer, Please help me, Thanks
In the objective C block, it takes an NSDictionary parameter. With Swift closures, the closure is already typed so you don't have to declare NSDictionary as the type and you really don't even need -> Void. Also the , callback: is extraneous in Swift as well because of trailing closures so your final product should be:
AlipaySDK.defaultService().payOrder(orderString, fromScheme: self.aliAppScheme) { resultDict in
println("Pay Success")
}

Swift Extension fails adding overloaded methods to Objective-C class

If a Swift extension is used to add overloaded methods to an Objective-C class, it appears to only call the first method, producing unexpected behaviour or a crash at runtime. An Extension on a Swift class (as opposed to Objective-C) works correctly. I would like to establish if there's any workarounds to this and confirm that this is a bug that I should report to Apple. Given that Objective-C does not support overloaded methods while Swift does, I can imagine that mixing the two is a recipe for problems.
The classes getting the extension are both empty except for the Objective-C class having an init function. ObjClass is declared within .h and .m files and imported using the Swift bridging header. The original SwiftClass is defined within the code sample along with the Swift extensions:
extension ObjClass {
func log(param: Int32) { NSLog("ObjClass->Int32: " + String(param)) }
func log(param: String) { NSLog("ObjClass->String: " + param) }
func log(param: ObjClass) { NSLog("ObjClass->ObjClass") }
}
class SwiftClass {
}
extension SwiftClass {
func log(param: Int32) { NSLog("SwiftClass->Int32: " + String(param)) }
func log(param: String) { NSLog("SwiftClass->String: " + param) }
func log(param: ObjClass) { NSLog("SwiftClass->ObjClass") }
}
Now call the overloaded methods with a string, int and object:
var objClass = ObjClass()
objClass.log(10)
objClass.log("string")
objClass.log(objClass)
var swiftClass = SwiftClass()
swiftClass.log(10)
swiftClass.log("string")
swiftClass.log(objClass)
For ObjClass, all the method calls are made to the first method with type Int32. Through use of COpaquePointer, I can see this is related to the object pointer but slightly different.
ObjClass->Int32: 10
ObjClass->Int32: 1881422800
ObjClass->Int32: 1879122512
Swift Extension on a Swift class works as expected:
SwiftClass->Int32: 10
SwiftClass->String: string
SwiftClass->ObjClass
Hopefully this can be supported at some point. Until then, I believe it should fail at compile time rather than give unexpected behaviour or a crash. Does anyone have a workaround to suggest for this?
I know it's calling the first method as if I swap the ObjClass extension methods, like so:
extension ObjClass {
func log(param: String) { NSLog("ObjClass->String: " + param) }
func log(param: Int32) { NSLog("ObjClass->Int32: " + String(param)) }
func log(param: ObjClass) { NSLog("ObjClass->ObjClass") }
}
Then the call to objClass.log(str) works fine but the call to objClass.log(10) produces:
main(1): EXC_BAD_ACCESS (code=1, address=0xa)
ObjC doesn't support method overloading. When you extend an ObjC class, the interface to your code is the ObjC runtime—even if you're calling your Swift code in that class from other Swift code—so your class' interface can't use features of Swift that aren't also present in ObjC.
As you've noted, this should probably be a compile error. I'd recommend filing a bug about that, especially since you've already put together a handy test case.

Using Swift CFunctionPointer to pass a callback to CoreMIDI API

It may be that this is actually not possible currently, which would be unfortunate. I'm trying to call the CoreMIDI API to set up a MIDI input. This is what I'm trying to do in Swift:
var midiClient = MIDIClientRef()
var inputPort = MIDIEndpointRef()
var status: OSStatus
func readProc(packetList: UnsafePointer<MIDIPacketList>, readProcRefCon: UnsafeMutablePointer<Void>, srcConnRefCon: UnsafeMutablePointer<Void>) -> Void {
}
status = MIDIClientCreate("MIDI client", nil, nil, &midiClient);
status = MIDIDestinationCreate(midiClient, "MIDI input", readProc, nil, &inputPort);
But I get this error: '(UnsafePointer, UnsafeMutablePointer, UnsafeMutablePointer) -> Void' is not convertible to 'MIDIReadProc'
MIDIReadProc's typedef is the following:
typealias MIDIReadProc = CFunctionPointer<((UnsafePointer<MIDIPacketList>, UnsafeMutablePointer<Void>, UnsafeMutablePointer<Void>) -> Void)>
Is there a way to get a function pointer for my readProc method to pass to the MIDIDestinationCreate API?
In Swift 2.0 (as part of Xcode 7), C APIs that deal in function pointers use function types that are annotated #convention(c). You can pass any Swift function, method, or closure as a #convention(c) function type — but only if that closure conforms to C conventions... e.g. it can't capture state from its surrounding scope.
For details, see Type Attributes in The Swift Programming Language.
As for what's in Xcode 6: Swift 1.x doesn't have a way to convert a Swift function or closure to a C function pointer -- the sole use of the CFunctionPointer type is to pass function pointers imported from (Obj)C APIs to other (Obj)C APIs.
You can declare a function pointer in C code that you expose to Swift via your project's bridging header, then use Swift to pass that to CoreMIDI. But since you're going to be reaching across a bridge anyway, you might instead think about which parts of your project are best to keep in C and what the best interface is from those parts to your Swift code is.
Swift 1.x (Old Way)
There's a way to do that - Objective-C Runtime is the trick.
import CoreMIDI
let block : #objc_block
(UnsafePointer<MIDIPacketList>,
UnsafeMutablePointer<Void>,
UnsafeMutablePointer<Void>) -> Void =
{ (pktlist,readProcRefCon,srcConnRefCon) in
//Your code goes here...
}
let imp : COpaquePointer =
imp_implementationWithBlock(unsafeBitCast(block, AnyObject.self))
let callback : MIDIReadProc = unsafeBitCast(imp, MIDIReadProc.self)
Works with CoreFoundation callbacks.
Should work for CoreMIDI too.
Swift 2.x (New Way)
In Swift 2 the process becomes "less hacky" (and slightly more readable).
import CoreMIDI
let callback : #convention(c) (pktlist : UnsafePointer<MIDIPacketList>,
readProcRefCon : UnsafeMutablePointer<Void>,
srcConnRefCon : UnsafeMutablePointer<Void>) -> Void =
{ (pktlist, readProcRefCon, srcConRefCon) in
}
let usableCallback = unsafeBitCast(callback, MIDIReadProc.self)