Swift’s type system is designed to make our lives easier by enforcing strict rules around what we can and cannot do in our code. This is undoubtedly a great thing and it encourages programmers to write better, more correct code. However, it can seem extremely prohibitive when interacting with legacy code bases, particularly C-based libraries. It is a reality that many C libraries abuse types in such a way that does not play nicely with the Swift compiler. It’s true that the Swift team at Apple has gone out of their way to support some of the more basic features of C, such as C strings, but there are still many issues when working with a legacy C library in Swift. Here’s how to deal with them. Before we begin, I would like to note that many of the operations in this article are inherently unsafe as they bypass the Swift compiler’s type system altogether. I encourage you to read carefully and refrain from copy and pasting code from this article. This is not Stack Overflow, these snippets have a real chance of corrupting memory, causing memory leaks, or simply crashing your application if used improperly.
The Basics
Most of the time, C pointers are imported into Swift in one of two ways:UnsafePointer<T>
or
UnsafeMutablePointer<T>
Where T is the equivalent Swift type for the original C type. Pointers declared const in C are imported as UnsafePointer
and pointers that are not declared const are imported as UnsafeMutablePointer
.
Here are a few examples
C:
void myFunction(const int *myConstIntPointer);
Swift:
func myFunction(myConstIntPointer: UnsafePointer<Int32>)
C:
void myOtherFunction(unsigned int *myUnsignedIntPointer);
Swift:
func myOtherFunction(myUnsignedIntPointer: UnsafeMutablePointer<UInt32>)
C:
void iTakeAVoidPointer(void *aVoidPointer);
Swift:
func iTakeAVoidPointer(aVoidPointer: UnsafeMutablePointer<Void>)
If the type of the pointer isn’t known to Swift, for example because it is a forward declaration, it is represented using a COpaquePointer
.
C:
struct SomeThing;
void iTakeAnOpaquePointer(struct SomeThing *someThing);
Swift:
func iTakeAnOpaquePointer(someThing: COpaquePointer)
Passing Pointers to Swift Objects
In many cases, this is as simple as using theinout
operator, which is the same as the familiar address-of
operator in C, the ampersand.
Swift:
let myInt: = 42
myFunction(&myInt)
var myUnsignedInt: UInt = 7
myOtherFunction(&myUnsignedInt)
There are two very important, but subtle details here.
- When using the
inout
operator, variables declared withvar
and constants declared withlet
are transformed intoUnsafePoiner
andUnsafeMutablePointer
respectively. This is very easy to miss if you do not pay close attention to the original type in the code. Trying to pass anUnsafePointer
where anUnsafeMutablePointer
is expected results in a seemingly cryptic compiler error so be wary. - This operator only works in the context of passing Swift values and references as function arguments that expect an
UnsafePointer
orUnsafeMutablePointer
. You cannot get the pointer in any other context. For example, this is invalid and will result in a compiler error:
From time to time, you will need to interoperate with an API that takes or returns a void pointer in place of an explicit type. This is unfortunately common in C, where there isn’t a way to specify a generic type.Swift: let x = 42 let y = &x
If you know the expected type taken by the function, you can coerce an object into a void pointer usingC: void takesAnObject(void *theObject);
withUnsafePointer
andunsafeBitCast
. For example, let’s saytakesAnObject
is actually expecting a pointer to anint
.
Let’s break this down. First, we make a call tovar test = 42 withUnsafePointer(&test, { (ptr: UnsafePointer<Int>) -> Void in var voidPtr: UnsafePointer<Void> = unsafeBitCast(ptr, UnsafePointer<Void>.self) takesAnObject(voidPtr) })
withUnsafeMutablePointer
. This generic function takes two arguments. The first is aninout
of typeT
, and the second is a closure of type(UnsafePointer) -> ResultType
. This function calls the closure by taking a pointer to the first argument of the function, and passing it as the sole argument of the closure. The function then returns the result of the closure. In the example above, the closure is typed to returnVoid
, and therefore doesn’t return anything. We could just as easily do something like this:
Note: Should you need to modify the pointer itself, there is alet ret = withUnsafePointer(&test, { (ptr: UnsafePointer<Int>) -> Int32 in var voidPtr: UnsafePointer<Void> = unsafeBitCast(ptr, UnsafePointer<Void>.self) return takesAnObjectAndReturnsAnInt(voidPtr) }) println(ret)
withUnsafeMutablePointer
variant. For convenience, Swift also has variants that pass two pointers:var x: Int = 7 var y: Double = 4 withUnsafePointers(&x, &y, { (ptr1: UnsafePointer<Int>, ptr2: UnsafePointer<Double>) -> Void in var voidPtr1: UnsafePointer<Void> = unsafeBitCast(ptr1, UnsafePointer<Void>.self) var voidPtr2: UnsafePointer<Void> = unsafeBitCast(ptr2, UnsafePointer<Void>.self) takesTwoPointers(voidPtr1, voidPtr2) })
About unsafeBitCast
unsafeBitCast
is an extremely dangerous operation. The documentation describes it as a “brutal bit-cast of something to anything of the same size.” The reason we are able to use it safely above is because we’re simply casting between pointers of different types, and all pointers are the same size on any given platform. This is why we must callwithUnsafePointer
to obtain a typedUnsafePointer
first before casting that to anUnsafePointer
as the C API is defined. This can be confusing at first, especially when working with a type that is the same size as a pointer, such as anInt
in Swift (on all currently available platforms anyway, where the size of a pointer is 1Word
, and 1 Word is the size of anInt
). It is easy to make a mistake like this:
With the intention of obtaining a pointer to x. This is incredibly misleading because it will compile and run, but lead to unexpected errors because instead of a pointer to x, the C APIs will receive a pointer with location 0x7, or garbage. Becausevar x: Int = 7 let xPtr = unsafeBitCast(x, UnsafePointer<Void>.self)
unsafeBitCast
requires that the size of the types be equal, it is less insidious when attempting to cast something other than anInt
, such asInt8
, or a one byte integer.
This will simply causevar x: Int8 = 7 let xPtr = unsafeBitCast(x, UnsafePointer<Void>.self)
unsafeBitCast
to throw an exception and crash your program!Interacting With C Structs
Let’s tackle this one with a concrete example. You want to retrieve information about the system your computer is running on. There’s a C API for that,uname(2)
, which takes a pointer to a structure and fills out the information in the supplied object with the systems information, such as OS name and version or its hardware identifier. There’s a catch though, the struct is imported into Swift as:
Oh no! Swift imports C array literals as tuples! On top of that, the default initializer requires values for each and every field. So if you were to do this the normal Swift way, it would be like this:struct utsname { var sysname: (Int8, Int8, ...253 times..., Int8) var nodename: (Int8, Int8, ...253 times..., Int8) var release: (Int8, Int8, ...253 times..., Int8) var version: (Int8, Int8, ...253 times..., Int8) var machine: (Int8, Int8, ...253 times..., Int8) }
That’s not a good idea! But there’s another problem. Thevar name = utsname(sysname: (0, 0, 0, ..., 0), nodename: (0, 0, 0, ..., 0), etc) utsname(&name) var machine = name.machine println(machine)
machine
field ofutsname
is a tuple so thatprintln
is going to print out 256Int8
’s, with only the first few representing the ASCII values of the characters in the string we actually want. So, how can we fix this? Swift’sUnsafeMutablePointer
supplies two methods,alloc(Int)
anddealloc(Int)
, for manually allocating and deallocating respectively. The argument the amount ofT
’s to allocate or deallocate. We can use these APIs to simplify our code.
The first step is making our call tolet name = UnsafeMutablePointer<utsname>.alloc(1) uname(name) let machine = withUnsafePointer(&name.memory.machine, { (ptr) -> String? in let int8Ptr = unsafeBitCast(ptr, UnsafePointer<Int8>.self) return String.fromCString(int8Ptr) }) name.dealloc(1) if let m = machine { println(m) }
withUnsafePointer
, passing it the machine tuple and telling it that our closure is going to return an optional String. Inside the closure we take the provided pointer and cast it toUnsafePointer
, a mostly equivalent representation of the same value. Except that Swift’sString
has a class method for initializing fromUnsafePointer
whereCChar
is a typealias forInt8
! So we can pass our new pointer to the initializer and return the value. After capturing the result ofwithUnsafePointer
(which, remember, forwards the return value of the supplied closure) we can test to see if we obtained a value using a conditional-let statement, and print the result. For me, this yields “x86_64” as expected.
Conclusion
A bit of a disclaimer. Using unsafe APIs in Swift should be a last resort because, they’re inherently not safe! As we transition from legacy C and Objective-C code to Swift, it’s likely that we’ll continue to need these APIs to be compatible with our existing tools. However, one should always be skeptical when their first resort is to break out withUnsafePointer and unsafeBitCast. New code should strive to be as idiomatic as possible, which explicitly prohibits the use of Swift’s unsafe APIs. As software developers, it’s just as important to know how to use your tools as it is to know when and where not to use them. Swift brings with it the benefit of modernizing a large subset of software development and we must respect its ideologies in order for it to really make an impact.Frequently Asked Questions about Using Legacy C APIs in Swift
How can I use C functions in Swift?
Swift provides a seamless way to use C functions. You can import C functions directly into Swift by creating a bridging header file. This file will contain the C function declarations that you want to use in your Swift code. Once you’ve created this file, you can call the C functions as if they were Swift functions. Remember to use the correct data types when calling these functions, as C and Swift data types may not always match.
What is UnsafeMutablePointer in Swift?
UnsafeMutablePointer is a Swift type that provides a mutable pointer to a value of a particular type. This is often used when working with C APIs, as many C functions require pointers to mutable memory locations. You can use the withUnsafeMutablePointer(to:_:)
function to safely create and manage these pointers.
How can I use withUnsafeMutablePointer in Swift?
The withUnsafeMutablePointer(to:_:)
function in Swift allows you to temporarily access or mutate the memory of a value through a pointer. This function takes two arguments: the value you want to access and a closure that takes a pointer to that value. The closure is immediately executed, and its return value becomes the return value of the withUnsafeMutablePointer(to:_:)
function.
How can I call C from Swift?
To call C from Swift, you need to create a bridging header file that includes the C function declarations you want to use. Once you’ve done this, you can call the C functions directly from your Swift code. Remember to use the correct data types when calling these functions, as C and Swift data types may not always match.
What is ManagedAudioChannelLayout in Swift?
ManagedAudioChannelLayout is a Swift structure that provides a way to manage audio channel layouts. It includes a withUnsafeMutablePointer
method that allows you to access the underlying C audio channel layout structure. This can be useful when working with audio APIs that require a pointer to an audio channel layout structure.
How can I use ManagedAudioChannelLayout in Swift?
You can use the withUnsafeMutablePointer
method of the ManagedAudioChannelLayout structure to access the underlying C audio channel layout structure. This method takes a closure that is executed with a pointer to the audio channel layout structure. The return value of the closure becomes the return value of the withUnsafeMutablePointer
method.
How can I handle errors when using C APIs in Swift?
When using C APIs in Swift, errors can be handled using Swift’s error handling mechanisms. If a C function returns an error code, you can check this code and throw a Swift error if necessary. You can also use optional types and the guard
statement to handle cases where a C function might return a null pointer.
How can I convert C data types to Swift data types?
Swift provides several ways to convert C data types to Swift data types. For example, you can use the Int
type to represent C integers, the Double
type to represent C doubles, and the String
type to represent C strings. For pointers, you can use the UnsafePointer
and UnsafeMutablePointer
types.
How can I use C structures in Swift?
C structures can be used in Swift by importing them through a bridging header file. Once imported, you can create instances of the structure, access its fields, and pass it to C functions. Remember to use the correct data types when working with these structures, as C and Swift data types may not always match.
How can I use C arrays in Swift?
C arrays can be used in Swift by importing them through a bridging header file. Once imported, you can access the elements of the array using subscript syntax, and pass the array to C functions. Remember to use the correct data types when working with these arrays, as C and Swift data types may not always match.
Julius is a freelance iOS developer who lives in central New Jersey. He works on the Fitocracy iOS apps and created the RunSwift website for running and sharing Swift code in your browser.