objc2-foundation 0.2.0-alpha.6

Bindings to the Objective-C Cocoa Foundation framework
Documentation
use core::marker::PhantomData;

use objc2::rc::{Id, Shared};
use objc2::{msg_send, msg_send_bool, msg_send_id};

use crate::{NSObject, NSString};

extern_class! {
    /// A thread of execution.
    ///
    /// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsthread?language=objc).
    #[derive(Debug, PartialEq, Eq, Hash)]
    unsafe pub struct NSThread: NSObject;
}

unsafe impl Send for NSThread {}
unsafe impl Sync for NSThread {}

impl NSThread {
    /// Returns the [`NSThread`] object representing the current thread.
    pub fn current() -> Id<Self, Shared> {
        // TODO: currentThread is @property(strong), what does that mean?
        // TODO: Always available?
        unsafe { msg_send_id![Self::class(), currentThread].unwrap() }
    }

    /// Returns the [`NSThread`] object representing the main thread.
    pub fn main() -> Id<NSThread, Shared> {
        // TODO: mainThread is @property(strong), what does that mean?
        // The main thread static may not have been initialized
        // This can at least fail in GNUStep!
        unsafe { msg_send_id![Self::class(), mainThread] }.expect("Could not retrieve main thread.")
    }

    /// Returns `true` if the thread is the main thread.
    pub fn is_main(&self) -> bool {
        unsafe { msg_send_bool![self, isMainThread] }
    }

    /// The name of the thread.
    pub fn name(&self) -> Option<Id<NSString, Shared>> {
        unsafe { msg_send_id![self, name] }
    }

    fn new() -> Id<Self, Shared> {
        unsafe { msg_send_id![Self::class(), new] }.unwrap()
    }

    fn start(&self) {
        unsafe { msg_send![self, start] }
    }
}

/// Whether the application is multithreaded according to Cocoa.
pub fn is_multi_threaded() -> bool {
    unsafe { msg_send_bool![NSThread::class(), isMultiThreaded] }
}

/// Whether the current thread is the main thread.
pub fn is_main_thread() -> bool {
    unsafe { msg_send_bool![NSThread::class(), isMainThread] }
}

#[allow(unused)]
fn make_multithreaded() {
    let thread = NSThread::new();
    thread.start();
    // Don't bother waiting for it to complete!
}

/// A marker type taken by functions that can only be executed on the main
/// thread.
///
/// By design, this is neither [`Send`] nor [`Sync`], and can only be created
/// on the main thread, meaning that if you're holding this, you know you're
/// running on the main thread.
///
/// See the following links for more information on main-thread-only APIs:
/// - [Main Thread Only APIs on OS X](https://www.dribin.org/dave/blog/archives/2009/02/01/main_thread_apis/)
/// - [Thread Safety Summary](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1)
/// - [Are the Cocoa Frameworks Thread Safe?](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47)
/// - [Technical Note TN2028 - Threading Architectures](https://developer.apple.com/library/archive/technotes/tn/tn2028.html#//apple_ref/doc/uid/DTS10003065)
/// - [Thread Management](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html)
/// - [Mike Ash' article on thread safety](https://www.mikeash.com/pyblog/friday-qa-2009-01-09.html)
///
///
/// # Examples
///
/// Use when designing APIs that are only safe to use on the main thread:
///
/// ```no_run
/// use objc2::msg_send;
/// use objc2::runtime::Object;
/// use objc2_foundation::MainThreadMarker;
/// # let obj = 0 as *const Object;
///
/// // This action requires the main thread, so we take a marker as parameter.
/// // It signals clearly to users "this requires the main thread".
/// unsafe fn do_thing(obj: *const Object, _mtm: MainThreadMarker) {
///     msg_send![obj, someActionThatRequiresTheMainThread]
/// }
///
/// // Usage
/// let mtm = MainThreadMarker::new().unwrap();
/// unsafe { do_thing(obj, mtm) }
/// ```
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
// This is valid to Copy because it's still `!Send` and `!Sync`.
pub struct MainThreadMarker {
    // No lifetime information needed; the main thread is static and available
    // throughout the entire program!
    _priv: PhantomData<*mut ()>,
}

impl MainThreadMarker {
    /// Construct a new [`MainThreadMarker`].
    ///
    /// Returns [`None`] if the current thread was not the main thread.
    pub fn new() -> Option<Self> {
        if is_main_thread() {
            // SAFETY: We just checked that we are running on the main thread.
            Some(unsafe { Self::new_unchecked() })
        } else {
            None
        }
    }

    /// Construct a new [`MainThreadMarker`] without first checking whether
    /// the current thread is the main one.
    ///
    /// # Safety
    ///
    /// The current thread must be the main thread.
    pub unsafe fn new_unchecked() -> Self {
        // SAFETY: Upheld by caller
        Self { _priv: PhantomData }
    }
}

#[cfg(test)]
mod tests {
    use core::panic::{RefUnwindSafe, UnwindSafe};

    use super::*;

    #[test]
    #[cfg_attr(
        feature = "gnustep-1-7",
        ignore = "Retrieving main thread is weirdly broken, only works with --test-threads=1"
    )]
    fn test_main_thread() {
        let current = NSThread::current();
        let main = NSThread::main();

        assert!(main.is_main());

        if main == current {
            assert!(current.is_main());
            assert!(is_main_thread());
        } else {
            assert!(!current.is_main());
            assert!(!is_main_thread());
        }
    }

    #[test]
    fn test_not_main_thread() {
        let res = std::thread::spawn(|| (is_main_thread(), NSThread::current().is_main()))
            .join()
            .unwrap();
        assert_eq!(res, (false, false));
    }

    #[test]
    fn test_main_thread_auto_traits() {
        fn assert_traits<T: Unpin + UnwindSafe + RefUnwindSafe + Sized>() {}

        assert_traits::<MainThreadMarker>()
    }
}