Swift bindings for Frida — the dynamic instrumentation toolkit.
frida-swift lets you use Frida from Swift or SwiftUI with fully
async/await-based APIs and reactive bindings.
Build and install the framework locally:
makeThen either:
-
Copy
build/Frida/Frida.frameworkinto your Xcode project, or -
Run:
make install
If you want a shared installation. In that case, you may first want to run:
./configure --prefix=/your/installation/prefix
Here’s a minimal SwiftUI view that lists connected devices and lets you tap to attach:
import Frida
import SwiftUI
struct DevicesView: View {
@StateObject private var manager = DeviceManager()
@State private var selectedDevice: Device?
@State private var session: Session?
var body: some View {
NavigationStack {
List(manager.devices, id: \.id) { device in
Button {
Task {
selectedDevice = device
session = try? await device.attach(to: 12345)
}
} label: {
VStack(alignment: .leading) {
Text(device.name)
.font(.headline)
Text(device.kind.rawValue)
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
}
.navigationTitle("Frida Devices")
.overlay {
if manager.devices.isEmpty {
ProgressView("Searching for devices…")
}
}
.alert(
"Error",
isPresented: Binding(
get: { manager.lastError != nil },
set: { _ in manager.lastError = nil }
),
actions: { Button("OK", role: .cancel) {} },
message: { Text(manager.lastError?.localizedDescription ?? "") }
)
}
}
}DeviceManageris a@StateObject, so the view updates automatically as devices are added or removed (e.g., USB or remote hotplug).manager.devicesis always the current list.- You can call async APIs like
device.attach(to:)directly from button actions insideTask { ... }blocks.
For non-SwiftUI contexts (e.g. unit tests), you can use Frida’s APIs directly:
func testFullCycle() async throws {
let pid: UInt = 20854
class TestDelegate: ScriptDelegate {
var messages: [Any] = []
var continuation: CheckedContinuation<Void, Never>?
func scriptDestroyed(_: Script) {
print("destroyed")
}
func script(_: Script, didReceiveMessage message: Any, withData data: Data?) {
print("didReceiveMessage")
messages.append(message)
if messages.count == 2 {
continuation?.resume()
continuation = nil
}
}
}
let delegate = TestDelegate()
let manager = DeviceManager()
let localDevice = try await manager.$devices
.values
.compactMap { devices in devices.first { $0.kind == .local } }
.first(where: { _ in true })
let session = try await localDevice.attach(to: pid)
let script = try await session.createScript("""
console.log("hello");
send(1337);
""")
script.delegate = delegate
try await script.load()
print("Script loaded")
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
delegate.continuation = continuation
}
print("Done with script \(script), messages: \(delegate.messages)")
}DeviceManagerautomatically tracks device hotplug and emits changes via its@Publisheddevicesproperty.- No delegate boilerplate: Frida signals are bridged directly into reactive Swift properties.
- The
AsyncSequenceapproach above is production-safe and avoids polling.
frida-swift aims to be:
- Swifty — embracing
async/await,@MainActor, and SwiftUI’s reactive model. - Zero boilerplate — no manual threading, no delegate wiring unless truly necessary.
- Faithful to Frida — what you can do in Frida’s C or JS APIs, you can do here, just idiomatically.
API stability is not yet guaranteed — the focus is on making it the best possible Swift binding before freezing the surface.
frida-swift is distributed under the wxWindows Library Licence, Version
3.1. See the accompanying COPYING file for full license text.