Skip to main content

objc2/rc/
weak.rs

1use alloc::boxed::Box;
2use core::cell::UnsafeCell;
3use core::fmt;
4use core::marker::PhantomData;
5use core::ptr;
6use std::panic::{RefUnwindSafe, UnwindSafe};
7
8use super::Retained;
9use crate::runtime::AnyObject;
10use crate::{ffi, Message};
11
12/// A weak pointer to an Objective-C reference counted object.
13///
14/// The object is allowed to be deallocated while the weak pointer is alive,
15/// though the backing allocation for the object can only be released once all
16/// weak pointers are gone.
17///
18/// Useful for breaking reference cycles and safely checking whether an
19/// object has been deallocated.
20///
21///
22/// # Comparison to `std` types
23///
24/// This is the Objective-C equivalent of [`sync::Weak`] from the standard
25/// library, and hence is only usable on types where `Retained<T>` acts like
26/// [`sync::Arc`], a.k.a. on non-mutable types.
27///
28/// [`sync::Weak`]: std::sync::Weak
29/// [`sync::Arc`]: std::sync::Arc
30#[repr(transparent)] // This is not a public guarantee
31#[doc(alias = "WeakId")] // Previous name
32pub struct Weak<T: ?Sized> {
33    /// We give the runtime the address to this box, so that it can modify it
34    /// even if the `Weak` is moved.
35    ///
36    /// Loading may modify the pointer through a shared reference, so we use
37    /// an UnsafeCell to get a *mut without self being mutable.
38    ///
39    /// Remember that any thread may actually modify the inner value
40    /// concurrently, but as long as we only use it through the `objc_XXXWeak`
41    /// methods, all access is behind a lock.
42    ///
43    /// TODO: Verify the need for UnsafeCell?
44    /// TODO: Investigate if we can avoid some allocations using `Pin`.
45    /// TODO: Add derive(CoercePointee) once this doesn't Box internally.
46    inner: Box<UnsafeCell<*mut AnyObject>>,
47    /// Weak inherits variance, dropck and various marker traits from
48    /// `Retained<T>`.
49    item: PhantomData<Retained<T>>,
50}
51
52/// Fully-deprecated type-alias to [`Weak`].
53#[deprecated(since = "0.6.0", note = "Renamed to `Weak`.")]
54pub type WeakId<T> = Weak<T>;
55
56impl<T: Message> Weak<T> {
57    /// Construct a new weak pointer that references the given object.
58    #[doc(alias = "objc_initWeak")]
59    #[inline]
60    pub fn new(obj: &T) -> Self {
61        // SAFETY: Pointer is valid since it came from a reference.
62        unsafe { Self::new_inner(obj) }
63    }
64
65    /// Construct a new weak pointer that references the given [`Retained`].
66    #[doc(alias = "objc_initWeak")]
67    #[deprecated = "use `Weak::from_retained` instead"]
68    #[inline]
69    pub fn from_id(obj: &Retained<T>) -> Self {
70        Self::from_retained(obj)
71    }
72
73    /// Construct a new weak pointer that references the given [`Retained`].
74    #[doc(alias = "objc_initWeak")]
75    #[inline]
76    pub fn from_retained(obj: &Retained<T>) -> Self {
77        // SAFETY: Pointer is valid since it came from `Retained`.
78        unsafe { Self::new_inner(Retained::as_ptr(obj)) }
79    }
80
81    /// Raw constructor.
82    ///
83    ///
84    /// # Safety
85    ///
86    /// The object must be valid or null.
87    unsafe fn new_inner(obj: *const T) -> Self {
88        let inner = Box::new(UnsafeCell::new(ptr::null_mut()));
89        // SAFETY: `ptr` will never move, and the caller verifies `obj`
90        let _ = unsafe { ffi::objc_initWeak(inner.get(), (obj as *mut T).cast()) };
91        Self {
92            inner,
93            item: PhantomData,
94        }
95    }
96
97    /// Load the object into an [`Retained`] if it still exists.
98    ///
99    /// Returns [`None`] if the object has been deallocated, or the `Weak`
100    /// was created with [`Default::default`].
101    #[doc(alias = "retain")]
102    #[doc(alias = "objc_loadWeak")]
103    #[doc(alias = "objc_loadWeakRetained")]
104    #[inline]
105    pub fn load(&self) -> Option<Retained<T>> {
106        let ptr = self.inner.get();
107        let obj = unsafe { ffi::objc_loadWeakRetained(ptr) }.cast();
108        // SAFETY: The object has +1 retain count
109        unsafe { Retained::from_raw(obj) }
110    }
111
112    // TODO: Add `autorelease(&self, pool) -> Option<&T>` using `objc_loadWeak`?
113}
114
115impl<T: ?Sized> Drop for Weak<T> {
116    /// Destroys the weak pointer.
117    #[doc(alias = "objc_destroyWeak")]
118    #[inline]
119    fn drop(&mut self) {
120        unsafe { ffi::objc_destroyWeak(self.inner.get()) }
121    }
122}
123
124// TODO: Add ?Sized
125impl<T: Message> Clone for Weak<T> {
126    /// Make a clone of the weak pointer that points to the same object.
127    #[doc(alias = "objc_copyWeak")]
128    fn clone(&self) -> Self {
129        let ptr = Box::new(UnsafeCell::new(ptr::null_mut()));
130        unsafe { ffi::objc_copyWeak(ptr.get(), self.inner.get()) };
131        Self {
132            inner: ptr,
133            item: PhantomData,
134        }
135    }
136}
137
138// TODO: Add ?Sized
139impl<T: Message> Default for Weak<T> {
140    /// Constructs a new weak pointer that doesn't reference any object.
141    ///
142    /// Calling [`Self::load`] on the return value always gives [`None`].
143    #[inline]
144    fn default() -> Self {
145        // SAFETY: The pointer is null
146        unsafe { Self::new_inner(ptr::null()) }
147    }
148}
149
150impl<T: ?Sized> fmt::Debug for Weak<T> {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        // Note: We intentionally don't try to debug-print the value, since
153        // that could lead to cycles. See:
154        // https://github.com/rust-lang/rust/pull/90291
155        write!(f, "(Weak)")
156    }
157}
158
159// SAFETY: Same as `std::sync::Weak<T>`.
160unsafe impl<T: ?Sized + Sync + Send> Sync for Weak<T> {}
161
162// SAFETY: Same as `std::sync::Weak<T>`.
163unsafe impl<T: ?Sized + Sync + Send> Send for Weak<T> {}
164
165// Same as `std::sync::Weak<T>`.
166impl<T: ?Sized> Unpin for Weak<T> {}
167
168// Same as `std::sync::Weak<T>`.
169impl<T: ?Sized + RefUnwindSafe> RefUnwindSafe for Weak<T> {}
170
171// Same as `std::sync::Weak<T>`.
172impl<T: ?Sized + RefUnwindSafe> UnwindSafe for Weak<T> {}
173
174impl<T: Message> From<&T> for Weak<T> {
175    #[inline]
176    fn from(obj: &T) -> Self {
177        Weak::new(obj)
178    }
179}
180
181impl<T: Message> From<&Retained<T>> for Weak<T> {
182    #[inline]
183    fn from(obj: &Retained<T>) -> Self {
184        Weak::from_retained(obj)
185    }
186}
187
188impl<T: Message> From<Retained<T>> for Weak<T> {
189    #[inline]
190    fn from(obj: Retained<T>) -> Self {
191        Weak::from_retained(&obj)
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use core::mem;
198
199    use super::*;
200    use crate::rc::{RcTestObject, ThreadTestData};
201    use crate::runtime::NSObject;
202
203    #[test]
204    fn test_weak() {
205        let obj = RcTestObject::new();
206        let mut expected = ThreadTestData::current();
207
208        let weak = Weak::from(&obj);
209        expected.assert_current();
210
211        let strong = weak.load().unwrap();
212        expected.try_retain += 1;
213        expected.assert_current();
214        assert!(ptr::eq(&*strong, &*obj));
215
216        drop(obj);
217        drop(strong);
218        expected.release += 2;
219        expected.drop += 1;
220        expected.assert_current();
221
222        if cfg!(not(feature = "gnustep-1-7")) {
223            // This loads the object on GNUStep for some reason??
224            assert!(weak.load().is_none());
225            expected.assert_current();
226        }
227
228        drop(weak);
229        expected.assert_current();
230    }
231
232    #[test]
233    fn test_weak_clone() {
234        let obj = RcTestObject::new();
235        let mut expected = ThreadTestData::current();
236
237        let weak = Weak::from(&obj);
238        expected.assert_current();
239
240        let weak2 = weak.clone();
241        if cfg!(target_vendor = "apple") {
242            expected.try_retain += 1;
243            expected.release += 1;
244        }
245        expected.assert_current();
246
247        let strong = weak.load().unwrap();
248        expected.try_retain += 1;
249        expected.assert_current();
250        assert!(ptr::eq(&*strong, &*obj));
251
252        let strong2 = weak2.load().unwrap();
253        expected.try_retain += 1;
254        expected.assert_current();
255        assert!(ptr::eq(&*strong, &*strong2));
256
257        drop(weak);
258        drop(weak2);
259        expected.assert_current();
260    }
261
262    #[test]
263    fn test_weak_default() {
264        let weak: Weak<RcTestObject> = Weak::default();
265        assert!(weak.load().is_none());
266        drop(weak);
267    }
268
269    #[repr(C)]
270    struct MyObject<'a> {
271        inner: NSObject,
272        p: PhantomData<&'a str>,
273    }
274
275    /// Test that `Weak<T>` is covariant over `T`.
276    #[allow(unused)]
277    fn assert_variance<'a, 'b>(obj: &'a Weak<MyObject<'static>>) -> &'a Weak<MyObject<'b>> {
278        obj
279    }
280
281    #[test]
282    fn test_size_of() {
283        assert_eq!(
284            mem::size_of::<Option<Weak<NSObject>>>(),
285            mem::size_of::<*const ()>()
286        );
287    }
288}