Skip to Content
Course content

142: C Interop in Swift

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've got a bit of a legacy situation. I'm working with a small C library that handles some low-level hardware telemetry—nothing fancy, just a few structs and functions—and I need to pull that data into a Swift app. Now, Swift is great, but it doesn't magically know how to talk to a .c file just because they're in the same folder. Let's figure out how to bridge the gap together.

Getting the compiler to see the C code

I'll start by creating a simple C header. Let's call it Telemetry.h. I've got a struct for a sensor reading and a function to simulate a reading coming from the hardware.

// Telemetry.h
typedef struct {
    int sensor_id;
    float value;
} SensorReading;

SensorReading get_latest_reading(int id);

And the implementation in Telemetry.c:

// Telemetry.c
#include "Telemetry.h"

SensorReading get_latest_reading(int id) {
    // In a real world, this would hit a register. 
    // Here, we'll just fake it.
    SensorReading r = { .sensor_id = id, .value = 42.0f };
    return r;
}


Now, if I go into my Swift file and try to call get_latest_reading(id: 1), the compiler screams at me. Cannot find 'get_latest_reading' in scope. This is the first hurdle. Swift needs a "map" to understand what the C world is offering. In a standard Xcode project, this is where the Bridging Header comes in. I'll create a file called YourProject-Bridging-Header.h and just add #import "Telemetry.h". Once I tell the build settings where that header lives, suddenly, the Swift compiler stops complaining. It's like the lights just turned on.

The "Type Soup" problem

Now that the function is visible, I try to use it. Simple enough, right?

let reading = get_latest_reading(1)
print("Sensor \(reading.sensor_id) is at \(reading.value)")

Surprisingly, this works perfectly. Swift imported the C struct SensorReading as a Swift struct. But here's where it gets messy. What if the C library doesn't return a value, but instead returns a pointer to a value? This is how most real C libraries actually work to avoid copying large structs on the stack.

Let's change the C function to return a pointer:

// Telemetry.h updated
SensorReading* get_reading_ptr(int id);

I try to call this in Swift, and now I'm staring at a type I've never seen before: UnsafeMutablePointer<SensorReading>. I'll be honest, the word "Unsafe" in the type name is a bit jarring. It's Swift's way of saying, "I'm letting you do this, but if you crash the app by accessing garbage memory, it's on you."

If I try to access reading.value now, it fails because reading is a pointer, not the struct itself. I need to "dereference" it. In Swift, we use the pointee property for this.

let ptr = get_reading_ptr(1)
if let reading = ptr?.pointee {
    print("The value is \(reading.value)")
}


Strings and the dreaded char*

The real headache starts with strings. C strings are just pointers to characters ending in a null terminator. Swift strings are complex, UTF-8 encoded objects. They don't speak the same language.

Let's add a function to our C library that returns the name of the sensor:

// Telemetry.h
const char* get_sensor_name(int id);

When I call get_sensor_name(1) in Swift, I get back an UnsafePointer<Int8>. That's a C string. If I try to print it, I just get the memory address. Not helpful. I need to convert this "C-style string" into a "Swift-style string".

I'll try the String(cString:) initializer:

let cName = get_sensor_name(1)
let swiftName = String(cString: cName)
print("Sensor name is \(swiftName)")

That works, but there's a catch. String(cString:) copies the data from the C memory into a new Swift-managed string. That's usually what you want. But if the C library expects you to free that memory afterward (which happens a lot), you'll have a memory leak. I've spent way too many hours hunting down leaks because I forgot that the C side of the bridge doesn't have Automatic Reference Counting (ARC). You have to manually manage what you allocate in C, even if you're calling it from Swift.




📋 Practical Task

Implement a C-to-Swift Memory Buffer Reader

You are given a small C library that simulates a hardware buffer. The library provides a function that returns a pointer to a buffer of integers and the size of that buffer.

The C Code:

// BufferLib.h
typedef struct {
    int* data;
    int length;
} DataBuffer;

DataBuffer get_hardware_buffer();
void release_buffer(DataBuffer buffer);

Your Goal: Write a Swift function called fetchHardwareData() that:

  • Calls get_hardware_buffer() to retrieve the DataBuffer struct.
  • Converts the C array (the UnsafeMutablePointer<Int32>) into a native Swift [Int] array.
  • Calls release_buffer() to prevent a memory leak.
  • Returns the Swift array.

Hint: Look into the UnsafeBufferPointer initializer that takes a base address and a count to make the conversion to a Swift array easier.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.