Jul 28, 2014
Interacting with C Pointers
Objective-C and C APIs often require the use of pointers. Data types in Swift are designed to feel natural when working with pointer-based Cocoa APIs, and Swift automatically handles several of the most common use cases for pointers as arguments. In this post we’ll look at how pointer parameters in C can be used with the variables, arrays, and strings in Swift.
Pointers as In/Out Parameters
C and Objective-C don’t support multiple return values, so Cocoa APIs frequently use pointers as a way of passing additional data in and out of functions. Swift allows pointer parameters to be treated like inout parameters, so you can pass a reference to a var as a pointer argument by using the same & syntax. For instance, UIColor’s getRed(_:green:blue:alpha:) method takes four CGFloat* pointers to receive the components of the color. We can use & to collect these components into local variables:
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
color.getRed(&r, green: &g, blue: &b, alpha: &a)
Another common case is the Cocoa NSError idiom. Many methods take an NSError** parameter to save an error in case of failure. For instance, we can list the contents of a directory using NSFileManager’s contentsOfDirectoryAtPath(_:error:) method, saving the potential error directly to an NSError? variable:
var maybeError: NSError?
if let contents = NSFileManager.defaultManager()
.contentsOfDirectoryAtPath("/usr/bin", error: &maybeError) {
// Work with the directory contents
} else if let error = maybeError {
// Handle the error
}
For safety, Swift requires the variables to be initialized before being passed with &. This is because it cannot know whether the method being called tries to read from a pointer before writing to it.
Pointers as Array Parameters
Pointers are deeply intertwined with arrays in C, and Swift facilitates working with array-based C APIs by allowing Array to be used as a pointer argument. An immutable array value can be passed directly as a const pointer, and a mutable array can be passed as a non-const pointer argument using the & operator, just like an inout parameter. For instance, we can add two arrays a and b using the vDSP_vadd function from the Accelerate framework, writing the result to a third result array:
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]
Pointers as String Parameters
C uses const char* pointers as the primary way to pass around strings. A Swift String can be used as a const char* pointer, which will pass the function a pointer to a null-terminated, UTF-8-encoded representation of the string. For instance, we can pass strings directly to standard C and POSIX library functions:
puts("Hello from libc")
let fd = open("/tmp/scratch.txt", O_WRONLY|O_CREAT, 0o666)
if fd < 0 {
perror("could not open /tmp/scratch.txt")
} else {
let text = "Hello World"
write(fd, text, strlen(text))
close(fd)
}
Safety with Pointer Argument Conversions
Swift works hard to make interaction with C pointers convenient, because of their pervasiveness within Cocoa, while providing some level of safety. However, interaction with C pointers is inherently unsafe compared to your other Swift code, so care must be taken. In particular:
- These conversions cannot safely be used if the callee saves the pointer value for use after it returns. The pointer that results from these conversions is only guaranteed to be valid for the duration of a call. Even if you pass the same variable, array, or string as multiple pointer arguments, you could receive a different pointer each time. An exception to this is global or static stored variables. You can safely use the address of a global variable as a persistent unique pointer value, e.g.: as a KVO context parameter.
- Bounds checking is not enforced when a pointer to an Array or String is passed. A C-based API can’t grow the array or string, so you must ensure that the array or string is of the correct size before passing it over to the C-based API.
If you need to work with pointer-based APIs that don’t follow these guidelines, or you need to override Cocoa methods that accept pointer parameters, then you can work directly with raw memory in Swift using unsafe pointers. We’ll look at a more advanced case in a future post.