Skip to content
Blog

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?

  1. Zero Serialization Overhead: Data buffers (Pointer<Uint8>, C-structs) are shared directly in process memory space without JSON/StandardMessageCodec encoding.
  2. Synchronous Execution: FFI bindings can execute synchronously without waiting for async channel event queues.
  3. 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

  • @objc Attribute: Swift classes and methods exposed to FFI must inherit from NSObject and bear the @objc attribute.
  • Memory Management: FFI memory allocated with calloc or malloc must 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.