Ivan FB c43072da46
feat(examples): iOS example consumes the generated Swift wrapper
Regenerates Sources/MyTimer/MyTimer.swift via `nimble genbindings_swift`
(replacing the hand-written wrapper) and points the tests at the derived
MyTimerNode class. Proves the generator reproduces the validated
create/version/echo path: build-xcframework.sh + `swift test` pass 2/2 on
the macOS slice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 16:40:04 +02:00

122 lines
4.2 KiB
Swift

// Generated by nim-ffi Swift codegen. Do not edit by hand.
//
// Idiomatic Swift wrapper over the library's native (zero-serialization) C ABI.
// Each call is dispatched on the library's background FFI thread; we block on a
// DispatchSemaphore until the result callback fires. A struct return is read out
// of the typed C-POD inside the callback valid only for the callback's
// lifetime and copied into a native Swift value.
import CMyTimer
import Foundation
public enum TimerError: Error, CustomStringConvertible {
case failed(String)
public var description: String {
switch self { case let .failed(m): return m }
}
}
public struct EchoResponse: Equatable {
public let echoed: String
public let timerName: String
}
public final class MyTimerNode {
private let ctx: UnsafeMutableRawPointer
public init(name: String) throws {
let box = Box()
let ud = Unmanaged.passUnretained(box).toOpaque()
var c_config = CMyTimer.TimerConfig()
let c_config_name = strdup(name)
defer { free(c_config_name) }
c_config.name = UnsafePointer(c_config_name)
guard let c = my_timer_create(c_config, ackCallback, ud) else {
throw TimerError.failed("create returned null")
}
box.sem.wait()
guard box.ret == 0 else { throw TimerError.failed(box.text) }
ctx = c
}
public func echo(_ message: String, delayMs: Int = 0) throws -> EchoResponse {
let box = MyTimerEchoBox()
let ud = Unmanaged.passUnretained(box).toOpaque()
var c_req = CMyTimer.EchoRequest()
let c_req_message = strdup(message)
defer { free(c_req_message) }
c_req.message = UnsafePointer(c_req_message)
c_req.delayMs = Int64(delayMs)
guard my_timer_echo(ctx, my_timer_echoCallback, ud, c_req) == 0 else {
throw TimerError.failed("echo dispatch failed")
}
box.sem.wait()
guard box.ret == 0 else { throw TimerError.failed(box.text) }
return EchoResponse(echoed: box.echoed, timerName: box.timerName)
}
public func version() throws -> String {
let box = Box()
let ud = Unmanaged.passUnretained(box).toOpaque()
guard my_timer_version(ctx, stringCallback, ud) == 0 else {
throw TimerError.failed("version dispatch failed")
}
box.sem.wait()
guard box.ret == 0 else { throw TimerError.failed(box.text) }
return box.text
}
deinit { my_timer_destroy(ctx) }
}
// MARK: - shared callback plumbing
final class Box {
var ret: Int32 = -1
var text = ""
let sem = DispatchSemaphore(value: 0)
}
func rawText(_ msg: UnsafePointer<CChar>?, _ len: Int) -> String {
guard let m = msg, len > 0 else { return "" }
let bytes = UnsafeRawPointer(m).assumingMemoryBound(to: UInt8.self)
return String(decoding: UnsafeBufferPointer(start: bytes, count: len), as: UTF8.self)
}
func ackCallback(_ ret: Int32, _ msg: UnsafePointer<CChar>?,
_ len: Int, _ ud: UnsafeMutableRawPointer?) {
let box = Unmanaged<Box>.fromOpaque(ud!).takeUnretainedValue()
box.ret = ret
if ret != 0 { box.text = rawText(msg, len) }
box.sem.signal()
}
func stringCallback(_ ret: Int32, _ msg: UnsafePointer<CChar>?,
_ len: Int, _ ud: UnsafeMutableRawPointer?) {
let box = Unmanaged<Box>.fromOpaque(ud!).takeUnretainedValue()
box.ret = ret
box.text = rawText(msg, len)
box.sem.signal()
}
final class MyTimerEchoBox {
var ret: Int32 = -1
var text = ""
var echoed = ""
var timerName = ""
let sem = DispatchSemaphore(value: 0)
}
private func my_timer_echoCallback(_ ret: Int32, _ msg: UnsafePointer<CChar>?,
_ len: Int, _ ud: UnsafeMutableRawPointer?) {
let box = Unmanaged<MyTimerEchoBox>.fromOpaque(ud!).takeUnretainedValue()
box.ret = ret
if ret == 0, let m = msg {
let resp = UnsafeRawPointer(m).assumingMemoryBound(to: CMyTimer.EchoResponse.self)
box.echoed = resp.pointee.echoed.map { String(cString: $0) } ?? ""
box.timerName = resp.pointee.timerName.map { String(cString: $0) } ?? ""
} else {
box.text = rawText(msg, len)
}
box.sem.signal()
}