SoFunction
Updated on 2025-04-06

Detailed introduction to pointer operation and usage in Swift

Apple expects that pointers can minimize the chance of appearing in Swift, so in Swift, pointers are mapped as a generic type and are also relatively abstract. This has caused difficulties in using pointers in Swift to a certain extent, especially for developers who are not familiar with pointers and do not have much experience in pointers (including myself). Using pointers in Swift is indeed a challenge. In this article, I hope to start with the most basic use and summarize some common ways and scenarios of using pointers in Swift. This article assumes that you at least know what a pointer is. If you don't know much about the concept of the pointer itself, you can take a look at this article first.Five-minute C pointer tutorial(or its Chinese version), it should be very helpful.

Preliminary

In Swift, pointers are represented by a special type, that is UnsafePointer<T>. Following Cocoa's consistent immutability principle, UnsafePointer<T> is also immutable. Of course, it also has a variable variant, UnsafeMutablePointer<T>. Most of the time, pointers in C are introduced into Swift in these two types: the const modified pointer in C is for UnsafePointer (the most common one should be the const char * of the C string), while other variable pointers correspond to UnsafeMutablePointer. In addition, there is an UnsafeBufferPointer<T> in Swift that represents a set of continuous data pointers, an opaque pointer that represents an incomplete structure, and so on. In addition, you may have noticed that you can determine that the pointer types to the content are all generic structs. We can use this generic to constrain the type pointed to by the pointer to provide some security.

For an UnsafePointer<T> type, we can use the memory property to value it. If the pointer is a mutable UnsafeMutablePointer<T> type, we can also use memory to assign it. For example, if we want to write a counter that uses pointers to directly operate memory, we can do this:

Copy the codeThe code is as follows:

func incrementor(ptr: UnsafeMutablePointer<Int>) { 
    += 1 

var a = 10 
incrementor(&a) 
a  // 11 

This is similar to the pointer in C. We can pass the pointer to this variable by preceding the variable name to the method that accepts the pointer as a parameter. In the incrementor above, we changed the content pointed to by directly manipulating the memory attribute.

Similar to this practice is to use Swift's inout keyword. When we pass variables into functions with inout parameters, we also use the & symbol to represent the address. However, the difference is that inside the function body we do not need to deal with pointer types, but can operate the parameters directly.

Copy the codeThe code is as follows:

func incrementor1(inout num: Int) { 
    num += 1 

var b = 10 
incrementor1(&b) 
b  // 11 

Although the meaning of & when passing the parameter is the same as in C, it is a "address of a variable", in Swift we cannot directly obtain an instance of UnsafePointer through this symbol. It is important to note that this is different from C:

Copy the codeThe code is as follows:

// Cannot compile
let a = 100 
let b = &a 

Pointer initialization and memory management

In Swift, we cannot directly retrieve the address of the existing object. We can still create a new UnsafeMutablePointer object. Unlike the automatic memory management of other objects in Swift, we need to manually apply and release memory for pointers. There are three possible states for memory of an UnsafeMutablePointer:

1. Memory is not allocated, which means this is a null pointer, or has been released before;
2. Memory has been allocated, but the value has not been initialized yet;
3. Memory has been allocated and the value has been initialized.

Among them, only pointers in the third state can be guaranteed to be used normally. The initialization method (init) of UnsafeMutablePointer is done by converting from other types to UnsafeMutablePointer. If we want to create a new pointer, what we need to do is to use the alloc: method. This method accepts a num: Int as a parameter, and will apply to the system for the corresponding generic type memory of the number of nums. The following code applies for an Int-sized memory and returns a pointer to this memory:

Copy the codeThe code is as follows:

var intPtr = UnsafeMutablePointer<Int>.alloc(1) 
// "UnsafeMutablePointer(0x7FD3A8E00060)" 

What should be done next is to initialize the content of this pointer. We can use the initialize: method to complete the initialization:
Copy the codeThe code is as follows:

(10) 
// is 10

After the initialization is completed, we can use memory to manipulate the memory value pointed to by the pointer.

After use, it is best to release the pointer pointing to and the pointer itself as soon as possible. The destroy paired with initialize: is used to destroy the object pointed to by the pointer, and the dealloc: corresponding to alloc: is used to free the memory requested previously. They should all be used paired:

Copy the codeThe code is as follows:

() 
(1) 
intPtr = nil 

Note: In fact, destroy is not necessary for "trivial values" like Int that are mapped to int in C, because these values ​​are allocated on the constant segment. However, for objects or structure instances of classes, if initialization and destruction pairing are not guaranteed, memory leaks will occur. So without special considerations, no matter what is in memory, it will be a good habit to pair initialize: with destroy.

Pointer to an array

Swift has helped us complete the conversion when passing an array as a parameter to the C API in Swift, which is a great example in Apple's official blog:

Copy the codeThe code is as follows:

import Accelerate 
let a: [Float] = [1, 2, 3, 4] 
let b: [Float] = [0.5, 0.25, 0.125, 0.0625] 
var result: [Float] = [0, 0, 0, 0] 
vDSP_vadd(a, 1, b, 1, &result, 1, 4) 
// result now contains [1.5, 2.25, 3.125, 4.0625] 

For a general C API that accepts const arrays, the required type is UnsafePointer, while an array that does not const corresponds to UnsafeMutablePointer. When using it, for the const parameter, we directly pass the Swift array into (a and b in the above example); and for mutable array, just add & before it and pass it in (result in the above example).

Swift has been simplified for parameter transfer and is very convenient to use. However, if we want to use pointers to directly manipulate arrays like before, we need to use a special type: UnsafeMutableBufferPointer.

Buffer Pointer is a continuous memory pointer, usually used to express collection types like arrays or dictionaries.

Copy the codeThe code is as follows:

var array = [1, 2, 3, 4, 5] 
var arrayPtr = UnsafeMutableBufferPointer<Int>(start: &array, count: ) 
// baseAddress is the pointer to the first element
var basePtr = as UnsafeMutablePointer<Int> 
// 1 
= 10 
// 10 
//Next element
var nextPtr = () 
// 2 

Pointer operation and conversion

withUnsafePointer

As we said above, in Swift, you cannot use the & symbol to directly obtain the address to operate directly. If we want to pointer a variable, we can use the helper method withUnsafePointer. This method accepts two parameters, the first is any type of inout, and the second is a closure. Swift will convert the first input into a pointer, and then use the converted Unsafe pointer as a parameter to call the closure. It looks like this to use:

Copy the codeThe code is as follows:

var test = 10 
test = withUnsafeMutablePointer(&test, { (ptr: UnsafeMutablePointer<Int>) -> Int in 
    += 1 
    return  
}) 
test // 11 

Here we actually did the same thing as the incrementor at the beginning of the article, the difference is that we do not need to convert the value into a pointer through the method call. The benefits of doing this are obvious to pointers that can only be executed once, and the intention of "we just want to do something with this pointer" can be expressed more clearly.

unsafeBitCast

unsafeBitCast is a very dangerous operation, which forces bitwise conversion of memory pointed to by a pointer to the target type. Because this conversion is done outside of Swift's type management, the compiler cannot ensure that the type obtained is indeed correct, and you have to know exactly what you are doing. for example:

Copy the codeThe code is as follows:

let arr = NSArray(object: "meow") 
let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), ) 
str // “meow” 

