Using Swift CFunctionPointer to pass a callback to CoreMIDI API - objective-c

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)

Related

Passing a Swift generic protocol argument to an Obj-C “Protocol” function

I have a class that needs to pass a Protocol to an Obj-C function. I have a constructor that takes the Protocol, but as the class is a generic that also takes the same protocol, I thought I could optimise it. However, if I try to use the generic value when calling the function, it fails to compile. I've tried various combinations of ".self" and ".Type" and ".Protocol", both in the code and in the generic argument, and nothing works. Is there any way to achieve this?
This is a Playground project to show the problem.
import Foundation
#objc protocol TestProtocol {
}
class Test<P> {
init() {
test(p: P.self) // Fails to compile with: Cannot convert value of type 'P.Type' to expected argument type 'Protocol'
test(p: TestProtocol.self) // Compiles
}
func test(p: Protocol) {
}
}
let c = Test<TestProtocol>()

'Property not found on object of type' when creating Objective C to Swift Bridge

I'm getting an error stating: Property 'clientChatLoad' not found on object of type 'DataManager' when using the following implementation:
AppDelegate.m
#import "IOS_Project_Name-Swift.h"
#class DataManager;
...
DataManager.clientChatLoad(0){ data, error in
guard data != nil else {
print(error)
return
}
guard let data = data else { return }
let json = JSON(data: data)
if let result = json["success"].bool{
if (result){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) { () -> Void in
NSNotificationCenter.defaultCenter().postNotificationName("refreshChatDetails", object: nil, userInfo:nil)
}
}
}
DataManager.swift
...
#objc class func clientChatLoad(_ chatId: Int, completionHandler: #escaping (Data?, NSError?) -> ()) -> URLSessionTask {
// AJ TO-DO
var defaults = UserDefaults.standard
let URL = Foundation.URL(string: chatUrl)!
var request = URLRequest(url: URL, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 3.0)
request.httpMethod = "POST"
...
From what I understand adding #class DataManager to my Objective C class as well as #objc before class func clientChatLoad should expose the Swift code to my Objective C class... but I still receive an error. What might I have overlooked?
At first I thought your code was just some sort of misprint, but over the course of discussion in the comments I have come to realize that it is real and that you are attempting to put Swift code into an Objective-C file (namely AppDelegate.m). You cannot do that! An Objective-C file must be written entirely in Objective-C!
I would question whether you really even need a hybrid app (an app written in two languages). Your life will be much simpler if you pick just one language and write the whole app in that language. But if you are going to have a hybrid app, you need to learn Objective-C and write the Objective-C in Objective-C.
make sure your DataManager class inherits from NSObject. You should be good.

How to decipher "objc_method_description" from protocol method description list?

I have some Swift 3 code to decode an iOS Objective-C protocol (which has a Swift counterpart). After concluding Swift 3 reflection was not developed enough to accomplish what I needed, I stumbled on the objc runtime method protocol_copyMethodDescriptionList(), which returns an array of the following C structs:
struct objc_method_description
SEL name;
char *types;
};
The code fetches a list of protocol selector names, but not sure what's being returned in the type field. I'm confused about how to properly decode objc_method_description.type values.
What I'm getting in the the type fields are in a cryptic format, for example, "B48#0:8#16{_NSRange=QQ}24#40" At first I thought it was a problem in how I was converting C strings, but after some study, I suspect it actually is an encoding of the parameters, similar how Java's JVM passes around method signatures. But I still have no reference by which to decode it.
import UIKit
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var noteView : UITextView!
func decodeProto() {
var methodCount : UInt32 = 1
if let methodList = protocol_copyMethodDescriptionList(UITextViewDelegate.self,
false, true, UnsafeMutablePointer<UInt32>(&methodCount)) {
for i in 0..<Int(methodCount) {
let methodDesc = methodList[i];
let name = methodDesc.name
let types = String(validatingUTF8: methodDesc.types)
print("\(name) \(types)")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
decodeProto()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
The output of that in the XCode console is:
Optional(textViewDidBeginEditing:) Optional("v24#0:8#16")
Optional(textViewDidEndEditing:) Optional("v24#0:8#16")
Optional(textViewShouldBeginEditing:) Optional("B24#0:8#16")
Optional(textViewShouldEndEditing:) Optional("B24#0:8#16")
Optional(textView:shouldChangeTextInRange:replacementText:) Optional("B48#0:8#16{_NSRange=QQ}24#40")
Optional(textView:shouldChangeTextInRange:replacementText:) Optional("B48#0:8#16{_NSRange=QQ}24#40")
.
.
.
What is returned objc_method_description.type field?
A method signature encoding scheme
... which includes an obsolete stack offset representation
In other words, to get a usable method signature encoding from the type field, just just extract the alpha-symbolic characters in left-to-right order discarding the digits.
Supporting documentation:
Objective-C Method Signature Type Encoding
Meaning of the numeric elements in signature encoding, explained by an Apple developer
Playing around with Type Encodings
class_addmethod (according to #Richard_J_Ross, this is the root of it)
Objective-C Runtime Programming Guide
Objective-C Runtime Reference

Bridging from Objective C to Swift with PromiseKit

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]

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