WIP: Talk to Swift via C without involving Objective-C

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

LiveKitObjC/LKRoom.m                              | 32 ----------------
LiveKitObjC/LiveKitObjC.xcodeproj/project.pbxproj | 12 +++---
LiveKitObjC/Room.swift                            | 26 ++++++++++++-
crates/capture/build.rs                           |  4 +
crates/capture/src/main.rs                        | 33 +++++++++++++---
5 files changed, 60 insertions(+), 47 deletions(-)

Detailed changes

LiveKitObjC/LKRoom.m 🔗

@@ -1,32 +0,0 @@
-//
-//  LKRoom.m
-//  LiveKitObjC
-//
-//  Created by Antonio Scandurra on 01/09/22.
-//
-
-#import <Foundation/Foundation.h>
-#import <LiveKitObjC-Swift.h>
-
-@interface LKRoom: NSObject {
-}
-
-@property (nonatomic, retain) SLKRoom* room;
-@end
-
-@implementation LKRoom
--(id)init {
-    if (self = [super init]) {
-        self.room = [[SLKRoom alloc] init];
-    }
-    return self;
-}
-
--(void)connectWithURL:(NSString *)url token:(NSString *)token callback:(void(^)(void))callback {
-    [self.room connectWithUrl:url token:token callback:callback];
-}
-@end
-
-LKRoom* BuildLKRoom() {
-    return [[LKRoom alloc] init];
-}

LiveKitObjC/LiveKitObjC.xcodeproj/project.pbxproj 🔗

@@ -9,14 +9,12 @@
 /* Begin PBXBuildFile section */
 		AFA4DBD628C0F839001AD7BE /* LiveKit in Frameworks */ = {isa = PBXBuildFile; productRef = AFA4DBD528C0F839001AD7BE /* LiveKit */; };
 		AFA4DBD928C0F87F001AD7BE /* Room.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4DBD828C0F87F001AD7BE /* Room.swift */; };
