use core::fmt;
use core::marker::PhantomData;
use core::mem::ManuallyDrop;
use core::ops::Deref;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr::{self, NonNull};
use super::AutoreleasePool;
use crate::runtime::{objc_release_fast, objc_retain_fast, AnyObject, ProtocolObject};
use crate::{ffi, ClassType, DowncastTarget, Message};
#[repr(transparent)]
#[doc(alias = "id")]
#[doc(alias = "Id")] #[doc(alias = "StrongPtr")]
#[cfg_attr(
feature = "unstable-coerce-pointee",
derive(std::marker::CoercePointee)
)]
pub struct Retained<T: ?Sized> {
ptr: NonNull<T>,
item: PhantomData<T>,
notunwindsafe: PhantomData<&'static mut ()>,
}
#[deprecated(since = "0.6.0", note = "Renamed to `Retained`.")]
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]
pub(crate) fn as_nonnull_ptr(&self) -> NonNull<T> {
self.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 fn downcast<U: DowncastTarget>(self) -> Result<Retained<U>, Retained<T>>
where
Self: 'static,
{
let ptr: *const AnyObject = Self::as_ptr(&self).cast();
let obj: &AnyObject = unsafe { &*ptr };
if obj.is_kind_of_class(U::class()).as_bool() {
Ok(unsafe { Self::cast_unchecked::<U>(self) })
} else {
Err(self)
}
}
#[inline]
pub unsafe fn cast_unchecked<U: Message>(this: Self) -> Retained<U> {
let ptr = ManuallyDrop::new(this).ptr.cast();
unsafe { Retained::new_nonnull(ptr) }
}
#[inline]
#[deprecated = "Use `downcast`, or `cast_unchecked` instead"]
pub unsafe fn cast<U: Message>(this: Self) -> Retained<U> {
unsafe { Self::cast_unchecked(this) }
}
#[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 unsafe fn autorelease<'p>(this: Self, pool: AutoreleasePool<'p>) -> &'p T {
let ptr = Self::autorelease_ptr(this);
unsafe { pool.ptr_as_ref(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(self) -> Retained<T::Super> {
unsafe { Self::cast_unchecked::<T::Super>(self) }
}
}
impl<T: Message> Clone for Retained<T> {
#[doc(alias = "objc_retain")]
#[doc(alias = "retain")]
#[inline]
fn clone(&self) -> Self {
self.retain()
}
}
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> fmt::Pointer for Retained<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.ptr.as_ptr(), f)
}
}
impl<T: ?Sized + AsRef<U>, U: Message> From<&T> for Retained<U> {
#[inline]
fn from(obj: &T) -> Self {
obj.as_ref().retain()
}
}
impl<T: ClassType + 'static> From<Retained<T>> for Retained<AnyObject> {
#[inline]
fn from(obj: Retained<T>) -> Self {
unsafe { Retained::cast_unchecked(obj) }
}
}
impl<P: ?Sized + 'static> From<Retained<ProtocolObject<P>>> for Retained<AnyObject> {
#[inline]
fn from(obj: Retained<ProtocolObject<P>>) -> Self {
unsafe { Retained::cast_unchecked(obj) }
}
}
unsafe impl<T: ?Sized + Sync + Send> Send for Retained<T> {}
unsafe impl<T: ?Sized + Sync + Send> Sync for Retained<T> {}
impl<T: ?Sized> Unpin for Retained<T> {}
impl<T: ?Sized + RefUnwindSafe> RefUnwindSafe for Retained<T> {}
impl<T: ?Sized + RefUnwindSafe> UnwindSafe for Retained<T> {}
#[cfg(test)]
mod tests {
use core::mem::size_of;
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
use crate::rc::{autoreleasepool, RcTestObject, ThreadTestData};
use crate::runtime::{AnyObject, NSObject, NSObjectProtocol};
use crate::{define_class, msg_send};
#[test]
fn auto_traits() {
macro_rules! helper {
($name:ident) => {
define_class!(
#[unsafe(super(NSObject))]
#[name = concat!(stringify!($name), "Test")]
#[ivars = *const ()]
struct $name;
);
};
}
helper!(Object);
helper!(SendObject);
unsafe impl Send for SendObject {}
helper!(SyncObject);
unsafe impl Sync for SyncObject {}
helper!(SendSyncObject);
unsafe impl Send for SendSyncObject {}
unsafe impl Sync for SendSyncObject {}
assert_impl_all!(Retained<AnyObject>: Unpin);
assert_not_impl_any!(Retained<AnyObject>: Send, Sync, UnwindSafe, RefUnwindSafe);
assert_not_impl_any!(Retained<Object>: Send, Sync);
assert_not_impl_any!(Retained<SendObject>: Send, Sync);
assert_not_impl_any!(Retained<SyncObject>: Send, Sync);
assert_impl_all!(Retained<SendSyncObject>: 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 = unsafe { 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 = unsafe { 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 = obj.into_super().into_super();
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> = obj.into();
expected.assert_current();
let _obj: Retained<RcTestObject> = Retained::downcast(obj).unwrap();
expected.assert_current();
}
#[repr(C)]
struct MyObject<'a> {
inner: NSObject,
p: PhantomData<&'a str>,
}
#[allow(unused)]
fn assert_retained_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);
}
#[test]
fn test_into() {
let obj = NSObject::new();
let obj: Retained<NSObject> = Into::into(obj);
let _: Retained<AnyObject> = Into::into(obj);
let obj_ref = &*NSObject::new();
let _: Retained<NSObject> = Into::into(obj_ref);
let _: Retained<AnyObject> = Into::into(obj_ref);
let obj_retained_ref = &NSObject::new();
let _: Retained<NSObject> = Into::into(obj_retained_ref);
let _: Retained<AnyObject> = Into::into(obj_retained_ref);
let protocol_obj = ProtocolObject::<dyn NSObjectProtocol>::from_retained(NSObject::new());
let _: Retained<AnyObject> = Into::into(protocol_obj);
}
#[test]
#[cfg(feature = "unstable-coerce-pointee")]
fn test_coercion() {
use crate::extern_protocol;
extern_protocol!(
unsafe trait ExampleProtocol: NSObjectProtocol {}
);
unsafe impl ExampleProtocol for RcTestObject {}
let obj = RcTestObject::new();
let mut expected = ThreadTestData::current();
let obj: Retained<dyn ExampleProtocol> = obj;
expected.assert_current();
let obj: Retained<dyn NSObjectProtocol> = obj;
expected.assert_current();
drop(obj);
expected.release += 1;
expected.drop += 1;
expected.assert_current();
}
}