use alloc::boxed::Box; use core::cell::UnsafeCell; use core::fmt; use core::marker::PhantomData; use core::ptr; use std::panic::{RefUnwindSafe, UnwindSafe}; use super::{Id, Shared}; use crate::ffi; use crate::Message; /// A pointer type for a weak reference to an Objective-C reference counted /// object. /// /// Allows breaking reference cycles and safely checking whether the object /// has been deallocated. #[repr(transparent)] pub struct WeakId { /// We give the runtime the address to this box, so that it can modify it /// even if the `WeakId` is moved. /// /// Loading may modify the pointer through a shared reference, so we use /// an UnsafeCell to get a *mut without self being mutable. /// /// Remember that any thread may actually modify the inner value /// concurrently, but as long as we only use it through the `objc_XXXWeak` /// methods, all access is behind a lock. /// /// TODO: Verify the need for UnsafeCell? /// TODO: Investigate if we can avoid some allocations using `Pin`. inner: Box>, /// WeakId inherits variance, dropck and various marker traits from /// `Id` because it can be loaded as a shared Id. item: PhantomData>, } impl WeakId { /// Construct a new [`WeakId`] referencing the given shared [`Id`]. #[doc(alias = "objc_initWeak")] #[inline] pub fn new(obj: &Id) -> Self { // Note that taking `&Id` would not be safe since that would // allow loading an `Id` later on. // SAFETY: `obj` is valid unsafe { Self::new_inner(Id::as_ptr(obj)) } } /// # Safety /// /// The object must be valid or null. unsafe fn new_inner(obj: *const T) -> Self { let inner = Box::new(UnsafeCell::new(ptr::null_mut())); // SAFETY: `ptr` will never move, and the caller verifies `obj` let _ = unsafe { ffi::objc_initWeak(inner.get(), (obj as *mut T).cast()) }; Self { inner, item: PhantomData, } } /// Load a shared (and retained) [`Id`] if the object still exists. /// /// Returns [`None`] if the object has been deallocated or was created /// with [`Default::default`]. #[doc(alias = "retain")] #[doc(alias = "objc_loadWeak")] #[doc(alias = "objc_loadWeakRetained")] #[inline] pub fn load(&self) -> Option> { let ptr = self.inner.get(); let obj = unsafe { ffi::objc_loadWeakRetained(ptr) }.cast(); unsafe { Id::new(obj) } } // TODO: Add `autorelease(&self) -> Option<&T>` using `objc_loadWeak`? } impl Drop for WeakId { /// Drops the `WeakId` pointer. #[doc(alias = "objc_destroyWeak")] #[inline] fn drop(&mut self) { unsafe { ffi::objc_destroyWeak(self.inner.get()) } } } // TODO: Add ?Sized impl Clone for WeakId { /// Makes a clone of the `WeakId` that points to the same object. #[doc(alias = "objc_copyWeak")] fn clone(&self) -> Self { let ptr = Box::new(UnsafeCell::new(ptr::null_mut())); unsafe { ffi::objc_copyWeak(ptr.get(), self.inner.get()) }; Self { inner: ptr, item: PhantomData, } } } // TODO: Add ?Sized impl Default for WeakId { /// Constructs a new `WeakId` that doesn't reference any object. /// /// Calling [`Self::load`] on the return value always gives [`None`]. #[inline] fn default() -> Self { // SAFETY: The pointer is null unsafe { Self::new_inner(ptr::null()) } } } /// This implementation follows the same reasoning as `Id`. unsafe impl Sync for WeakId {} /// This implementation follows the same reasoning as `Id`. unsafe impl Send for WeakId {} // Unsure about the Debug bound on T, see std::sync::Weak impl fmt::Debug for WeakId { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "(WeakId)") } } // Underneath this is just a `Box` impl Unpin for WeakId {} // Same as `Id`. impl RefUnwindSafe for WeakId {} // Same as `Id`. impl UnwindSafe for WeakId {} impl From> for WeakId { #[inline] fn from(obj: Id) -> Self { WeakId::new(&obj) } } impl TryFrom> for Id { type Error = (); fn try_from(weak: WeakId) -> Result { weak.load().ok_or(()) } } #[cfg(test)] mod tests { use super::*; use crate::rc::{RcTestObject, ThreadTestData}; use crate::runtime::Object; #[test] fn test_weak() { let obj: Id<_, Shared> = RcTestObject::new().into(); let mut expected = ThreadTestData::current(); let weak = WeakId::new(&obj); expected.assert_current(); let strong = weak.load().unwrap(); expected.try_retain += 1; expected.assert_current(); assert!(ptr::eq(&*strong, &*obj)); drop(obj); drop(strong); expected.release += 2; expected.dealloc += 1; expected.assert_current(); if cfg!(not(feature = "gnustep-1-7")) { // This loads the object on GNUStep for some reason?? assert!(weak.load().is_none()); expected.assert_current(); } drop(weak); expected.assert_current(); } #[test] fn test_weak_clone() { let obj: Id<_, Shared> = RcTestObject::new().into(); let mut expected = ThreadTestData::current(); let weak = WeakId::new(&obj); expected.assert_current(); let weak2 = weak.clone(); if cfg!(feature = "apple") { expected.try_retain += 1; expected.release += 1; } expected.assert_current(); let strong = weak.load().unwrap(); expected.try_retain += 1; expected.assert_current(); assert!(ptr::eq(&*strong, &*obj)); let strong2 = weak2.load().unwrap(); expected.try_retain += 1; expected.assert_current(); assert!(ptr::eq(&*strong, &*strong2)); drop(weak); drop(weak2); expected.assert_current(); } #[test] fn test_weak_default() { let weak: WeakId = WeakId::default(); assert!(weak.load().is_none()); drop(weak); } }