Swift 2 - UnsafeMutablePointer<Void> to object
Asked Answered
C

2

29

If I have a method like:

func someMethod(contextPtr: UnsafeMutablePointer<Void>)

how do I get the object from the contextPtr?

func someMethod(contextPtr: UnsafeMutablePointer<Void>){    
    let object:MyObject = contextPtr.memory
}

gives:

'Void' is not convertible to 'MyObject'

What's the secret sauce


More detail:

What I'm actually doing here is setting up a global callback function for SCNetworkReachability:

func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutablePointer<Void>) {

    let r:Reachability = info.memory
}

and then adding the callback as follows:

var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
var s = self
withUnsafeMutablePointer(&s) {
    context.info = UnsafeMutablePointer($0)
}
SCNetworkReachabilitySetCallback(reachability, callback, &context)
Courland answered 11/6, 2015 at 16:54 Comment(2)
What do you pass when you you call the method? And why is it passed as void pointer?Puglia
I was trying to simplify the code a little - I've added some more detailCourland
P
45

This should work: pass the object pointer as an opaque unmanaged pointer to the callback:

context.info = UnsafeMutablePointer(Unmanaged.passUnretained(myObject).toOpaque())
SCNetworkReachabilitySetCallback(reachability, callback, &context) 

and retrieve in the callback via:

func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutablePointer<Void>) {

    let myObject = Unmanaged<MyObject>.fromOpaque(COpaquePointer(info)).takeUnretainedValue()

}

Of course this assumes that some strong reference to the object exists as long as the callback is installed, so that the object is not deallocated.

Update: Note that both conversions from object pointer to void pointer and back can be simplified if you are willing to use "unsafe" functions:

context.info = unsafeAddressOf(myObject)
// ...
myObject = unsafeBitCast(info, MyObject.self)

The generated assembly code is – as far as I can see – identical.

Update 2: See also How to cast self to UnsafeMutablePointer<Void> type in swift for more information about the "bridging" and some helper functions which can be used here.


Swift 3 update (Xcode 8 beta 6):

var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())

// ...

func callback(reachability:SCNetworkReachability, flags: SCNetworkReachabilityFlags, info: UnsafeMutableRawPointer?) {
    if let info = info {
        let myObject = Unmanaged<MyObject>.fromOpaque(info).takeUnretainedValue()
        // ...
    }
}
Puglia answered 11/6, 2015 at 18:1 Comment(14)
@AshleyMills: Nice to see that you could use it in your Reachability project!Puglia
That was the final key to the puzzle!Courland
just noticed your update. Nice idea, but unfortunately this doesn't work for the problem I was trying to solve as unsafeAddressOf requires AnyObject and SCNetworkReachabilityContext is a structCourland
@AshleyMills: But the first solution doesn't work for struct either! And in your current Reachability project, at context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque()), self is an instance of class Reachability, not a SCNetworkReachabilityContext.Puglia
@AshleyMills: Here #33295120 some helper functions are defined to make the bridging easier.Puglia
Brain fart… Please ignore my previous message!Courland
Nice solution. I'm surprised how long it took me to find this, though. Given the reliance of certain low-level API's on unsafe pointers you'd think that a lot of people would need to know this!Argyres
the unsafeAddressOf and unsafeBitCast are the most useful yet. Due to a world of C that doesn't articulate these names as such, it's pretty much still hard to mentally shift to where we have to go to interact with C from Swift. :(Deliladelilah
Thanks for the Swift 3 update, but I still have a problem with trying to run your code in Swift 3. The line where you set context.info, gives the error Value of type 'Unmanaged<MyObject>' has no member 'toOpaque'. I have been trying to fix this for a while and found nothing. Any idea what to do?Lully
@yesthisisjoe: It seems that I updated the code using a Swift 3 preview release from swift.org. It does now compile with Xcode 8, can you check it again? Thanks for your feedback!Puglia
@yesthisisjoe: And the syntax changed again with Xcode 8 beta 2 (to what I originally had written :)Puglia
I will try this in the morning. Thanks for all the updates!Lully
In the Swift 3 version, why use passRetained with takeUnretainedValue? Should be passUnretained, right?Wind
Oh THANK YOU. I've been searching for an answer for this for days, and have performed so much ritual sacrifice that the neighbours are worrying about me. And damn you Apple, for such a terrible API. If it wasn't for the contributors of SO, I'd have given up on iOS dev a long time ago.Stevenstevena
H
-2
struct S {
    var i: Int = 10
}
var first = S()

func foo(contextPtr: UnsafeMutablePointer<Void>){
    let pS = UnsafeMutablePointer<S>(contextPtr)
    pS.memory.i = 100
}
print(first.i) // 10
foo(&first)
print(first.i) // 100

if we need pass as UnsafeMutablePointer self to async function

import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

import Foundation
// can be struct, class ...
class C {
    let queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT)
    var s: String = ""
    func foo() {
        var c = self
        dispatch_async(queue) { () -> Void in
            f(&c)
        }

    }
}

func f(pV: UnsafeMutablePointer<Void>) {
    let pC = UnsafeMutablePointer<C>(pV)
    sleep(1)
    print(pC.memory.s)
}

var c1: C? = C()
c1!.s = "C1"
c1!.foo()        // C1
var c2: C? = C()
c2!.s = "C2"
c2!.foo()        // C2
c1 = nil
c2 = nil
print("test")
Housecarl answered 11/11, 2015 at 9:50 Comment(1)
Your sample code is different than the question, though, because C.foo() calls f() synchronously, so c stays allocated. In the original question, f() is an asynchronous callback function, and so c would be released before f() is called.Glyn

© 2022 - 2024 — McMap. All rights reserved.