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
Memberwise init with the same defaults. pollingOnly: required
against cmd/mockserver — see Wire Protocol.
final class TunnelClient
macOS only — see the iOS section below for why this class doesn't exist there.
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
- Build the archive (needs a Mac with Xcode's iOS SDK;
CCmust 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 - Add
libtunnelcat_core_arm64.ato your Xcode target'sLIBRARY_SEARCH_PATHS/OTHER_LDFLAGS(-ltunnelcat_core_arm64). - Set
Bridging-Header.has the target'sSWIFT_OBJC_BRIDGING_HEADER. - Add
GoCore.swiftto 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()
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)
| Function | Behavior |
|---|---|
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.