tunnelcat-sdk docs
Docs / Language SDKs / Swift

Swift SDK

Two entirely different mechanisms, because iOS and macOS have fundamentally different constraints — not a single shared design. Pick the section for your target.

macOS — sdk/swift/macOS

Same shape as the Python/Kotlin/C# adapters: spawns cmd/tunneld via Foundation.Process and talks to it over a Unix domain socket — raw POSIX socket(AF_UNIX, ...), wrapped in FileHandle, not a higher-level networking API.

Build

cd sdk/swift/macOS
swift build

You need a built tunneld binary on your PATH:

go build -o tunneld ./cmd/tunneld   # from the repo root

Usage

import TunnelCatSDK

let client = TunnelClient()
try client.start()
let result = try client.connect(ConnectParams(
    server: "https://your-control-server:443", username: "you", password: "secret"))
print(result?["socksAddr"] ?? "")
try client.disconnect()
client.close()

Try it with zero real credentials

go run ./cmd/mockserver &
swift run Basic   # see sdk/swift/macOS/Examples/Basic/main.swift

API reference

struct TunnelError: Error, CustomStringConvertible

let message: String, init(_ message: String). Thrown when tunneld returns an {"error": ...} response, or on transport failure.

struct ConnectParams

ConnectParams(server: String, apiKey: String = "", username: String = "", password: String = "", socksAddr: String = "", pollingOnly: Bool = false)

Memberwise init with the same defaults. pollingOnly: required against cmd/mockserver — see Wire Protocol.

final class TunnelClient

init(tunneldPath: String? = nil, socketPath: String? = nil, onEvent: (([String: Any]) -> Void)? = nil, connectTimeout: TimeInterval = 5)

macOS only — see the iOS section below for why this class doesn't exist there.

func start() throws

@discardableResult func connect(_ p: ConnectParams) throws -> [String: Any]?

@discardableResult func disconnect() throws -> [String: Any]?

@discardableResult func status() throws -> [String: Any]?

@discardableResult func reconnect() throws -> [String: Any]?

func close()

Also called automatically from deinit.


iOS — sdk/swift/ios

iOS sandboxing forbids spawning subprocesses, so this is a completely different mechanism: cmd/lib-ios is built as a cgo c-archive and linked directly into your app or extension target. Not a Swift package — two files you copy into your app target.

Setup

  1. Build the archive (needs a Mac with Xcode's iOS SDK; CC must point at the iOS-targeted clang):
    GOOS=ios GOARCH=arm64 CGO_ENABLED=1 CC=<clang> \
      go build -buildmode=c-archive -o libtunnelcat_core_arm64.a ./cmd/lib-ios
  2. Add libtunnelcat_core_arm64.a to your Xcode target's LIBRARY_SEARCH_PATHS / OTHER_LDFLAGS (-ltunnelcat_core_arm64).
  3. Set Bridging-Header.h as the target's SWIFT_OBJC_BRIDGING_HEADER.
  4. Add GoCore.swift to the target.

Usage

Note this is synchronous call-and-poll, not event-driven — there's no separate process boundary once linked in, so there's nothing to push events across.

let ok = GoCore.start(server: "https://your-control-server:443", apiKey: "",
                       username: "you", password: "secret",
                       logDir: logDirPath, dataDir: dataDirPath)
let port = GoCore.socksPort()
// point a WKWebView (WKWebsiteDataStore.proxyConfigurations, iOS 17+) or
// any other SOCKS5-consuming component at 127.0.0.1:<port>
GoCore.stop()
No TUN / packet-tunnel primitive

This exposes only a local SOCKS5 proxy port — there is no NEPacketTunnelProvider integration here. The documented consumption pattern is a WKWebView via WKWebsiteDataStore.proxyConfigurations (iOS 17+), or any other SOCKS5-consuming component.

C API reference (Bridging-Header.h)

FunctionBehavior
int TCStart(char *server, char *apiKey, char *username, char *password, char *logDir, char *dataDir)Logs in and starts a local SOCKS5 server. Returns 0 on success, -1 on failure (check TCGetStatus for the error). Refuses if already connecting/connected — returns -1 without changing state.
void TCStop(void)Tears down the SOCKS5 server and clears session state.
char *TCGetStatus(void)Heap-allocated JSON C-string: {"state":"idle"|"connecting"|"connected"|"error","error":"..."}. Caller must free with TCFreeString.
void TCFreeString(char *s)Frees a string returned by TCGetStatus.
int TCGetSocksPort(void)Local SOCKS5 listener's port, or 0 if not currently listening.
void TCReconnect(void)Re-runs Login() against the same credentials passed to the last successful TCStart.

Hand-written rather than cgo's autogenerated header, which pulls in cgo-internal GoString/GoInt typedefs — this header is the one actually meant to be read.

GoCore.swift reference

A thin 1:1 Swift wrapper over the C exports above. No callback/delegate mechanism — status is synchronous call-and-poll.

public enum TunnelState: String, Decodable { case idle, connecting, connected, error }

public struct TunnelStatus: Decodable { let state: TunnelState; let error: String? }

GoCore.start(server:apiKey:username:password:logDir:dataDir:) -> Bool

GoCore.stop()

GoCore.status() -> TunnelStatus?

GoCore.socksPort() -> Int32

GoCore.reconnect()