Because NSArray can store any NSObject object, when we use CFArrayGetValueAtIndex to get the value from it, the result will be an UnsafePointer<Void>. Since we understand that what is stored in it is a String object, it can be cast directly to CFString.

A more common use scenario about unsafeBitCast is to convert between different types of pointers. Because the size occupied by the pointer itself is certain, there will be no fatal problems when converting the pointer type. This can be common when collaborating with some C APIs. For example, many C APIs require input void *, corresponding to UnsafePointer<Void> in Swift. We can convert any pointer to UnsafePointer in the following way.

Copy the codeThe code is as follows:

var count = 100 
var voidPtr = withUnsafePointer(&count, { (a: UnsafePointer<Int>) -> UnsafePointer<Void> in 
    return unsafeBitCast(a, UnsafePointer<Void>.self) 
}) 
// voidPtr is UnsafePointer<Void>. Equivalent to void in C *
// Convert back to UnsafePointer<Int>
var intPtr = unsafeBitCast(voidPtr, UnsafePointer<Int>.self) 
//100 

Summarize

Swift is designed to take security as an important principle. Although it may be a bit long-winded, it is still necessary to reiterate that directly using and operating pointers in Swift should be used as a last resort, and they will never ensure safety. Migrating from traditional C code and Objective-C code that works seamlessly with it is not a small project, and our code base will surely have some places to collaborate with C from time to time. We certainly have the option to rewrite some of the stale code using Swift, but for parts like security or performance critical, we may have no choice but to continue using the C API. If we want to continue using those APIs, it will be helpful to know some basic Swift pointer operations and usage.

For new code, try to avoid using types starting with Unsafe, which means that many unnecessary troubles can be avoided. The biggest benefit Swift brings to developers is that it allows us to use more advanced programming ideas to carry out faster and more focused development. Only by respecting this idea can we better enjoy the advantages brought by this new language. Obviously, this idea does not include using UnsafePointer everywhere.