| 
 | 1 | +//! Creating a `CFRunLoopTimer` and attaching it with a runloop.  | 
 | 2 | +
  | 
 | 3 | +use std::sync::Arc;  | 
 | 4 | +use std::{cell::Cell, ffi::c_void};  | 
 | 5 | + | 
 | 6 | +use objc2_core_foundation::{  | 
 | 7 | +    kCFAllocatorDefault, kCFRunLoopCommonModes, CFAbsoluteTime, CFAbsoluteTimeGetCurrent, CFIndex,  | 
 | 8 | +    CFRetained, CFRunLoop, CFRunLoopTimer, CFRunLoopTimerContext, CFTimeInterval,  | 
 | 9 | +};  | 
 | 10 | + | 
 | 11 | +fn main() {  | 
 | 12 | +    let rl = CFRunLoop::main().unwrap();  | 
 | 13 | + | 
 | 14 | +    let iterations = Cell::new(0);  | 
 | 15 | + | 
 | 16 | +    let callback = {  | 
 | 17 | +        let rl = rl.clone();  | 
 | 18 | +        move |timer: &CFRunLoopTimer| {  | 
 | 19 | +            println!("Timer called: {}", iterations.get());  | 
 | 20 | + | 
 | 21 | +            if 10 <= iterations.get() {  | 
 | 22 | +                // Remove the timer.  | 
 | 23 | +                rl.remove_timer(Some(timer), unsafe { kCFRunLoopCommonModes });  | 
 | 24 | + | 
 | 25 | +                // Stop the run loop explicitly  | 
 | 26 | +                // (the main run-loop won't stop otherwise).  | 
 | 27 | +                println!("Stopping run loop");  | 
 | 28 | +                rl.stop();  | 
 | 29 | +            }  | 
 | 30 | + | 
 | 31 | +            iterations.set(iterations.get() + 1);  | 
 | 32 | +        }  | 
 | 33 | +    };  | 
 | 34 | + | 
 | 35 | +    // SAFETY: The timer is added to a run loop on the same thread.  | 
 | 36 | +    let timer = unsafe { create_timer_unchecked(0.0, 0.1, 0, callback) };  | 
 | 37 | +    rl.add_timer(Some(&timer), unsafe { kCFRunLoopCommonModes });  | 
 | 38 | + | 
 | 39 | +    // Add a single-shot timer from another thread.  | 
 | 40 | +    let fire_date = CFAbsoluteTimeGetCurrent() + 0.5;  | 
 | 41 | +    let timer = create_timer(fire_date, 0.0, 0, |_| {  | 
 | 42 | +        println!("Fired one-shot timer after 0.5 seconds");  | 
 | 43 | +        // Still runs on the main thread.  | 
 | 44 | +        assert_eq!(CFRunLoop::current().unwrap(), CFRunLoop::main().unwrap());  | 
 | 45 | +    });  | 
 | 46 | +    rl.add_timer(Some(&timer), unsafe { kCFRunLoopCommonModes });  | 
 | 47 | + | 
 | 48 | +    println!("Starting run loop");  | 
 | 49 | +    CFRunLoop::run();  | 
 | 50 | +}  | 
 | 51 | + | 
 | 52 | +/// Create a new `CFRunLoopTimer`.  | 
 | 53 | +fn create_timer<F: Fn(&CFRunLoopTimer) + Send + Sync + 'static>(  | 
 | 54 | +    fire_date: CFAbsoluteTime,  | 
 | 55 | +    interval: CFTimeInterval,  | 
 | 56 | +    order: CFIndex,  | 
 | 57 | +    callback: F,  | 
 | 58 | +) -> CFRetained<CFRunLoopTimer> {  | 
 | 59 | +    // SAFETY: The callback is `Send + Sync`.  | 
 | 60 | +    unsafe { create_timer_unchecked(fire_date, interval, order, callback) }  | 
 | 61 | +}  | 
 | 62 | + | 
 | 63 | +/// Create a new `CFRunLoopTimer`, without enforcing the callback to be  | 
 | 64 | +/// thread-safe.  | 
 | 65 | +///  | 
 | 66 | +/// # Safety  | 
 | 67 | +///  | 
 | 68 | +/// The callback must be either `Send` + `Sync`, or the timer must only be  | 
 | 69 | +/// added to a run loop that runs on the current thread.  | 
 | 70 | +unsafe fn create_timer_unchecked<F: Fn(&CFRunLoopTimer) + 'static>(  | 
 | 71 | +    fire_date: CFAbsoluteTime,  | 
 | 72 | +    interval: CFTimeInterval,  | 
 | 73 | +    order: CFIndex,  | 
 | 74 | +    callback: F,  | 
 | 75 | +) -> CFRetained<CFRunLoopTimer> {  | 
 | 76 | +    // We use an `Arc` here to make sure that the reference-counting of the  | 
 | 77 | +    // signal container is atomic (`Retained`/`CFRetained` would be valid  | 
 | 78 | +    // alternatives too).  | 
 | 79 | +    let callback = Arc::new(callback);  | 
 | 80 | + | 
 | 81 | +    unsafe extern "C-unwind" fn retain<F>(info: *const c_void) -> *const c_void {  | 
 | 82 | +        // SAFETY: The pointer was passed to `CFRunLoopTimerContext.info` below.  | 
 | 83 | +        unsafe { Arc::increment_strong_count(info.cast::<F>()) };  | 
 | 84 | +        info  | 
 | 85 | +    }  | 
 | 86 | +    unsafe extern "C-unwind" fn release<F>(info: *const c_void) {  | 
 | 87 | +        // SAFETY: The pointer was passed to `CFRunLoopTimerContext.info` below.  | 
 | 88 | +        unsafe { Arc::decrement_strong_count(info.cast::<F>()) };  | 
 | 89 | +    }  | 
 | 90 | + | 
 | 91 | +    unsafe extern "C-unwind" fn callout<F: Fn(&CFRunLoopTimer)>(  | 
 | 92 | +        timer: *mut CFRunLoopTimer,  | 
 | 93 | +        info: *mut c_void,  | 
 | 94 | +    ) {  | 
 | 95 | +        // SAFETY: The timer is valid for at least the duration of the callback.  | 
 | 96 | +        let timer = unsafe { &*timer };  | 
 | 97 | + | 
 | 98 | +        // SAFETY: The pointer was passed to `CFRunLoopTimerContext.info` below.  | 
 | 99 | +        let callback = unsafe { &*info.cast::<F>() };  | 
 | 100 | + | 
 | 101 | +        // Call the provided closure.  | 
 | 102 | +        callback(timer);  | 
 | 103 | +    }  | 
 | 104 | + | 
 | 105 | +    // This is marked `mut` to match the signature of `CFRunLoopTimerCreate`,  | 
 | 106 | +    // but the information is copied, and not actually mutated.  | 
 | 107 | +    let mut context = CFRunLoopTimerContext {  | 
 | 108 | +        version: 0,  | 
 | 109 | +        // This pointer is retained by CF on creation.  | 
 | 110 | +        info: Arc::as_ptr(&callback) as *mut c_void,  | 
 | 111 | +        retain: Some(retain::<F>),  | 
 | 112 | +        release: Some(release::<F>),  | 
 | 113 | +        copyDescription: None,  | 
 | 114 | +    };  | 
 | 115 | + | 
 | 116 | +    // SAFETY: The retain/release callbacks are thread-safe, and caller  | 
 | 117 | +    // upholds that the main callback is used in a thread-safe manner.  | 
 | 118 | +    //  | 
 | 119 | +    // `F: 'static`, so extending the lifetime of the closure is fine.  | 
 | 120 | +    unsafe {  | 
 | 121 | +        CFRunLoopTimer::new(  | 
 | 122 | +            kCFAllocatorDefault,  | 
 | 123 | +            fire_date,  | 
 | 124 | +            interval,  | 
 | 125 | +            0, // Documentation says to pass 0 for future compat.  | 
 | 126 | +            order,  | 
 | 127 | +            Some(callout::<F>),  | 
 | 128 | +            &mut context,  | 
 | 129 | +        )  | 
 | 130 | +    }  | 
 | 131 | +    .unwrap()  | 
 | 132 | +}  | 
0 commit comments