yesthisisjoe yesthisisjoe - 5 months ago 121
Swift Question

Swift: Pass data to a closure that captures context

I am trying to call a function when the internet connection is restored and the

updateOnConnection
variable is true. Here is my code:

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

SCNetworkReachabilitySetCallback(reachability, { (_, flags, _) in
if flags.rawValue == 0 { //internet is not connected

} else { //internet became connected
if self.updateOnConnection {
self.refreshWallpaper()
}
}
}, &context)

SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes)
}


My problem is that the lines:

if self.updateOnConnection {
self.refreshWallpaper()
}


cause the error: "
A C function pointer cannot be formed from a closure that captures context
"

I am not sure how to check the state of
updateOnConnection
and call
refreshWallpaper()
in the closure that monitors changes in the internet connection. How can I fix this, or is there a totally different workaround I should be using?

Answer

Similar as in Swift - Proper use of CFNotificationCenterAddObserver w/callback, you have to convert self to a void pointer, store that in the context, and convert it back to an object pointer in the closure:

func checkForConnection() {

    let host = "reddit.com"
    var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
    context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque())

    let reachability = SCNetworkReachabilityCreateWithName(nil, host)!

    SCNetworkReachabilitySetCallback(reachability, { (_, flags, info) in
        if flags.rawValue == 0 { //internet is not connected

        } else { //internet became connected
            let mySelf = Unmanaged<ViewController>.fromOpaque(COpaquePointer(info)).takeUnretainedValue()

            if mySelf.updateOnConnection {
                mySelf.refreshWallpaper()
            }
        }
        }, &context)

    SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes)
}

See also How to cast self to UnsafeMutablePointer<Void> type in swift for more details about this mechanism.

Remark: if flags.rawValue == 0 can be expressed slightly more elegant as if flags.isEmpty, but what you actually should check is if flags.contains(.Reachable).


Update for Swift 3:

func checkForConnection() {

    let host = "reddit.com"
    var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
    context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque())

    let reachability = SCNetworkReachabilityCreateWithName(nil, host)!

    SCNetworkReachabilitySetCallback(reachability, { (_, flags, info) in
        if let info = info {
            if flags.rawValue == 0 { //internet is not connected

            } else { //internet became connected
                let mySelf = Unmanaged<ViewController>.fromOpaque(info).takeUnretainedValue()
                // ...
            }
        }
    }, &context)

    SCNetworkReachabilityScheduleWithRunLoop(reachability, CFRunLoopGetMain(), kCFRunLoopCommonModes)
}
Comments