-		AFA4DBDB28C0FBC0001AD7BE /* LKRoom.m in Sources */ = {isa = PBXBuildFile; fileRef = AFA4DBDA28C0FBC0001AD7BE /* LKRoom.m */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXFileReference section */
 		AFA4DBCD28C0F7F5001AD7BE /* libLiveKitObjC.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libLiveKitObjC.a; sourceTree = BUILT_PRODUCTS_DIR; };
 		AFA4DBD728C0F87F001AD7BE /* LiveKitObjC-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LiveKitObjC-Bridging-Header.h"; sourceTree = "<group>"; };
 		AFA4DBD828C0F87F001AD7BE /* Room.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Room.swift; sourceTree = "<group>"; };
-		AFA4DBDA28C0FBC0001AD7BE /* LKRoom.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LKRoom.m; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -34,7 +32,6 @@
 		AFA4DBC428C0F7F5001AD7BE = {
 			isa = PBXGroup;
 			children = (
-				AFA4DBDA28C0FBC0001AD7BE /* LKRoom.m */,
 				AFA4DBD828C0F87F001AD7BE /* Room.swift */,
 				AFA4DBCE28C0F7F5001AD7BE /* Products */,
 				AFA4DBD728C0F87F001AD7BE /* LiveKitObjC-Bridging-Header.h */,
@@ -124,7 +121,6 @@
 			buildActionMask = 2147483647;
 			files = (
 				AFA4DBD928C0F87F001AD7BE /* Room.swift in Sources */,
-				AFA4DBDB28C0FBC0001AD7BE /* LKRoom.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -245,13 +241,15 @@
 			buildSettings = {
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
+				DEFINES_MODULE = YES;
 				EXECUTABLE_PREFIX = lib;
-				KEEP_PRIVATE_EXTERNS = NO;
+				KEEP_PRIVATE_EXTERNS = YES;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/../Frameworks",
 					"@loader_path/../Frameworks",
 				);
+				PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "LiveKitObjC-Bridging-Header.h";
 				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -264,13 +262,15 @@
 			buildSettings = {
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_STYLE = Automatic;
+				DEFINES_MODULE = YES;
 				EXECUTABLE_PREFIX = lib;
-				KEEP_PRIVATE_EXTERNS = NO;
+				KEEP_PRIVATE_EXTERNS = YES;
 				LD_RUNPATH_SEARCH_PATHS = (
 					"$(inherited)",
 					"@executable_path/../Frameworks",
 					"@loader_path/../Frameworks",
 				);
+				PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				SWIFT_OBJC_BRIDGING_HEADER = "LiveKitObjC-Bridging-Header.h";
 				SWIFT_VERSION = 5.0;

LiveKitObjC/Room.swift 🔗

@@ -8,10 +8,18 @@
 import Foundation
 import LiveKit
 
-@objc public class SLKRoom: NSObject, RoomDelegate {
+public class LKRoom: RoomDelegate {
     lazy var room = Room(delegate: self)
     
-    @objc public func connect(
+    init() {
+        print("INIT!\n");
+    }
+    
+    deinit {
+        print("DEINIT!\n");
+    }
+    
+    public func connect(
         url: String,
         token: String,
         callback: @convention(block) @escaping () -> Void
@@ -20,4 +28,18 @@ import LiveKit
             callback()
         }
     }
+    
+}
+
+
+@_cdecl("LKRoomCreate")
+public func LKRoomCreate() -> UnsafeMutableRawPointer  {
+    Unmanaged.passRetained(LKRoom()).toOpaque()
+}
+
+@_cdecl("LKRoomDestroy")
+public func LKRoomDestroy(ptr: UnsafeRawPointer)  {
+    let _ = Unmanaged<LKRoom>.fromOpaque(ptr).takeRetainedValue();
 }
+
+

crates/capture/build.rs 🔗

@@ -58,9 +58,11 @@ pub fn link_swift_libs() {
 fn main() {
     link_swift_libs();
     println!("cargo:rerun-if-changed=/Users/as-cii/Library/Developer/Xcode/DerivedData/LiveKitObjC-ftgpxknhsgkrocbhhgjkyyvkgkbj/Build/Products/Debug/libLiveKitObjC.a");
-    println!("cargo:rustc-link-search=/Users/as-cii/Library/Developer/Xcode/DerivedData/LiveKitObjC-ftgpxknhsgkrocbhhgjkyyvkgkbj/Build/Products/Debug");
+    println!("cargo:rustc-link-search=native=/Users/as-cii/Library/Developer/Xcode/DerivedData/LiveKitObjC-ftgpxknhsgkrocbhhgjkyyvkgkbj/Build/Products/libs");
+    println!("cargo:rustc-link-search=framework=/Users/as-cii/Library/Developer/Xcode/DerivedData/LiveKitObjC-ftgpxknhsgkrocbhhgjkyyvkgkbj/Build/Products/frameworks");
     println!("cargo:rustc-link-lib=static=LiveKitObjC");
     println!("cargo:rustc-link-lib=framework=ScreenCaptureKit");
+    println!("cargo:rustc-link-lib=framework=WebRTC");
     println!("cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET=12.3");
 
     let sdk_path = String::from_utf8(

crates/capture/src/main.rs 🔗

@@ -10,7 +10,7 @@ use cocoa::{
     foundation::{NSArray, NSString, NSUInteger},
 };
 use core_foundation::{
-    base::TCFType,
+    base::{CFRelease, TCFType},
     number::{CFBooleanGetValue, CFBooleanRef, CFNumberRef},
     string::CFStringRef,
 };
@@ -35,7 +35,7 @@ use objc::{
     class,
     declare::ClassDecl,
     msg_send,
-    runtime::{Object, Sel},
+    runtime::{Class, Object, Sel},
     sel, sel_impl,
 };
 use parking_lot::Mutex;
@@ -48,13 +48,34 @@ const NSUTF8StringEncoding: NSUInteger = 4;
 actions!(capture, [Quit]);
 
 extern "C" {
-    fn BuildLKRoom() -> *const c_void;
+    fn LKRoomCreate() -> *const c_void;
+    fn LKRoomDestroy(ptr: *const c_void);
 }
 
-fn main() {
-    unsafe {
-        BuildLKRoom();
+struct Room {
+    native_room: *const c_void,
+}
+
+impl Room {
+    pub fn new() -> Self {
+        Self {
+            native_room: unsafe { LKRoomCreate() },
+        }
+    }
+}
+
+impl Drop for Room {
+    fn drop(&mut self) {
+        unsafe { LKRoomDestroy(self.native_room) }
     }
+}
+
+fn main() {
+    println!("Creating room...");
+    let room = Room::new();
+
+    println!("Dropping room...");
+    drop(room);
 
     SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");