This is an example of C callback that triggers a Combine notification.
( Moved on GitHub right here : https://github.com/moosefactory/C-callback-to-Swift )
This example changes a value of a C String in a C Library and displays it in a field in a SwiftUI view.
You don't need to go through Objective-C.
I think it's nice to have an object that makes the bridge between your C code and your swift application, to remove esoteric syntax from the app code. In this example, the file MyCLibraryInterface
does the job.
This class is an observable object that will publish the value change using combine, so it goes a bit beyond the question - You can stop there and do what you want once you are in the callback block. Note that we can't catch the swift context in c calls ( no calls to self or variables declared on the heap )
- The third part is a simple SwiftUI app that receive change and update interface
C Library
#ifndef MyCLibrary_h
#define MyCLibrary_h
#include <stdio.h>
#include <dispatch/dispatch.h>
#include <stdlib.h>
/// The callback to call when the string is changed
typedef void callback_t(const char* string);
void setCallBack(callback_t _callback);
/// A function that will change the string
void setString(const char* string);
void startTimer(void);
void cancelTimer(void);
#endif /* MyCLibrary_h */
#include "MyCLibrary.h"
const char* myString;
dispatch_queue_t queue;
dispatch_source_t timer;
bool running;
callback_t* callback;
void setCallBack(callback_t _callback) {
callback = _callback;
}
void setString(const char* string) {
myString = string;
callback(myString);
}
/// A function that will start a timer that changes string
int ticks = 0;
void idle(dispatch_source_t timer)
{
ticks++;
char ticksStr[32];
sprintf(ticksStr, "Time : %ds", ticks);
setString(ticksStr);
}
void startTimer() {
if (running) { cancelTimer(); sleep(1); }
queue = dispatch_queue_create("timerQueue", 0);
// Create dispatch timer source
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_event_handler(timer, ^{idle(timer);});
dispatch_source_set_cancel_handler(timer, ^{
dispatch_release(timer);
dispatch_release(queue);
});
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 0);
// Set timer
dispatch_source_set_timer(timer, start, NSEC_PER_SEC, 0);
ticks = 0;
running = true;
dispatch_resume(timer);
}
void cancelTimer() {
running = false;
dispatch_source_cancel(timer);
char ticksStr[32];
sprintf(ticksStr, "Canceled after %ds", ticks);
setString(ticksStr);
}
C<>Swift Part
#import "MyCLibrary.h"
- MyCLibraryInterface.swift
import Foundation
class MyCLibraryInterface: ObservableObject {
@Published var string: String = "This is a string"
static let shared = MyCLibraryInterface()
init() {
setCallBack { stringPtr in
let newString = CFStringCreateWithCString(kCFAllocatorDefault, stringPtr, kCFStringEncodingASCII) ?? "" as CFString
DispatchQueue.main.async {
MyCLibraryInterface.shared.string = newString as String
}
}
}
func setLibString(string: String) {
string.withCString { stringPointer in
setString(stringPointer)
}
}
func startLibTimer() {
startTimer()
}
func cancelLibTimer() {
cancelTimer()
}
}
SwiftUI Sample
This sample app present the intial string and a button. On click or tap, the setString
function is called in the CLibrary, the swift callback is called and the view is updated following the ObservableObject
modification
import SwiftUI
struct ContentView: View {
@ObservedObject var myCLibInterface: MyCLibraryInterface = MyCLibraryInterface.shared
var body: some View {
VStack {
Text(myCLibInterface.string).frame(width:150).padding()
Button("Reset") {
myCLibInterface.setLibString(string: "C Timer Example")
}.padding()
Button("Start Timer") {
myCLibInterface.startLibTimer()
}.padding()
Button("Cancel") {
myCLibInterface.cancelLibTimer()
}.padding()
}.padding(20)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}