### Build FSKit Example Extension Source: https://github.com/madsmtm/objc2/blob/main/examples/fskit/README.md Builds the FSKit example extension. This command should be run from the project's root directory. ```sh ./examples/fskit/bundle.sh ``` -------------------------------- ### Application Delegate for Graphical Apps (macOS) Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/run_loop.md This example demonstrates setting up an `AppDelegate` for macOS graphical applications. It defines the delegate class with methods for `applicationDidFinishLaunching:` and `applicationWillTerminate:`, which are called by the application's run loop. The `NSApplicationMain` function is used to start the application. ```rust use objc2::rc::{Allocated, Retained}; use objc2::{define_class, msg_send, ClassType, Ivars, MainThreadOnly}; use objc2_foundation::{NSNotification, NSObject, NSObjectProtocol}; // Application delegate protocols happens to share a few methods, // we can utilize that to be a bit more platform-generic. #[cfg(target_os = "macos")] use objc2_app_kit::NSApplicationDelegate as DelegateProtocol; #[cfg(not(target_os = "macos"))] use objc2_ui_kit::UIApplicationDelegate as DelegateProtocol; define_class!( // SAFETY: // - NSObject does not have any subclassing requirements. // - `AppDelegate` does not implement `Drop`. #[unsafe(super(NSObject))] #[thread_kind = MainThreadOnly] #[derive(Debug)] struct AppDelegate { // Whatever state you want to store in your delegate. } impl AppDelegate { // Called by `NSApplicationMain`, `UIApplicationMain` // or our `msg_send![AppDelegate::class(), new]`. #[unsafe(method(init))] fn init(this: Allocated) -> Retained { let this = this.set_ivars(Ivars:: { // Initialize state. }); unsafe { msg_send![super(this), init] } } } unsafe impl NSObjectProtocol for AppDelegate {} unsafe impl DelegateProtocol for AppDelegate { #[unsafe(method(applicationDidFinishLaunching:))] fn did_finish_launching(&self, _notification: &NSNotification) { println!("did finish launching!"); // Do UI initialization in here, such as creating windows, views, etc. } #[unsafe(method(applicationWillTerminate:))] fn will_terminate(&self, _notification: &NSNotification) { println!("will terminate!"); // Tear down your application state here. `NSApplicationMain` and // `UIApplicationMain` will not return, this is (roughly) the last // thing that will be called. } } ); // AppKit (macOS). #[cfg(target_os = "macos")] fn main() { let mtm = objc2::MainThreadMarker::new().unwrap(); let app = objc2_app_kit::NSApplication::sharedApplication(mtm); let delegate: Retained = unsafe { msg_send![AppDelegate::class(), new] }; app.setDelegate(Some(objc2::runtime::ProtocolObject::from_ref(&*delegate))); app.run(); } ``` -------------------------------- ### Application Delegate for Graphical Apps (macOS with Storyboard) Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/run_loop.md This example shows how to initialize an `AppDelegate` class when using a storyboard in a macOS application. It ensures the class is registered with the Objective-C runtime so that the storyboard can instantiate it. `NSApplication::main` is then called to start the application. ```rust use objc2::rc::{Allocated, Retained}; use objc2::{define_class, msg_send, ClassType, Ivars, MainThreadOnly}; use objc2_foundation::{NSNotification, NSObject, NSObjectProtocol}; // Application delegate protocols happens to share a few methods, // we can utilize that to be a bit more platform-generic. #[cfg(target_os = "macos")] use objc2_app_kit::NSApplicationDelegate as DelegateProtocol; #[cfg(not(target_os = "macos"))] use objc2_ui_kit::UIApplicationDelegate as DelegateProtocol; define_class!( // SAFETY: // - NSObject does not have any subclassing requirements. // - `AppDelegate` does not implement `Drop`. #[unsafe(super(NSObject))] #[thread_kind = MainThreadOnly] #[derive(Debug)] struct AppDelegate { // Whatever state you want to store in your delegate. } impl AppDelegate { // Called by `NSApplicationMain`, `UIApplicationMain` // or our `msg_send![AppDelegate::class(), new]`. #[unsafe(method(init))] fn init(this: Allocated) -> Retained { let this = this.set_ivars(Ivars:: { // Initialize state. }); unsafe { msg_send![super(this), init] } } } unsafe impl NSObjectProtocol for AppDelegate {} unsafe impl DelegateProtocol for AppDelegate { #[unsafe(method(applicationDidFinishLaunching:))] fn did_finish_launching(&self, _notification: &NSNotification) { println!("did finish launching!"); // Do UI initialization in here, such as creating windows, views, etc. } #[unsafe(method(applicationWillTerminate:))] fn will_terminate(&self, _notification: &NSNotification) { println!("will terminate!"); // Tear down your application state here. `NSApplicationMain` and // `UIApplicationMain` will not return, this is (roughly) the last // thing that will be called. } } ); // AppKit (macOS), if bundled and using a storyboard. #[cfg(target_os = "macos")] # #[cfg(with_storyboard)] // Hack to make example compile. fn main() { let mtm = objc2::MainThreadMarker::new().unwrap(); // Initialize the class so that the storyboard can see it. // // The name specified in `define_class!`, i.e. "AppDelegate", must // match what's specified in the storyboard. let _cls = AppDelegate::class(); objc2_app_kit::NSApplication::main(mtm); } ``` -------------------------------- ### Run XCTest with `run_xc_test` script Source: https://github.com/madsmtm/objc2/blob/main/examples/testing-helper/README.md Example command to execute the `run_xc_test` binary. Specify the target test type (e.g., 'default_xcode_game') and the test suite (e.g., 'ui_test'). ```bash cargo run --bin run_xc_test -- default_xcode_game ui_test ``` -------------------------------- ### Application Entry Point for Graphical Apps (iOS/tvOS/watchOS/visionOS) Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/run_loop.md This snippet shows the entry point for graphical applications on iOS, tvOS, watchOS, and visionOS. It uses `UIApplication::main` to start the application, specifying the delegate class name. This is the standard way to launch UIKit applications. ```rust use objc2::rc::{Allocated, Retained}; use objc2::{define_class, msg_send, ClassType, Ivars, MainThreadOnly}; use objc2_foundation::{NSNotification, NSObject, NSObjectProtocol}; // Application delegate protocols happens to share a few methods, // we can utilize that to be a bit more platform-generic. #[cfg(target_os = "macos")] use objc2_app_kit::NSApplicationDelegate as DelegateProtocol; #[cfg(not(target_os = "macos"))] use objc2_ui_kit::UIApplicationDelegate as DelegateProtocol; define_class!( // SAFETY: // - NSObject does not have any subclassing requirements. // - `AppDelegate` does not implement `Drop`. #[unsafe(super(NSObject))] #[thread_kind = MainThreadOnly] #[derive(Debug)] struct AppDelegate { // Whatever state you want to store in your delegate. } impl AppDelegate { // Called by `NSApplicationMain`, `UIApplicationMain` // or our `msg_send![AppDelegate::class(), new]`. #[unsafe(method(init))] fn init(this: Allocated) -> Retained { let this = this.set_ivars(Ivars:: { // Initialize state. }); unsafe { msg_send![super(this), init] } } } unsafe impl NSObjectProtocol for AppDelegate {} unsafe impl DelegateProtocol for AppDelegate { #[unsafe(method(applicationDidFinishLaunching:))] fn did_finish_launching(&self, _notification: &NSNotification) { println!("did finish launching!"); // Do UI initialization in here, such as creating windows, views, etc. } #[unsafe(method(applicationWillTerminate:))] fn will_terminate(&self, _notification: &NSNotification) { println!("will terminate!"); // Tear down your application state here. `NSApplicationMain` and // `UIApplicationMain` will not return, this is (roughly) the last // thing that will be called. } } ); // UIKit (iOS/tvOS/watchOS/visionOS). #[cfg(not(target_os = "macos"))] fn main() { let mtm = objc2::MainThreadMarker::new().unwrap(); let delegate_class = objc2_foundation::NSString::from_class(AppDelegate::class()); objc2_ui_kit::UIApplication::main(None, Some(&delegate_class), mtm); } ``` -------------------------------- ### Stream FSKit Example Logs Source: https://github.com/madsmtm/objc2/blob/main/examples/fskit/README.md Streams debug logs for the FSKit example extension. This command should be run in a separate terminal to monitor the extension's activity. ```sh /usr/bin/log stream --predicate 'subsystem == "fskit-example"' --style compact --level debug ``` -------------------------------- ### Cross-compiling Setup for aarch64-apple-darwin Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/cross_compiling.md This snippet shows the environment variables and commands needed to set up a cross-compilation environment for the aarch64-apple-darwin target. It includes adding the target, setting the linker, specifying the SDK root, and configuring C compilers if necessary. ```bash $ rustup target add aarch64-apple-darwin $ export CARGO_TARGET_AARCH64_APPLE_DARWIN_LINKER=rust-lld $ export SDKROOT=$(pwd)/MacOSX.sdk $ export CC=clang CXX=clang++ AR=llvm-ar # If compiling C code $ cargo build --target aarch64-apple-darwin ``` -------------------------------- ### Example Translation Config TOML Source: https://github.com/madsmtm/objc2/blob/main/crates/header-translator/README.md A template for the `translation-config.toml` file used when adding a new framework crate. It includes essential fields for framework metadata and version information. ```toml framework = "XXX" crate = "objc2-xxx" required-crates = ["objc2", "objc2-foundation"] macos = "XXX" maccatalyst = "XXX" ios = "XXX" tvos = "XXX" watchos = "XXX" visionos = "XXX" ``` -------------------------------- ### Configure Developer Directory and Libclang Path Source: https://github.com/madsmtm/objc2/blob/main/crates/header-translator/README.md Set environment variables to specify a custom developer directory and libclang path. This is necessary when using a different Xcode version or a custom libclang installation. ```console export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer export LIBCLANG_PATH=/path/to/custom/installation/usr/lib/libclang.dylib export CPATH=/path/to/custom/installation/usr/lib/clang/14.0.0/include/ ``` -------------------------------- ### Mark All Methods of a Class/Protocol as Safe Source: https://github.com/madsmtm/objc2/blob/main/crates/header-translator/README.md Example TOML configurations for marking all methods within a class or protocol as safe. This is a broader setting than marking individual methods. ```toml # Mark all methods as safe class.MyClass.unsafe = false protocol.MyClass.unsafe = false ``` -------------------------------- ### Mark Specific Method as Safe Source: https://github.com/madsmtm/objc2/blob/main/crates/header-translator/README.md Example TOML configurations for marking specific functions, methods, or properties as safe. This overrides the default unsafe status. ```toml # Mark specific function/method as safe fn.my_method.unsafe = false class.MyClass.methods.myProperty.unsafe = false protocol.MyProtocol.methods."myMethodWithArg:".unsafe = false # Uses selector name ``` -------------------------------- ### Create Dummy Block Device Source: https://github.com/madsmtm/objc2/blob/main/examples/fskit/README.md Creates a dummy file and constructs a new block device from it using hdiutil. This is a prerequisite for mounting the FSKit filesystem. ```sh mkfile -n 100m ./target/dummy-block-device DISK="$(basename $(hdiutil attach -imagekey diskimage-class=CRawDiskImage -nomount ./target/dummy-block-device))" echo $DISK ``` -------------------------------- ### Creating and Using NSData with Manual Memory Management Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/layered_safety.md Shows how to create an NSData instance and call its 'length' method using msg_send. Manual 'release' is required. ```rust use objc2::ffi::NSUInteger; use objc2::runtime::NSObject; use objc2::{class, msg_send}; let obj: *const NSObject = unsafe { msg_send![class!(NSData), new] }; let length: NSUInteger = unsafe { msg_send![obj, length] }; // We have to specify the return type here, see layer 4 below let _: () = unsafe { msg_send![obj, release] }; ``` -------------------------------- ### Build Script to Compile Swift Library and Link in Rust Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/swift.md This build script demonstrates how to compile a Swift file into a library using `swiftc` and then instruct Cargo to link against it. ```rust // build.rs fn main() { // Somehow invoke `swiftc` to compile the library. // You probably want to use a helper library for this! let status = std::process::Command::new("swiftc") .arg("foo.swift") .arg("-emit-library") .status() .unwrap(); assert!(status.success()); // And somehow tell Cargo to link the library. println!("cargo::rustc-link-lib=foo"); } ``` -------------------------------- ### Create Mount Target Directory Source: https://github.com/madsmtm/objc2/blob/main/examples/fskit/README.md Creates a directory to serve as the mount target for the FSKit filesystem. ```sh mkdir /tmp/fskit-mount-target ``` -------------------------------- ### Rust Example of Documented Safety Requirement Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/frameworks_soundness.md This Rust code demonstrates how to document a safety requirement for a function. The `unsafe` keyword and documentation comments clearly state the precondition that the input number must not be 42. ```rust /// # Safety /// /// The given number must not be `42`. pub unsafe fn takes_anything_but_42(num: i32) { if num == 42 { // SAFETY: Upheld by caller. unsafe { std::hint::unreachable_unchecked() } } } ``` -------------------------------- ### Migrating Dependencies from icrate to objc2-* Crates Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/FRAMEWORKS_CHANGELOG.md Illustrates the transition from using the monolithic 'icrate' dependency to the new modular 'objc2-*' crates. Shows how feature flags have changed and how to update your Cargo.toml. ```toml # Before [dependencies.icrate] version = "0.1.0" features = [ "Foundation", "Foundation_NSNotification", "Foundation_NSString", "Foundation_NSThread", "Foundation_NSArray", "Foundation_NSMutableArray", "AppKit", "AppKit_NSApplication", ] # After # Moved to `objc2-foundation` and `objc2-app-kit` crates. [dependencies] # Removed `Foundation_NSMutableArray`, it is included via `NSArray`. # Added `NSObject` as the `NSCopying` protocol comes from there. objc2-foundation = { version = "0.2", features = ["NSNotification", "NSString", "NSThread", "NSObject", "NSArray"] } # Added `NSResponder` as it's required by `NSApplication`. # Added `NSRunningApplication` as a lot of application constants come from here. objc2-app-kit = { version = "0.2", features = ["NSResponder", "NSApplication", "NSRunningApplication"] } ``` -------------------------------- ### Running a Run Loop Periodically in Non-Graphical Apps Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/run_loop.md Use this snippet in non-graphical applications to get the current thread's run loop and run it periodically. This allows scheduled work like timers and sources to complete. Ensure necessary timers, sources, etc., are set up before running. ```rust use objc2_foundation::{NSDate, NSDefaultRunLoopMode, NSRunLoop}; fn main() { let run_loop = NSRunLoop::currentRunLoop(); // Set up timers, sources, etc. let mut date = NSDate::now(); // Run for roughly 10 seconds for i in 0..10 { date = date.dateByAddingTimeInterval(1.0); run_loop.runUntilDate(&date); // Do something every second (if there are any sources attached) } } ``` -------------------------------- ### Build and Fuzz with AFL++ Source: https://github.com/madsmtm/objc2/blob/main/crates/test-fuzz/README.md Build the fuzz target with AFL++ features enabled and then run the fuzzing process. This involves specifying input and output directories for AFL++. ```sh cargo afl build --bin $fuzz_target --features=afl,fuzz-all --release cargo afl fuzz -i crates/test-fuzz/corpus/$fuzz_target -o crates/test-fuzz/afl -- target/release/$fuzz_target ``` -------------------------------- ### Using Autogenerated NSData Interface from objc2-foundation Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/layered_safety.md Demonstrates using the pre-defined NSData interface from the objc2-foundation crate, which is typically autogenerated from Objective-C headers. ```rust use objc2_foundation::NSData; let obj = NSData::new(); let length = obj.length(); ``` -------------------------------- ### View All Registered FSKit Entries Source: https://github.com/madsmtm/objc2/blob/main/examples/fskit/README.md This command displays all registered FSKit entries, formatted for readability by splitting on '------' and printing each entry. ```sh /System/Library/Frameworks/CoreServices.framework/Versions/Current/Frameworks/LaunchServices.framework/Versions/Current/Support/lsregister -dump | awk 'BEGIN{RS="------"; FS="\n"} /com\.apple\.fskit\.fsmodule/ {print $0}' ``` -------------------------------- ### Run Header Translator for All Crates Source: https://github.com/madsmtm/objc2/blob/main/crates/header-translator/README.md Execute the header translator to process all available framework crates. This command is typically run in release mode. ```console cargo run --bin header-translator --release all ``` -------------------------------- ### Binary Dependency Configuration Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/FRAMEWORKS_CHANGELOG.md For binary crates, the 'all' feature can be removed, simplifying the dependency declaration. ```toml # Before [dependencies] objc2-foundation = { version = "0.2", features = ["all"] } # After [dependencies] # Removed "all" feature objc2-foundation = "0.3" ``` -------------------------------- ### Run Framework Features Check Tool Source: https://github.com/madsmtm/objc2/blob/main/crates/header-translator/README.md Execute the check_framework_features tool to verify that changes to framework features result in compilable code. This is a crucial step during development and improvement of the translator. ```console cargo run --bin check_framework_features ``` -------------------------------- ### Library Dependency Configuration Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/FRAMEWORKS_CHANGELOG.md For library crates, it's recommended to disable default features and explicitly list desired features to manage compile times for consumers. ```toml # Before [dependencies] objc2-foundation = { version = "0.2", features = [ "NSNotification", "NSString", "NSThread", "NSObject", "NSArray", ] } # After [dependencies] # Added `default-features = false` objc2-foundation = { version = "0.3", default-features = false, features = [ "NSNotification", "NSString", "NSThread", "NSObject", "NSArray", ] } ``` -------------------------------- ### Run Fuzz Target with cargo-fuzz Source: https://github.com/madsmtm/objc2/blob/main/crates/test-fuzz/README.md Execute a fuzz target using `cargo-fuzz`. Ensure the `--fuzz-dir` argument points to the correct directory. ```sh cargo fuzz run --fuzz-dir=./crates/test-fuzz/ --features=fuzz-all $fuzz_target ``` -------------------------------- ### Mount FSKit Filesystem (macOS 26+) Source: https://github.com/madsmtm/objc2/blob/main/examples/fskit/README.md Mounts the FSKit filesystem directly using a path, bypassing the need for a block device. This method is available on macOS 26 and later and requires specific FSKit resource handling. ```sh mount -F -t fskitexample ~/some-path /tmp/fskit-mount-target ``` -------------------------------- ### Main Thread Only Class Usage Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/FRAMEWORKS_CHANGELOG.md Demonstrates how to use UI-related classes and protocols that are now marked as `MainThreadOnly` or `IsMainThreadOnly`. Requires acquiring a `MainThreadMarker` before use. ```rust // Before let app = unsafe { NSApplication::sharedApplication() }; let view = unsafe { NSView::initWithFrame(NSView::alloc(), frame) }; // Do something with `app` and `view` // After let mtm = MainThreadMarker::new().unwrap(); let app = unsafe { NSApplication::sharedApplication(mtm) }; let view = unsafe { NSView::initWithFrame(mtm.alloc(), frame) }; // Do something with `app` and `view` ``` -------------------------------- ### Basic Message Sending with NSObject Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/layered_safety.md Demonstrates sending a 'hash' message to an NSObject instance using msg_send. Requires manual memory management. ```rust use objc2::runtime::NSObject; use objc2::{class, msg_send}; let obj = &*objc2::runtime::NSObject::new(); let hash_code: objc2::ffi::NSUInteger = unsafe { msg_send![obj, hash] }; ``` -------------------------------- ### Custom KVO Observer Implementation Source: https://github.com/madsmtm/objc2/blob/main/crates/objc2/src/topics/kvo.md This snippet demonstrates how to create a custom observer class (`MyObserver`) in Rust using objc2 to handle Key-Value Observing. It includes setting up the observer, registering it with an object, and defining the callback for when observed properties change. Ensure the observed object is retained and the observer is properly unregistered in `Drop`. ```rust use core::ffi::c_void; use core::ptr; use objc2::rc::Retained; use objc2::runtime::AnyObject; use objc2::{define_class, msg_send, AnyThread, ClassType, Ivars}; use objc2_foundation::{ ns_string, NSCopying, NSDictionary, NSKeyValueChangeKey, NSKeyValueObservingOptions, NSObject, NSObjectNSKeyValueObserverRegistration, NSObjectProtocol, NSString, }; define_class!( // SAFETY: // - The superclass NSObject does not have any subclassing requirements. // - MyObserver implements `Drop` and ensures that: // - It does not call an overridden method. // - It does not `retain` itself. #[unsafe(super(NSObject))] struct MyObserver { object: Retained, key_path: Retained, handler: Box) + 'static>, } impl MyObserver { #[unsafe(method(observeValueForKeyPath:ofObject:change:context:))] fn observe_value( &self, _key_path: Option<&NSString>, _object: Option<&AnyObject>, change: Option<&NSDictionary>, _context: *mut c_void, ) { if let Some(change) = change { (self.handler())(change); } else { (self.handler())(&NSDictionary::new()); } } } unsafe impl NSObjectProtocol for MyObserver {} ); impl MyObserver { fn new( object: Retained, key_path: &NSString, options: NSKeyValueObservingOptions, // TODO: Thread safety? This probably depends on whether the observed // object is later moved to another thread. handler: impl Fn(&NSDictionary) + 'static + Send + Sync, ) -> Retained { let observer = Self::alloc().set_ivars(Ivars:: { object, key_path: key_path.copy(), handler: Box::new(handler), }); let observer: Retained = unsafe { msg_send![super(observer), init] }; // SAFETY: We make sure to un-register the observer before it's deallocated. // // Passing `NULL` as the `context` parameter here is fine, as the observer does not // have any subclasses, and the superclass (NSObject) is not observing anything. unsafe { observer.object().addObserver_forKeyPath_options_context( &observer, key_path, options, ptr::null_mut(), ); } observer } } impl Drop for MyObserver { fn drop(&mut self) { unsafe { self.object().removeObserver_forKeyPath(&self, &self.key_path()) }; } } fn main() { let obj; # obj = NSObject::new(); let _observer = MyObserver::new( obj, ns_string!("myKeyPath"), NSKeyValueObservingOptions::New | NSKeyValueObservingOptions::Old, |change| { println!("object changed: {:?}", change); }, ); // Do something that triggers the observer } ``` -------------------------------- ### Mount Block Device with FSKit Source: https://github.com/madsmtm/objc2/blob/main/examples/fskit/README.md Mounts the FSKit filesystem using the previously created block device. This is the standard method for mounting the FSKit filesystem. ```sh mount -F -t fskitexample $DISK /tmp/fskit-mount-target ```