Isuru Isuru - 3 months ago 39
Swift Question

Working with C APIs from Swift

I'm trying to keep track of the network status. I went through the code of the FXReachability. Specifically the following method.

- (void)setHost:(NSString *)host
{
if (host != _host)
{
if (_reachability)
{
SCNetworkReachabilityUnscheduleFromRunLoop(_reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
CFRelease(_reachability);
}
_host = [host copy];
_status = FXReachabilityStatusUnknown;
_reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [_host UTF8String]);
SCNetworkReachabilityContext context = { 0, ( __bridge void *)self, NULL, NULL, NULL };
SCNetworkReachabilitySetCallback(_reachability, ONEReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop(_reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
}


What it does is it keeps checking the connection to the specified host. I'm trying to convert this method to Swift and I'm having a couple of problems.

1. Converting the host string to UTF8.

I have to convert the host string to UTF8 and pass it into the SCNetworkReachabilityCreateWithName method. I can't find the exact Swift equivalent of
UTF8String
property in Objective-C. But there is a property called
utf8
for the
String
type. According to the documentation,


You can access a UTF-8 representation of a String by iterating over its utf8 property. This property is of type String.UTF8View, which is a collection of unsigned 8-bit (UInt8) values, one for each byte in the string’s UTF-8 representation:


How can I get a complete UTF8 representation of a string instead of a collection of unsigned 8-bit (UInt8) values?




2. Callback function for SCNetworkReachabilitySetCallback.

Th second parameter of this function expects something of type
SCNetworkReachabilityCallBack
. I dived in to the source file and found out that it's actually a
typealias
.

typealias SCNetworkReachabilityCallBack = CFunctionPointer<((SCNetworkReachability!, SCNetworkReachabilityFlags, UnsafeMutablePointer<Void>) -> Void)>


I defined it like this.

let reachabilityCallBack: SCNetworkReachabilityCallBack = {

}()


But I get the error Missing return in a closure expected to return 'SCNetworkReachabilityCallBack' when in that typealias you can clearly see that it returns
Void
.

Is this the wrong way to do this?




These are the problems I'm facing. I've been at this for hours now with no luck so I'd really appreciate any help.

Thank you.

Answer

Your first problem can be solved with

let reachability = host.withCString {
    SCNetworkReachabilityCreateWithName(nil, $0).takeRetainedValue()
}

Inside the closure, $0 is a pointer to the NUL-terminated UTF-8 representation of the String.

Update: As Nate Cook said in a now deleted answer and also here, you can actually pass a Swift string to a function taking a UnsafePointer<UInt8> directly:

let reachability = SCNetworkReachabilityCreateWithName(nil, host).takeRetainedValue()

Unfortunately there is (as far as I know) currently no solution to your second problem. SCNetworkReachabilitySetCallback expects a pointer to a C function as second parameter, and there is currently no method to pass a Swift function or closure. Note that the documentation for SCNetworkReachabilityCallBack shows only Objective-C but no Swift.

See also Does Swift not work with function pointers?.


Update for Swift 2: It is now possible to pass a Swift closure to a C function taking a function pointer parameter. Also SCNetworkReachabilityCreateWithName() does not return an unmanaged object anymore:

let host = "google.com"
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
let reachability = SCNetworkReachabilityCreateWithName(nil, host)!

SCNetworkReachabilitySetCallback(reachability, { (_, flags, _) in
    print(flags)
}, &context) 

SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes)

Update for Swift 3 (Xcode 8 beta 6):

let host = "google.com"
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
let reachability = SCNetworkReachabilityCreateWithName(nil, host)!

SCNetworkReachabilitySetCallback(reachability, { (_, flags, _) in
    print(flags)
    }, &context)

SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(),
                                         CFRunLoopMode.commonModes.rawValue)
Comments