20

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?

yesthisisjoe
  • 1,987
  • 2
  • 16
  • 32

1 Answers1

23

Similar as in How to use instance method as callback for function which takes only func or literal closure, 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 (Xcode 8 beta 6):

func checkForConnection() {

    let host = "reddit.com"
    var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
    context.info = UnsafeMutableRawPointer(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(), CFRunLoopMode.commonModes.rawValue)
}
Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I am getting this error for Swift 3 (in an Xcode 8 beta): "Value of type 'Unmanaged' has no member 'toOpaque'" in the line that is meant to assign to `context.info`. I.e. I cannot compile your code. Do you have an idea why this could be the case? – Drux Sep 01 '16 at 20:48
  • 1
    @Drux: There were some changes in the "unsafe pointer" APIs between the different Xcode 8 versions. I have now updated the code for the current Xcode 8 beta 6. – Martin R Sep 01 '16 at 20:58
  • Thx! Amazing what substantial changes Swift syntax still undergoes. – Drux Sep 02 '16 at 03:23
  • if you use `passRetained()` shouldn't you use `takeRetainedValue()` as well? wouldn't `takeUnretainedValue` retain it again inside the closure causing a leak? – BoteRock Sep 27 '16 at 18:40
  • @BoteRock: It seems that I was not careful enough when translating the code from Swift 2 to Swift 3, thanks for letting me know. – Martin R Sep 27 '16 at 18:42