/ SWIFT

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:

Swift Pointers Overview: Managed, Unsafe, Raw, Buffer, Mutable and Typed Pointers

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 to ManagedBuffer. 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 an NSArray whose contiguous storage is created and filled, upon first access, by bridging the elements of a Swift Array [6].
  • CVaListPointer – a Swift equivalent of an (Objective-) C va_list pointer. An example of CVaListPointer that you probably already know is NSString’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.

Vadim Bulavin

Creator of Yet Another Swift Blog. Coding for fun since 2008, for food since 2012.

Follow