Swift Unsafe Pointers
Swift 3 has several different pointer-like types that allow interfacing with the outside world. This article provides a concise overview of UnsafeRawPointer
, UnsafePointer
, AutoreleasingUnsafeMutablePointer
, Unmanaged
and OpaquePointer
.
Note that UnsafeRawPointer
and UnsafePointer
have mutable variants, namely UnsafeMutableRawPointer
and UnsafeMutablePointer
. I’ll refer to both variants using the shorter names and clarify as required.
In Swift 3, the pointer types now represent non-nullable pointers ( UnsafePointer
, UnsafeMutablePointer
, AutoreleasingUnsafeMutablePointer
, OpaquePointer
, Selector
and NSZone
). This means you don’t have to check whether they are nil
as nullability is expressed via the type system using optionals. This allows the preservation of nullability annotations when interacting with legacy APIs.
UnsafeRawPointer
UnsafeRawPointer
represents a untyped pointer ( void*
) and is declared as a struct
:
public struct UnsafeRawPointer : Strideable, Hashable {
// ... more code ...
public func distance(to x: UnsafeRawPointer) -> Int
public func advanced(by n: Int) -> UnsafeRawPointer
}
UnsafePointer
UnsafePointer
represents a typed pointer. It’s declared as a generic struct. Here’s the mutable variant:
public struct UnsafeMutablePointer : Strideable, Hashable {
// ... more code ...
public var pointee: Pointee { get nonmutating set }
public static func allocate(capacity count: Int) -> UnsafeMutablePointer
public func deallocate(capacity: Int)
public func initialize(to newValue: Pointee, count: Int = default)
public func deinitialize(count: Int = default) -> UnsafeMutableRawPointer
}
Memory State
The memory referenced by an UnsafeMutablePointer
can be in one of the following states:
- Deallocated.
- Allocated but not initialised.
- Allocated and initialised.
It’s only safe to use the pointee
property if and only if the memory is allocated and initialised. This means you have to call allocate()
, initialize()
, deinitialize()
and deallocate()
in the correct order.
// Allocate storage on the heap for UInt64 (8 bytes) and point to it
let uint64Pointer = UnsafeMutablePointer.allocate(capacity: 1)
// The memory state is undefined, so before using the pointee property,
// it must be initialised
uint64Pointer.initialize(to: 0xAABBCCDD)
// prints AABBCCDD
print("int value: " + String(format:"%X", uint64Pointer.pointee))
// Treat the pointer as a pointer to UInt8
uint64Pointer.withMemoryRebound(to: UInt8.self, capacity: 1) { (byte: UnsafeMutablePointer) in
// When executing on a little endian machine, it will print DD,CC,BB,AA
// On big endian, it will print AA,BB,CC,DD
print("first byte value: " + String(format:"%2X", byte.pointee))
print("second byte value: " + String(format:"%2X", byte.advanced(by: 1).pointee))
print("third byte value: " + String(format:"%2X", byte.advanced(by: 2).pointee))
print("fourth byte value: " + String(format:"%2X", byte.advanced(by: 3).pointee))
}
uint64Pointer.deinitialize(count: 1)
// `pointee` should not be used after being deinitialised
// `uint64Pointer` can now be re-initialised
uint64Pointer.deallocate(capacity: 1)
// `uint64Pointer` cannot be used anymore
Pointers to Reference Types
It’s important to realise that when UnsafePointer
is used with reference types, it does not in fact point to an object but rather, it points to a managed reference (which itself points to an object). Reference type lvalues represent managed pointers which perform reference counting, so the storage of let x: NSObject
translates to NSObject *x
. Consequently, UnsafeMutablePointer<NSObject>
can be thought of as pointing to a var x: NSObject
and assigning through pointee
is equivalent to assigning to var x
(i.e., referencing counting will be performed). Let’s illustrate with some code.
// Allocate storage for `var x: NSObject` (NSObject *x), the memory state is undefined
let objVarPointer = UnsafeMutablePointer.allocate(capacity: 1)
// Set the initial value to a new NSObject, equivalent to `var x = NSObject()`.
// Underneath, it's equivalent to the following pseudocode:
// x = obj
// retain(x)
objVarPointer.initialize(to: NSObject())
print("\(objVarPointer.pointee.description)")
// Assign another obj, equivalent to `x = NSObject()`.
// Underneath, it's equivalent to the following pseudocode:
// release(x)
// x = obj
// retain(x)
objVarPointer.pointee = NSObject()
print("\(objVarPointer.pointee.description)")
// It's crucial to de-initialise ref type pointers.
// Underneath, equivalent to:
// release(x)
objVarPointer.deinitialize(count: 1)
// Deallocate storage for the managed reference
objVarPointer.deallocate(capacity: 1)
AutoreleasingUnsafeMutablePointer
AutoreleasingUnsafeMutablePointer
is a typed pointer to references and it must point to storage that does not own the referenced object. By contrast, UnsafeMutablePointer
assumes the referenced storage owns a ref count to the referenced object.
Unmanaged
Unmanaged
represents an unmanaged reference to an instance of a particular type. It is declared as a generic struct restricted to reference types. Its job is to facilitative the conversion from unmanaged to managed references and vice versa.
public struct Unmanaged {
// ... more code ...
public static func passRetained(_ value: Instance) -> Unmanaged
public static func passUnretained(_ value: Instance) -> Unmanaged
public func takeUnretainedValue() -> Instance
public func takeRetainedValue() -> Instance
public static func fromOpaque(_ value: UnsafeRawPointer) -> Unmanaged
public func toOpaque() -> UnsafeMutableRawPointer
}
If you receive an unmanaged CFType
reference from an unannotated API, you can convert it to a managed one using the take
family of methods. Use takeUnretainedValue()
when you’re not responsible for releasing the instance (those usually come from Get
family of functions). Use takeRetainedValue()
when you’re responsible for releasing the instance (those usually come from Copy
or Create
family of functions).
To go in the other direction, use the take pass
family of methods. If there’s an unannotated CFType
API which expects to receive a +1 reference, you need to use passRetained()
. Note that the receiver of the unmanaged reference is responsible for balancing the +1, otherwise the object will leak. Most of time, you need to use passUnretained()
when interacting with APIs which expect a +0 reference.
Finally, fromOpaque()
and toOpaque()
allow you to convert between raw pointers (i.e., void*
pointers) and Unmanaged
references.
OpaquePointer
OpaquePointer
is used for C pointers that cannot be represented in Swift (e.g., pointers to incomplete struct types). There’s very little you can do with this type: it can be created, compared and debug logged.