Below is the sample code I use:
class Dummy {
let uuid = UUID()
}
func test() {
let dummy = Dummy()
let unmangedOpaquePointer = Unmanaged.passUnretained(dummy).toOpaque()
let fromWithUnsafeAPIPointer = withUnsafePointer(to: dummy, { UnsafeMutableRawPointer(mutating: $0) })
print(unmangedOpaquePointer == fromWithUnsafeAPIPointer) // false
let dummy1 = Unmanaged<Dummy>.fromOpaque(unmangedOpaquePointer).takeUnretainedValue()
let dummy2 = fromWithUnsafeAPIPointer.assumingMemoryBound(to: Dummy.self).pointee
let dummy3 = Unmanaged<Dummy>.fromOpaque(fromWithUnsafeAPIPointer).takeUnretainedValue()
let dummy4 = unmangedOpaquePointer.assumingMemoryBound(to: Dummy.self).pointee
print(dummy1 === dummy2) // true
print(dummy1 === dummy3) // EXC_BAD_ACCESS
print(dummy2 === dummy4) // EXC_BAD_ACCESS
}
test()
print(unmangedOpaquePointer == fromWithUnsafeAPIPointer) // false
The result of the above code snippet is "false". It's kind of expected. unmangedOpaquePointer and fromWithUnsafeAPIPointer are different instances of UnsafeMutableRawPointer
, so they are not equal.
However, they both point to the same object dummy here. How can I test this piece of fact, if unmangedOpaquePointer == fromWithUnsafeAPIPointer
is not the right way to achieve it?
Unmanaged
APIs and the withUnsafePointer
APIs?print(dummy1 === dummy2) // true
The print
call says dummy1 and dummy2 retrieved from the two pointers are identical (the same object instance). However dummy1 === dummy3
and dummy2 === dummy4
are not runtime valid expressions (cause they both crash).
So it seems the pointer returned by calling an Unmanaged
API should only be used with an Unmanaged
API to retrieve the value that the pointer points to. The same goes for withUnsafePointer
APIs. Why?
With the help of @Sweeper, I tested another version of the sample code:
class Dummy {
let uuid = UUID()
}
func test() {
var dummy = Dummy()
let unmangedOpaquePointer = Unmanaged.passUnretained(dummy).toOpaque()
withUnsafeMutablePointer(to: &dummy) {
let fromWithUnsafeAPIPointer = UnsafeMutableRawPointer($0)
print(unmangedOpaquePointer == fromWithUnsafeAPIPointer) // false
let dummy1 = Unmanaged<Dummy>.fromOpaque(unmangedOpaquePointer).takeUnretainedValue()
let dummy2 = fromWithUnsafeAPIPointer.assumingMemoryBound(to: Dummy.self).pointee
let dummy3 = Unmanaged<Dummy>.fromOpaque(fromWithUnsafeAPIPointer).takeUnretainedValue()
let dummy4 = unmangedOpaquePointer.assumingMemoryBound(to: Dummy.self).pointee
print(dummy1 === dummy2) // true
print(dummy1 === dummy3) // EXC_BAD_ACCESS
print(dummy2 === dummy4) // EXC_BAD_ACCESS
}
}
test()
This corrects the wrong way to use withUnsafePointer
APIs. But the result is the same.
Dummy
is a reference type. When you do var dummy = Dummy()
, the actual UUID in the class instance is stored somewhere else in memory (the heap), not in the dummy
variable, which is on the stack. dummy
only stores a reference to the class instance. As an analogy, the Swift type Dummy
would be similar to Dummy *
in C, where Dummy
is a C struct. Note that you cannot express the C struct Dummy
in Swift.
The pointer you get with withUnsafeMutablePointer
is of type UnsafeMutablePointer<Dummy>
. As we have established, Dummy
itself is just "a reference to a class instance on the heap", so a UnsafeMutablePointer<Dummy>
is a pointer to "a reference to a class instance on the heap". In C terms, this would be like a Dummy **
.
The raw pointer you get with Unmanaged.toOpaque
however, is just a pointer to the class instance - the same kind of thing that the dummy
variable stores. This is the fundamental difference.
This explains why the two raw pointers are not equal. They point to very different things.
Using fromOpaque
and takeUnretainedValue
effectively converts the raw pointer to a Dummy
. These are different Swift types, but they are really the same thing - a reference to the class instance. In the C analogy, this would look like casting a void *
to Dummy *
. In dummy3
, you are trying to cast a Dummy **
to a Dummy *
, which obviously goes very badly. Crucially, there is no dereferencing.
Using pointee
, however, is dereferencing. Again, using a C notation, you can dereference a Dummy **
to get the Dummy *
that it is pointing to (like you do in dummy2
). If you dereference a Dummy *
like in dummy4
, the value you get is the Dummy
struct (not expressible in Swift), not Dummy *
(Dummy
in Swift).
To summarise this in a table:
Swift | Analogous to C |
---|---|
Dummy |
Dummy * |
UnsafeMutablePointer<Dummy> |
Dummy ** |
UnsafeMutableRawPointer |
void * |
fromOpaque then takeUnretained |
Casting to Dummy * |
assumingMemoryBound(to: Dummy.self).pointee |
Casting to Dummy ** then dereference |
Here is some analogous C pseudocode to illustrate what's wrong with dummy3
and dummy4
.
Dummy *dummy = ...;
// dummy is a Dummy *, a pointer pointing to a Dummy
void *unmangedOpaquePointer = dummy;
// &dummy is a Dummy **, a pointer pointing to a Dummy *
void *fromWithUnsafeAPIPointer = &dummy;
// OK, unmangedOpaquePointer is indeed pointing to a Dummy
Dummy *dummy1 = (Dummy *)unmangedOpaquePointer;
// OK, fromWithUnsafeAPIPointer is a Dummy **, pointing to a Dummy* and we dereference get the Dummy * it is pointing to
Dummy *dummy2 = *(Dummy **)fromWithUnsafeAPIPointer;
// BAD! fromWithUnsafeAPIPointer is not a Dummy *
Dummy *dummy3 = (Dummy *)fromWithUnsafeAPIPointer;
// BAD! unmangedOpaquePointer is not a Dummy **
Dummy *dummy4 = *(Dummy **)unmangedOpaquePointer;