Working with C APIs from Swift
Asked Answered
O

1

4

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.

Outwardbound answered 26/11, 2014 at 5:55 Comment(0)
K
12

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

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)
Knighterrant answered 26/11, 2014 at 6:29 Comment(4)
Thanks Martin. Limitations suck :( One small thing. Now the type of reachability is Unmanaged<SCNetworkReachability>!. I get the error 'Unmanaged<SCNetworkReachability>' is not identical to 'SCNetworkReachability' when passing it to other functions. Is there a way to resolve this?Outwardbound
@Isuru: I forgot the takeRetainedValue(). Should be OK now.Knighterrant
In the Swift 3 code, what return or output tells you that the app was or wasn't able to contact the specified host?Stercoricolous
@ConfusionTowers: That is not what the Reachability API is about. Compare developer.apple.com/reference/systemconfiguration/…: "A remote host is considered reachable when a data packet, sent by an application into the network stack, can leave the local device. Reachability does not guarantee that the data packet will actually be received by the host." – Only if you create a TCP connection you'll see if the host/port exists and can be contacted.Knighterrant

© 2022 - 2024 — McMap. All rights reserved.