Direct Swift and Objective-C Interoperability in Dart using ffigen
Learn how to use Dart's ffigen to generate direct FFI bindings for Swift and Objective-C libraries, bypassing method channel overhead.
Published on • November 9, 2026
AI Assistant

While traditional MethodChannel communications pass data across platform threads using serialized message buffers, intensive computations—such as image processing, cryptography, audio synthesis, or high-frequency native library calls—benefit from zero-copy, zero-serialization interop.
Dart’s Foreign Function Interface (dart:ffi) combined with the ffigen code generator provides direct, synchronous call bindings into Objective-C and Swift native codebases.
Why FFI over Platform Channels?
- Zero Serialization Overhead: Data buffers (
Pointer<Uint8>, C-structs) are shared directly in process memory space without JSON/StandardMessageCodec encoding. - Synchronous Execution: FFI bindings can execute synchronously without waiting for async channel event queues.
- Sub-millisecond Performance: Ideal for real-time graphics algorithms or native DSP audio pipelines.
How Swift Interop Works in ffigen
Dart’s ffigen uses Clang under the hood to parse C, Objective-C, and Swift headers (when exported with @objc annotations) and generates type-safe Dart wrapper classes.
Step 1: Adding ffigen Configuration
Add ffigen to your pubspec.yaml:
dev_dependencies:
ffigen: ^16.0.0
ffigen:
name: NativeMathBindings
description: 'Bindings to native Swift/ObjC math library'
output: 'lib/src/generated_native_bindings.dart'
headers:
entry-points:
- 'ios/Classes/NativeMath.h'
objective-c:
include-utils: true
Step 2: Defining Native Objective-C / Swift Header
ios/Classes/NativeMath.h:
#import <Foundation/Foundation.h>
@interface NativeMath : NSObject
+ (double)calculateFastFourierTransform:(const double *)input length:(int)length;
@end
ios/Classes/NativeMath.swift:
import Foundation
@objc public class NativeMath: NSObject {
@objc public static func calculateFastFourierTransform(_ input: UnsafePointer<Double>, length: Int32) -> Double {
// High-performance Swift processing
var sum: Double = 0.0
for i in 0..<Int(length) {
sum += input[i]
}
return sum / Double(length)
}
}
Step 3: Generating Bindings and Executing FFI Calls
Run the generator from your terminal:
dart run ffigen
ffigen creates lib/src/generated_native_bindings.dart. Now you can invoke the Swift code synchronously from Dart:
import 'dart:ffi';
import 'package:ffi/ffi.dart';
import 'src/generated_native_bindings.dart';
void main() {
// Load the dynamic framework library
final DynamicLibrary nativeLib = DynamicLibrary.process();
final bindings = NativeMathBindings(nativeLib);
// Allocate native memory buffer for 4 doubles
final Pointer<Double> buffer = calloc<Double>(4);
buffer[0] = 10.0;
buffer[1] = 20.0;
buffer[2] = 30.0;
buffer[3] = 40.0;
// Call Swift method synchronously via FFI
final double average = bindings.NativeMath.calculateFastFourierTransform_length_(buffer, 4);
print('Average calculated synchronously by Swift: $average');
// Always free allocated FFI memory pointers!
calloc.free(buffer);
}
Considerations for Swift FFI
@objcAttribute: Swift classes and methods exposed to FFI must inherit fromNSObjectand bear the@objcattribute.- Memory Management: FFI memory allocated with
callocormallocmust be explicitly freed to prevent native memory leaks. - Threading: Long-running computational FFI tasks should be dispatched off the main UI thread using
Isolate.run()to prevent UI frame drops.
Takeaway: Combining dart:ffi and ffigen unlocks zero-overhead, synchronous Swift and Objective-C execution for high-performance Flutter applications.