Summary
I'd like to propose migrating the iOS integration layer from Objective-C to Swift, inspired by the Blinc project's Rust+Swift architecture. After a thorough comparative analysis of both projects, I believe this change would significantly improve safety, maintainability, and long-term viability.
Current Situation
The current iOS integration uses Objective-C (main.m) as the app entry point and relies heavily on Rust-side objc2::msg_send! calls to interact with UIKit APIs. This approach has several systemic issues:
1. Pervasive unsafe Code
Every platform service (clipboard, device_info, battery, etc.) is implemented in Rust using msg_send!, which requires unsafe blocks and provides zero compile-time type checking:
// clipboard/ios.rs — 66 lines, all unsafe
pub fn set_text(text: &str) -> Result<(), String> {
unsafe {
let pasteboard: *mut AnyObject = msg_send![class!(UIPasteboard), generalPasteboard];
let ns_text: *mut AnyObject = msg_send![class!(NSString), alloc];
let ns_text: *mut AnyObject = msg_send![ns_text,
initWithBytes: text.as_ptr() as *const c_void,
length: text.len(), encoding: 4u64];
let _: () = msg_send![pasteboard, setString: ns_text];
Ok(())
}
}
The compiler cannot verify Obj-C message signatures — a wrong return type or missing argument silently corrupts memory at runtime.
2. Runtime Dynamic Class Registration
Custom UIView/UIViewController subclasses are registered at runtime via ClassBuilder:
fn register_metal_view_class() -> &'static AnyClass {
METAL_VIEW_CLASS_REGISTERED.call_once(|| {
let mut decl = ClassBuilder::new(c"GPUIMetalView", superclass).unwrap();
decl.add_ivar::<*mut c_void>(c"gpui_window_ptr");
decl.add_method(sel!(touchesBegan:withEvent:), touches_began);
// ...
decl.register();
});
}
These dynamically-registered classes:
- Cannot be debugged in Xcode (no breakpoints, no view hierarchy inspector)
- Have no compile-time validation of method signatures
- Fail silently on name collisions or signature mismatches
3. No Bidirectional Communication
The FFI is one-directional: Obj-C calls Rust. When Rust needs platform APIs, it can only use msg_send! — there's no way to call back into Swift/Obj-C code. This means:
- Complex platform features (camera, share sheet, haptics) must be reimplemented in Rust with
msg_send!
- Cannot leverage Swift ecosystem or third-party libraries
- Every new platform feature requires more
unsafe Rust code
4. Memory Management Risks
Objects created via msg_send! are outside ARC's scope:
let ns_text: *mut AnyObject = msg_send![class!(NSString), alloc];
let ns_text: *mut AnyObject = msg_send![ns_text, initWithBytes:...];
// Who calls release? Manual memory management is error-prone
5. Legacy iOS Lifecycle
The app still uses the traditional UIWindow pattern without UIScene support. Apple has warned:
"UIScene lifecycle will soon be required. Failure to adopt will result in an assert in the future."
6. Objective-C Ecosystem Decline
Apple's focus has fully shifted to Swift. New iOS frameworks (WidgetKit, App Intents, etc.) are Swift-first or Swift-only. The Obj-C developer pool is shrinking.
How Blinc Solves This
The Blinc project uses a Rust+Swift architecture that addresses all of the above issues:
Native Bridge for Bidirectional Communication
Blinc implements a NativeBridge pattern where Swift registers handlers by namespace/function name, and Rust calls them with type-safe NativeValue arguments:
Swift side — native, type-safe, ARC-managed:
BlincNativeBridge.shared.register(namespace: "clipboard", name: "copy") { args in
let text = args.first as? String ?? ""
UIPasteboard.general.string = text
return nil
}
Rust side — clean, no msg_send!:
native_call::<(), _>("clipboard", "copy", (text,))?;
Swift-Native View Controller
All UIKit interaction (touch handling, keyboard management, Metal layer setup) is done natively in Swift:
class BlincViewController: UIViewController {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let ctx = renderContext else { return }
for touch in touches {
let point = touch.location(in: view)
blinc_handle_touch(ctx, touchId, Float(point.x), Float(point.y), 0)
}
}
}
No runtime class registration, no ClassBuilder, no msg_send! for touch events.
Modern iOS Lifecycle
Full UISceneDelegate support with SceneDelegate.swift, avoiding Apple's deprecation warnings and enabling multi-window on iPad.
Rich Platform Features
Blinc's Swift layer already implements:
- Haptic feedback (
UIImpactFeedbackGenerator, UINotificationFeedbackGenerator)
- Camera preview (
AVCaptureSession)
- Audio recording (
AVAudioRecorder)
- Native edit menu (
UIMenuController)
- Share sheet (
UIActivityViewController)
- Keyboard management (hidden
UITextField delegate pattern)
- Dark mode detection, safe area insets, device info
All implemented in Swift with full type safety and ARC — zero unsafe Rust code needed.
Graceful Degradation via dlsym
Optional Swift functions are resolved at runtime, so the Rust code doesn't crash if a Swift handler is missing:
fn keyboard_show_fn() -> Option<extern "C" fn()> {
static FN: OnceLock<Option<extern "C" fn()>> = OnceLock::new();
*FN.get_or_init(|| unsafe { lookup_extern_fn(b"blinc_ios_show_keyboard\0") })
}
Proposed Migration Path
This doesn't need to be a big-bang rewrite. A phased approach:
Phase 1: Replace Entry Point
- Replace
main.m with AppDelegate.swift + SceneDelegate.swift
- Add a Bridging Header declaring existing Rust FFI functions
- Keep all existing FFI function signatures unchanged
Phase 2: Introduce Native Bridge
- Implement a
NativeBridge pattern for Rust → Swift calls
- Migrate platform services one by one (start with clipboard, device_info, keyboard)
- Each migrated service removes
msg_send! code from Rust
Phase 3: Swift View Controller
- Create
BlincViewController-style Swift view controller
- Move touch event handling from Rust dynamic classes to Swift
- Move Metal layer management to Swift
Phase 4: Remove objc2 Dependency
- Eliminate all
msg_send! calls from Rust
- Remove
ClassBuilder dynamic class registration
- All platform APIs go through Native Bridge
Comparison Summary
| Dimension |
Current (Obj-C) |
Proposed (Swift) |
| Type safety |
❌ unsafe + msg_send! |
✅ Swift compile-time checks |
| Memory management |
❌ Manual (no ARC) |
✅ ARC automatic |
| Debuggability |
❌ Dynamic classes invisible to Xcode |
✅ Full Xcode support |
| Bidirectional FFI |
❌ One-way only |
✅ Native Bridge |
| iOS lifecycle |
❌ Legacy UIWindow |
✅ UIScene |
| Platform features |
⚠️ Limited, requires unsafe Rust |
✅ Rich, native Swift |
| Extensibility |
❌ New feature = more unsafe code |
✅ Register a Swift handler |
| Obj-C ecosystem |
❌ Declining |
✅ Swift is Apple's future |
Trade-offs Acknowledged
- Bridging Header maintenance: Every Rust FFI function needs a C declaration in the header. This can be mitigated with code generation (e.g.,
cbindgen or a build script).
- JSON serialization overhead: The Native Bridge uses JSON for argument passing. For hot paths (touch events), direct C FFI with primitive types should be used instead (as Blinc does with
blinc_handle_touch).
- Swift runtime size: Adds ~2-5MB for Swift stdlib. Negligible for modern iOS apps.
- Migration effort: Significant but can be done incrementally with both systems coexisting.
Summary
I'd like to propose migrating the iOS integration layer from Objective-C to Swift, inspired by the Blinc project's Rust+Swift architecture. After a thorough comparative analysis of both projects, I believe this change would significantly improve safety, maintainability, and long-term viability.
Current Situation
The current iOS integration uses Objective-C (
main.m) as the app entry point and relies heavily on Rust-sideobjc2::msg_send!calls to interact with UIKit APIs. This approach has several systemic issues:1. Pervasive
unsafeCodeEvery platform service (clipboard, device_info, battery, etc.) is implemented in Rust using
msg_send!, which requiresunsafeblocks and provides zero compile-time type checking:The compiler cannot verify Obj-C message signatures — a wrong return type or missing argument silently corrupts memory at runtime.
2. Runtime Dynamic Class Registration
Custom UIView/UIViewController subclasses are registered at runtime via
ClassBuilder:These dynamically-registered classes:
3. No Bidirectional Communication
The FFI is one-directional: Obj-C calls Rust. When Rust needs platform APIs, it can only use
msg_send!— there's no way to call back into Swift/Obj-C code. This means:msg_send!unsafeRust code4. Memory Management Risks
Objects created via
msg_send!are outside ARC's scope:5. Legacy iOS Lifecycle
The app still uses the traditional
UIWindowpattern withoutUIScenesupport. Apple has warned:6. Objective-C Ecosystem Decline
Apple's focus has fully shifted to Swift. New iOS frameworks (WidgetKit, App Intents, etc.) are Swift-first or Swift-only. The Obj-C developer pool is shrinking.
How Blinc Solves This
The Blinc project uses a Rust+Swift architecture that addresses all of the above issues:
Native Bridge for Bidirectional Communication
Blinc implements a
NativeBridgepattern where Swift registers handlers by namespace/function name, and Rust calls them with type-safeNativeValuearguments:Swift side — native, type-safe, ARC-managed:
Rust side — clean, no
msg_send!:Swift-Native View Controller
All UIKit interaction (touch handling, keyboard management, Metal layer setup) is done natively in Swift:
No runtime class registration, no
ClassBuilder, nomsg_send!for touch events.Modern iOS Lifecycle
Full
UISceneDelegatesupport withSceneDelegate.swift, avoiding Apple's deprecation warnings and enabling multi-window on iPad.Rich Platform Features
Blinc's Swift layer already implements:
UIImpactFeedbackGenerator,UINotificationFeedbackGenerator)AVCaptureSession)AVAudioRecorder)UIMenuController)UIActivityViewController)UITextFielddelegate pattern)All implemented in Swift with full type safety and ARC — zero
unsafeRust code needed.Graceful Degradation via
dlsymOptional Swift functions are resolved at runtime, so the Rust code doesn't crash if a Swift handler is missing:
Proposed Migration Path
This doesn't need to be a big-bang rewrite. A phased approach:
Phase 1: Replace Entry Point
main.mwithAppDelegate.swift+SceneDelegate.swiftPhase 2: Introduce Native Bridge
NativeBridgepattern for Rust → Swift callsmsg_send!code from RustPhase 3: Swift View Controller
BlincViewController-style Swift view controllerPhase 4: Remove
objc2Dependencymsg_send!calls from RustClassBuilderdynamic class registrationComparison Summary
unsafe+msg_send!Trade-offs Acknowledged
cbindgenor a build script).blinc_handle_touch).