Swift Pointers Overview: Unsafe, Buffer, Raw and Managed Pointers
Pointers are one of the most complex features in the Swift language. First, the concept of pointers is hard by itself. Second, using them incorrectly could lead to memory leaks and undefined behavior. Third, Swift exposes pointers through a set of thirteen types, which may seem overwhelming. The goal of this article is to put Swift pointers in a systematic way: what they are, when to use, and what you can get from them.
What is Memory
Memory is a long list of bytes. Every byte has a number, called memory address, and can hold a content. Using memory addresses, we can store things in memory and then read them back.
The amount of memory a value occupies depends on its data type and the Swift compiler implementation. For example, here is how a memory address space can look like if we store a Bool
value:
It is up to the programmer to interpret the content of the memory since it is not associated with any concrete type. In our example, to get back the value from the memory, we need to read out the byte 0
, then resolve the type to Bool
.
Here you can learn about advanced iOS memory management and objects life cycle.
Pointers Overview
A pointer is a variable that stores the memory address of an object.
Swift pointers can be broken down into the following types:
- A buffer type provides a collection interface to a group of elements stored contiguously in memory so that you can treat them like an array. Conversely, non-buffer type points to a single element.
- A mutable type allows us to mutate the memory referenced by that pointer. Conversely, an immutable type provides read-only access to the referenced memory.
- A raw type contains uninitialized and untyped data. Raw pointers must be bound to a certain type and value before we can use them. They can also be reinterpreted to several different types. Conversely, typed pointers have a generic parameter, which is the type of the value being pointed to.
- An unsafe type does not have some of Swift’s safety features, such as bounds check, automatic memory management. It is possible to violate memory, access unallocated memory, or interpret memory as a wrong type by means of unsafe pointers.
- A managed type has automatic memory management. Conversely, an unmanaged type makes you partially responsible for the object’s lifecycle.
Swift Unsafe Pointers
For most of the time, when we need to use a pointer, we are interested in the unsafe kind. In Swift, unsafe pointers are exposed via nine types:
Immutable | Mutable |
---|---|
UnsafePointer<T> |
UnsafeMutablePointer<T> |
UnsafeBufferPointer<T> |
UnsafeMutableBufferPointer<T> |
UnsafeRawPointer |
UnsafeMutableRawBufferPointer |
UnsafeRawBufferPointer |
UnsafeRawBufferPointer |
There is also AutoreleasingUnsafeMutablePointer<T>
that is used only for Objective-C interoperability. It corresponds to an Objective-C pointer T
__autoreleasing *
, where T
is an Objective-C pointer type [1].
Unsafe pointers have an associated type Pointee
which represents the type of data being pointed at. Typed unsafe pointers are generic over their Pointee
type. Raw unsafe pointers have their Pointee
fixed to UInt8
, representing a byte of memory.
Under the hood, every unsafe pointer implements the _Pointer
protocol. Its goal is to provide an abstraction on top of Builtin.RawPointer
, which is essentially raw memory (void*
in C).
Typically, we use unsafe pointers in the following scenarios:
- Interacting with C APIs, e.g., Core Foundation.
- Performance optimizations.
Using Raw Pointers
With UnsafeRawPointer
, we can access untyped memory content. It can be useful when we don’t know what kind of data we point to. Say, when initializing an NSData
object from a given buffer [2]:
public init(bytes: UnsafeRawPointer?, length: Int) {
super.init()
_init(bytes: UnsafeMutableRawPointer(mutating: bytes), length: length, copy: true)
}
Additionally, raw pointers allow type punning and provide special APIs to do it correctly and safely [3].
Using Typed and Buffer Pointers
When we know Pointee
ahead of time, we can use typed pointers:
let oneTwo = UnsafeMutablePointer<Int>.allocate(capacity: 2)
oneTwo.initialize(repeating: 1, count: 2)
print("First memory address:", oneTwo) // First memory address: 0x000000010045deb0
print("First value:", oneTwo.pointee) // First value: 1
oneTwo[1] = 2 // Subscript
print("Second memory address:", oneTwo + 1) // Second memory address: 0x000000010045deb8
print("Second value:", oneTwo[1]) // Second value: 2
Note that the integers are placed contiguously in memory. Therefore, we can interpret them as an array of two elements using UnsafeBufferPointer
:
let buffer = UnsafeBufferPointer(start: oneTwo, count: 2)
for (index, element) in buffer.enumerated() {
print("\(index): \(element)")
}
Lastly, free the pointer from memory:
oneTwo.deinitialize(count: 2)
oneTwo.deallocate()
Opaque and Managed Swift Pointers
Apart from the family of unsafe types, Swift has four more pointers:
OpaquePointer
– a pointer for which we do not know the type of data it points to. It can be that the type of pointee is determined by the value of some other variable or cannot be represented in Swift [4].Unmanaged
– a manual-memory-managed pointer.ManagedBufferPointer
– a pointer toManagedBuffer
. The latter is used to building custom buffer-backed collections. It consists of a header value, where we can store a number of elements in the buffer, and a contiguous raw memory for the elements.ManagedBuffer
is used internally inside the Swift standard library. It backs__BridgingHashBuffer
, which is a minimal hash table storage [5], and__SwiftDeferredNSArray
, which is anNSArray
whose contiguous storage is created and filled, upon first access, by bridging the elements of a SwiftArray
[6].CVaListPointer
– a Swift equivalent of an (Objective-) Cva_list
pointer. An example ofCVaListPointer
that you probably already know isNSString
’s convenience initialize.
The Unmanaged
pointer is used in two primary cases. First and foremost, on the boundary of C and Swift APIs to bypass automatic reference counting, that is otherwise enforced to every Swift object. In such a case, an unmanaged pointer is used to wrap a Swift object reference into an opaque pointer, or convert an opaque pointer back:
class Foo {
let x: Int
init(_ x: Int) { self.x = x }
deinit { print("Deinit \(x)") }
}
do {
let unmanaged = Unmanaged.passRetained(Foo(0))
unmanaged.release() // Deinit 0
}
do {
_ = Unmanaged.passUnretained(Foo(1)) // Deinit 1
}
do {
let opaque = Unmanaged.passRetained(Foo(2)).toOpaque()
Unmanaged<Foo>.fromOpaque(opaque).release() // Deinit 2
}
Second, to avoid reference counting overhead using fine-grained memory management. One example of the latter is Operation
and OperationQueue
from Swift Core Foundation:
open class Operation : NSObject {
...
internal var __previousOperation: Unmanaged<Operation>?
internal var __nextOperation: Unmanaged<Operation>?
internal var __nextPriorityOperation: Unmanaged<Operation>?
internal var __queue: Unmanaged<OperationQueue>?
...
}
Opaque pointers are used when working with incomplete C data structures which cannot be represented in Swift [7]. That is, data structures which are not fully defined in .h
files, and are implemented privately. For example, this is the case for the Accelerate framework [8]:
public static func destroySetup(_ setup: OpaquePointer) {
vDSP_destroy_fftsetupD(setup)
}
Summary
Swift exposes pointers via the family of thirteen types. Typically, we are interested in the unsafe pointers group, which allows us to access memory as instances of a specific type. Here is the list of them:
UnsafePointer<T>
UnsafeMutablePointer<T>
UnsafeBufferPointer<T>
UnsafeMutableBufferPointer<T>
UnsafeRawPointer
UnsafeMutableRawBufferPointer
UnsafeRawBufferPointer
UnsafeRawBufferPointer
There is also a group pointers, designed for C interoperability:
Unmanaged
OpaquePointer
AutoreleasingUnsafeMutablePointer
CVaListPointer
Lastly, there is ManagedBufferPointer
, which is useful for creating custom buffer-based collections.
Further Reading
Thanks for reading!
If you enjoyed this post, be sure to follow me on Twitter to keep up with the new content. There I write daily on iOS development, programming, and Swift.