use core::fmt;
use core::marker::PhantomData;
use core::mem::ManuallyDrop;
use core::ops::{Deref, DerefMut};
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr::{self, NonNull};
use super::AutoreleasePool;
use crate::mutability::{IsIdCloneable, IsMutable};
use crate::runtime::{objc_release_fast, objc_retain_fast};
use crate::{ffi, ClassType, Message};
#[repr(transparent)]
#[doc(alias = "id")]
#[doc(alias = "Id")]
#[doc(alias = "StrongPtr")]
pub struct Retained<T: ?Sized> {
ptr: NonNull<T>,
item: PhantomData<T>,
notunwindsafe: PhantomData<&'static mut ()>,
}
pub type Id<T> = Retained<T>;
impl<T: ?Sized> Retained<T> {
#[inline]
pub(crate) unsafe fn new_nonnull(ptr: NonNull<T>) -> Self {
Self {
ptr,
item: PhantomData,
notunwindsafe: PhantomData,
}
}
}
impl<T: ?Sized + Message> Retained<T> {
#[inline]
pub unsafe fn from_raw(ptr: *mut T) -> Option<Self> {
NonNull::new(ptr).map(|ptr| unsafe { Retained::new_nonnull(ptr) })
}
#[deprecated = "use the more descriptive name `Retained::from_raw` instead"]
#[inline]
pub unsafe fn new(ptr: *mut T) -> Option<Self> {
unsafe { Self::from_raw(ptr) }
}
#[inline]
pub fn into_raw(this: Self) -> *mut T {
ManuallyDrop::new(this).ptr.as_ptr()
}
#[inline]
pub fn as_ptr(this: &Self) -> *const T {
this.ptr.as_ptr()
}
#[inline]
#[allow(unknown_lints)] #[allow(clippy::needless_pass_by_ref_mut)]
pub fn as_mut_ptr(this: &mut Self) -> *mut T
where
T: IsMutable,
{
this.ptr.as_ptr()
}
#[inline]
pub(crate) fn consume_as_ptr_option(this: Option<Self>) -> *mut T
where
T: Sized,
{
this.map(|this| Retained::into_raw(this))
.unwrap_or_else(ptr::null_mut)
}
}
impl<T: Message> Retained<T> {
#[inline]
pub unsafe fn cast<U: Message>(this: Self) -> Retained<U> {
let ptr = ManuallyDrop::new(this).ptr.cast();
unsafe { Retained::new_nonnull(ptr) }
}
#[doc(alias = "objc_retain")]
#[inline]
pub unsafe fn retain(ptr: *mut T) -> Option<Retained<T>> {
let res: *mut T = unsafe { objc_retain_fast(ptr.cast()) }.cast();
debug_assert_eq!(res, ptr, "objc_retain did not return the same pointer");
unsafe { Self::from_raw(res) }
}
#[doc(alias = "objc_retainAutoreleasedReturnValue")]
#[inline]
pub unsafe fn retain_autoreleased(ptr: *mut T) -> Option<Retained<T>> {
#[cfg(target_vendor = "apple")]
{
#[cfg(target_arch = "x86_64")]
{
}
#[cfg(target_arch = "arm")]
unsafe {
core::arch::asm!("mov r7, r7", options(nomem, preserves_flags, nostack))
};
#[cfg(all(target_arch = "aarch64", not(feature = "unstable-apple-new")))]
unsafe {
core::arch::asm!("mov fp, fp", options(nomem, preserves_flags, nostack))
};
#[cfg(target_arch = "x86")]
unsafe {
core::arch::asm!("mov ebp, ebp", options(nomem, preserves_flags, nostack))
};
}
let res: *mut T = unsafe { ffi::objc_retainAutoreleasedReturnValue(ptr.cast()) }.cast();
#[cfg(all(target_vendor = "apple", target_arch = "x86_64"))]
{
unsafe { core::arch::asm!("nop", options(nomem, preserves_flags, nostack)) };
}
debug_assert_eq!(
res, ptr,
"objc_retainAutoreleasedReturnValue did not return the same pointer"
);
unsafe { Self::from_raw(res) }
}
#[doc(alias = "objc_autorelease")]
#[must_use = "if you don't intend to use the object any more, drop it as usual"]
#[inline]
pub fn autorelease_ptr(this: Self) -> *mut T {
let ptr = ManuallyDrop::new(this).ptr.as_ptr();
let res: *mut T = unsafe { ffi::objc_autorelease(ptr.cast()) }.cast();
debug_assert_eq!(res, ptr, "objc_autorelease did not return the same pointer");
res
}
#[doc(alias = "objc_autorelease")]
#[must_use = "if you don't intend to use the object any more, drop it as usual"]
#[inline]
#[allow(clippy::needless_lifetimes)]
pub fn autorelease<'p>(this: Self, pool: AutoreleasePool<'p>) -> &'p T {
let ptr = Self::autorelease_ptr(this);
unsafe { pool.ptr_as_ref(ptr) }
}
#[doc(alias = "objc_autorelease")]
#[must_use = "if you don't intend to use the object any more, drop it as usual"]
#[inline]
#[allow(clippy::needless_lifetimes)]
pub fn autorelease_mut<'p>(this: Self, pool: AutoreleasePool<'p>) -> &'p mut T
where
T: IsMutable,
{
let ptr = Self::autorelease_ptr(this);
unsafe { pool.ptr_as_mut(ptr) }
}
#[inline]
pub(crate) fn autorelease_return_option(this: Option<Self>) -> *mut T {
let ptr: *mut T = this
.map(|this| ManuallyDrop::new(this).ptr.as_ptr())
.unwrap_or_else(ptr::null_mut);
let res: *mut T = unsafe { ffi::objc_autoreleaseReturnValue(ptr.cast()) }.cast();
debug_assert_eq!(
res, ptr,
"objc_autoreleaseReturnValue did not return the same pointer"
);
res
}
#[doc(alias = "objc_autoreleaseReturnValue")]
#[must_use = "if you don't intend to use the object any more, drop it as usual"]
#[inline]
pub fn autorelease_return(this: Self) -> *mut T {
Self::autorelease_return_option(Some(this))
}
}
impl<T: ClassType + 'static> Retained<T>
where
T::Super: 'static,
{
#[inline]
pub fn into_super(this: Self) -> Retained<T::Super> {
unsafe { Self::cast::<T::Super>(this) }
}
}
impl<T: Message + IsIdCloneable> Clone for Retained<T> {
#[doc(alias = "objc_retain")]
#[doc(alias = "retain")]
#[inline]
fn clone(&self) -> Self {
let obj = unsafe { Retained::retain(self.ptr.as_ptr()) };
unsafe { obj.unwrap_unchecked() }
}
}
impl<T: ?Sized> Drop for Retained<T> {
#[doc(alias = "objc_release")]
#[doc(alias = "release")]
#[inline]
fn drop(&mut self) {
unsafe { objc_release_fast(self.ptr.as_ptr().cast()) };
}
}
impl<T: ?Sized> Deref for Retained<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { self.ptr.as_ref() }
}
}
impl<T: ?Sized + IsMutable> DerefMut for Retained<T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { self.ptr.as_mut() }
}
}
impl<T: ?Sized> fmt::Pointer for Retained<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.ptr.as_ptr(), f)
}
}
#[allow(missing_debug_implementations)]
mod private {
use crate::runtime::AnyObject;
use crate::ClassType;
use core::panic::{RefUnwindSafe, UnwindSafe};
pub struct UnknownStorage<T: ?Sized>(*const T, AnyObject);
pub struct ArcLikeStorage<T: ?Sized>(*const T);
unsafe impl<T: ?Sized + Sync + Send> Send for ArcLikeStorage<T> {}
unsafe impl<T: ?Sized + Sync + Send> Sync for ArcLikeStorage<T> {}
impl<T: ?Sized + RefUnwindSafe> RefUnwindSafe for ArcLikeStorage<T> {}
impl<T: ?Sized + RefUnwindSafe> UnwindSafe for ArcLikeStorage<T> {}
impl<T: ?Sized> Unpin for ArcLikeStorage<T> {}
pub struct BoxLikeStorage<T: ?Sized>(T);
use crate::mutability;
#[doc(hidden)]
pub trait SendSyncHelper<T: ?Sized>: mutability::Mutability {
type EquivalentType: ?Sized;
}
impl<T: ?Sized> SendSyncHelper<T> for mutability::Root {
type EquivalentType = UnknownStorage<T>;
}
impl<T: ?Sized> SendSyncHelper<T> for mutability::Immutable {
type EquivalentType = ArcLikeStorage<T>;
}
impl<T: ?Sized> SendSyncHelper<T> for mutability::Mutable {
type EquivalentType = BoxLikeStorage<T>;
}
impl<T: ?Sized, MS: ?Sized> SendSyncHelper<T> for mutability::ImmutableWithMutableSubclass<MS> {
type EquivalentType = ArcLikeStorage<T>;
}
impl<T: ?Sized, IS: ?Sized> SendSyncHelper<T> for mutability::MutableWithImmutableSuperclass<IS> {
type EquivalentType = BoxLikeStorage<T>;
}
impl<T: ?Sized> SendSyncHelper<T> for mutability::InteriorMutable {
type EquivalentType = ArcLikeStorage<T>;
}
impl<T: ?Sized> SendSyncHelper<T> for mutability::MainThreadOnly {
type EquivalentType = ArcLikeStorage<T>;
}
pub struct EquivalentType<T: ?Sized + ClassType>(
<T::Mutability as SendSyncHelper<T>>::EquivalentType,
)
where
T::Mutability: SendSyncHelper<T>;
}
unsafe impl<T: ?Sized + ClassType + Send> Send for Retained<T>
where
T::Mutability: private::SendSyncHelper<T>,
private::EquivalentType<T>: Send,
{
}
unsafe impl<T: ?Sized + ClassType + Sync> Sync for Retained<T>
where
T::Mutability: private::SendSyncHelper<T>,
private::EquivalentType<T>: Sync,
{
}
impl<T: ?Sized> Unpin for Retained<T> {}
impl<T: ?Sized + RefUnwindSafe> RefUnwindSafe for Retained<T> {}
impl<T: ?Sized + RefUnwindSafe + UnwindSafe> UnwindSafe for Retained<T> {}
#[cfg(doc)]
#[allow(unused)]
struct TestDocWithNonClassType {
id: Retained<crate::runtime::AnyObject>,
}
#[cfg(test)]
mod tests {
use core::mem::size_of;
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
use crate::mutability::{Immutable, Mutable};
use crate::rc::{autoreleasepool, RcTestObject, ThreadTestData};
use crate::runtime::{AnyObject, NSObject, NSObjectProtocol};
use crate::{declare_class, msg_send, DeclaredClass};
#[test]
fn auto_traits() {
macro_rules! helper {
($name:ident, $mutability:ty) => {
declare_class!(
struct $name;
unsafe impl ClassType for $name {
type Super = NSObject;
type Mutability = $mutability;
const NAME: &'static str = concat!(stringify!($name), "Test");
}
impl DeclaredClass for $name {}
);
};
}
helper!(ImmutableObject, Immutable);
helper!(ImmutableSendObject, Immutable);
unsafe impl Send for ImmutableSendObject {}
helper!(ImmutableSyncObject, Immutable);
unsafe impl Sync for ImmutableSyncObject {}
helper!(ImmutableSendSyncObject, Immutable);
unsafe impl Send for ImmutableSendSyncObject {}
unsafe impl Sync for ImmutableSendSyncObject {}
helper!(MutableObject, Mutable);
helper!(MutableSendObject, Mutable);
unsafe impl Send for MutableSendObject {}
helper!(MutableSyncObject, Mutable);
unsafe impl Sync for MutableSyncObject {}
helper!(MutableSendSyncObject, Mutable);
unsafe impl Send for MutableSendSyncObject {}
unsafe impl Sync for MutableSendSyncObject {}
assert_impl_all!(Retained<AnyObject>: Unpin);
assert_not_impl_any!(Retained<AnyObject>: Send, Sync, UnwindSafe, RefUnwindSafe);
assert_not_impl_any!(Retained<ImmutableObject>: Send, Sync);
assert_not_impl_any!(Retained<ImmutableSendObject>: Send, Sync);
assert_not_impl_any!(Retained<ImmutableSyncObject>: Send, Sync);
assert_impl_all!(Retained<ImmutableSendSyncObject>: Send, Sync);
assert_not_impl_any!(Retained<MutableObject>: Send, Sync);
assert_not_impl_any!(Retained<MutableSendObject>: Sync);
assert_impl_all!(Retained<MutableSendObject>: Send);
assert_not_impl_any!(Retained<MutableSyncObject>: Send);
assert_impl_all!(Retained<MutableSyncObject>: Sync);
assert_impl_all!(Retained<MutableSendSyncObject>: Send, Sync);
}
#[test]
fn test_drop() {
let mut expected = ThreadTestData::current();
let obj = RcTestObject::new();
expected.alloc += 1;
expected.init += 1;
expected.assert_current();
drop(obj);
expected.release += 1;
expected.drop += 1;
expected.assert_current();
}
#[test]
fn test_autorelease() {
let obj = RcTestObject::new();
let cloned = obj.clone();
let mut expected = ThreadTestData::current();
autoreleasepool(|pool| {
let _ref = Retained::autorelease(obj, pool);
expected.autorelease += 1;
expected.assert_current();
assert_eq!(cloned.retainCount(), 2);
});
expected.release += 1;
expected.assert_current();
assert_eq!(cloned.retainCount(), 1);
autoreleasepool(|pool| {
let _ref = Retained::autorelease(cloned, pool);
expected.autorelease += 1;
expected.assert_current();
});
expected.release += 1;
expected.drop += 1;
expected.assert_current();
}
#[test]
fn test_clone() {
let obj = RcTestObject::new();
assert_eq!(obj.retainCount(), 1);
let mut expected = ThreadTestData::current();
expected.assert_current();
assert_eq!(obj.retainCount(), 1);
let cloned = obj.clone();
expected.retain += 1;
expected.assert_current();
assert_eq!(cloned.retainCount(), 2);
assert_eq!(obj.retainCount(), 2);
let obj = Retained::into_super(Retained::into_super(obj));
let cloned_and_type_erased = obj.clone();
expected.retain += 1;
expected.assert_current();
let retain_count: usize = unsafe { msg_send![&cloned_and_type_erased, retainCount] };
assert_eq!(retain_count, 3);
let retain_count: usize = unsafe { msg_send![&obj, retainCount] };
assert_eq!(retain_count, 3);
drop(obj);
expected.release += 1;
expected.assert_current();
assert_eq!(cloned.retainCount(), 2);
drop(cloned_and_type_erased);
expected.release += 1;
expected.assert_current();
assert_eq!(cloned.retainCount(), 1);
drop(cloned);
expected.release += 1;
expected.drop += 1;
expected.assert_current();
}
#[test]
fn test_retain_autoreleased_works_as_retain() {
let obj = RcTestObject::new();
let mut expected = ThreadTestData::current();
let ptr = Retained::as_ptr(&obj) as *mut RcTestObject;
let _obj2 = unsafe { Retained::retain_autoreleased(ptr) }.unwrap();
expected.retain += 1;
expected.assert_current();
}
#[test]
fn test_cast() {
let obj: Retained<RcTestObject> = RcTestObject::new();
let expected = ThreadTestData::current();
let obj: Retained<AnyObject> = unsafe { Retained::cast(obj) };
expected.assert_current();
let _obj: Retained<RcTestObject> = unsafe { Retained::cast(obj) };
expected.assert_current();
}
#[repr(C)]
struct MyObject<'a> {
inner: NSObject,
p: PhantomData<&'a str>,
}
#[allow(unused)]
fn assert_id_variance<'b>(obj: Retained<MyObject<'static>>) -> Retained<MyObject<'b>> {
obj
}
#[test]
fn test_size_of() {
let ptr_size = size_of::<&NSObject>();
assert_eq!(size_of::<Retained<NSObject>>(), ptr_size);
assert_eq!(size_of::<Option<Retained<NSObject>>>(), ptr_size);
}
}