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 = "Retained")]
#[doc(alias = "StrongPtr")]
pub struct Id<T: ?Sized> {
ptr: NonNull<T>,
item: PhantomData<T>,
notunwindsafe: PhantomData<&'static mut ()>,
}
impl<T: ?Sized> Id<T> {
#[inline]
pub(crate) unsafe fn new_nonnull(ptr: NonNull<T>) -> Self {
Self {
ptr,
item: PhantomData,
notunwindsafe: PhantomData,
}
}
}
impl<T: ?Sized + Message> Id<T> {
#[inline]
pub unsafe fn new(ptr: *mut T) -> Option<Self> {
NonNull::new(ptr).map(|ptr| unsafe { Id::new_nonnull(ptr) })
}
#[inline]
pub fn as_ptr(this: &Self) -> *const T {
this.ptr.as_ptr()
}
#[inline]
pub fn as_mut_ptr(this: &mut Self) -> *mut T
where
T: IsMutable,
{
this.ptr.as_ptr()
}
#[inline]
pub(crate) fn consume_as_ptr(this: Self) -> *mut T {
ManuallyDrop::new(this).ptr.as_ptr()
}
#[inline]
pub(crate) fn consume_as_ptr_option(this: Option<Self>) -> *mut T
where
T: Sized,
{
this.map(|this| Id::consume_as_ptr(this))
.unwrap_or_else(ptr::null_mut)
}
}
impl<T: Message> Id<T> {
#[inline]
pub unsafe fn cast<U: Message>(this: Self) -> Id<U> {
let ptr = ManuallyDrop::new(this).ptr.cast();
unsafe { Id::new_nonnull(ptr) }
}
#[doc(alias = "objc_retain")]
#[inline]
pub unsafe fn retain(ptr: *mut T) -> Option<Id<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::new(res) }
}
#[doc(alias = "objc_retainAutoreleasedReturnValue")]
#[inline]
pub unsafe fn retain_autoreleased(ptr: *mut T) -> Option<Id<T>> {
#[cfg(all(feature = "apple", not(target_os = "windows")))]
{
#[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(feature = "apple", not(target_os = "windows"), 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::new(res) }
}
#[inline]
pub(super) fn autorelease_inner(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_inner(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_inner(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> Id<T>
where
T::Super: 'static,
{
#[inline]
pub fn into_super(this: Self) -> Id<T::Super> {
unsafe { Self::cast::<T::Super>(this) }
}
}
impl<T: IsIdCloneable> Clone for Id<T> {
#[doc(alias = "objc_retain")]
#[doc(alias = "retain")]
#[inline]
fn clone(&self) -> Self {
let obj = unsafe { Id::retain(self.ptr.as_ptr()) };
unsafe { obj.unwrap_unchecked() }
}
}
impl<T: ?Sized> Drop for Id<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 Id<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
unsafe { self.ptr.as_ref() }
}
}
impl<T: ?Sized + IsMutable> DerefMut for Id<T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
unsafe { self.ptr.as_mut() }
}
}
impl<T: ?Sized> fmt::Pointer for Id<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.ptr.as_ptr(), f)
}
}
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 IdSendSyncHelper<T: ?Sized>: mutability::Mutability {
type EquivalentType: ?Sized;
}
impl<T: ?Sized> IdSendSyncHelper<T> for mutability::Root {
type EquivalentType = UnknownStorage<T>;
}
impl<T: ?Sized> IdSendSyncHelper<T> for mutability::Immutable {
type EquivalentType = ArcLikeStorage<T>;
}
impl<T: ?Sized> IdSendSyncHelper<T> for mutability::Mutable {
type EquivalentType = BoxLikeStorage<T>;
}
impl<T: ?Sized, MS: ?Sized> IdSendSyncHelper<T> for mutability::ImmutableWithMutableSubclass<MS> {
type EquivalentType = ArcLikeStorage<T>;
}
impl<T: ?Sized, IS: ?Sized> IdSendSyncHelper<T> for mutability::MutableWithImmutableSuperclass<IS> {
type EquivalentType = BoxLikeStorage<T>;
}
impl<T: ?Sized> IdSendSyncHelper<T> for mutability::InteriorMutable {
type EquivalentType = ArcLikeStorage<T>;
}
impl<T: ?Sized> IdSendSyncHelper<T> for mutability::MainThreadOnly {
type EquivalentType = ArcLikeStorage<T>;
}
pub struct EquivalentType<T: ?Sized + ClassType>(
<T::Mutability as IdSendSyncHelper<T>>::EquivalentType,
)
where
T::Mutability: IdSendSyncHelper<T>;
}
unsafe impl<T: ?Sized + ClassType + Send> Send for Id<T>
where
T::Mutability: private::IdSendSyncHelper<T>,
private::EquivalentType<T>: Send,
{
}
unsafe impl<T: ?Sized + ClassType + Sync> Sync for Id<T>
where
T::Mutability: private::IdSendSyncHelper<T>,
private::EquivalentType<T>: Sync,
{
}
impl<T: ?Sized + Message> Unpin for Id<T> {}
impl<T: ?Sized + Message + RefUnwindSafe> RefUnwindSafe for Id<T> {}
impl<T: ?Sized + Message + RefUnwindSafe + UnwindSafe> UnwindSafe for Id<T> {}
#[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::{__RcTestObject, __ThreadTestData, autoreleasepool};
use crate::runtime::{AnyObject, NSObject};
use crate::{declare_class, msg_send};
#[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");
}
);
};
}
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!(Id<AnyObject>: Unpin);
assert_not_impl_any!(Id<AnyObject>: Send, Sync, UnwindSafe, RefUnwindSafe);
assert_not_impl_any!(Id<ImmutableObject>: Send, Sync);
assert_not_impl_any!(Id<ImmutableSendObject>: Send, Sync);
assert_not_impl_any!(Id<ImmutableSyncObject>: Send, Sync);
assert_impl_all!(Id<ImmutableSendSyncObject>: Send, Sync);
assert_not_impl_any!(Id<MutableObject>: Send, Sync);
assert_not_impl_any!(Id<MutableSendObject>: Sync);
assert_impl_all!(Id<MutableSendObject>: Send);
assert_not_impl_any!(Id<MutableSyncObject>: Send);
assert_impl_all!(Id<MutableSyncObject>: Sync);
assert_impl_all!(Id<MutableSendSyncObject>: Send, Sync);
}
#[track_caller]
fn assert_retain_count(obj: &AnyObject, expected: usize) {
let retain_count: usize = unsafe { msg_send![obj, retainCount] };
assert_eq!(retain_count, expected);
}
#[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.dealloc += 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 = Id::autorelease(obj, pool);
expected.autorelease += 1;
expected.assert_current();
assert_retain_count(&cloned, 2);
});
expected.release += 1;
expected.assert_current();
assert_retain_count(&cloned, 1);
autoreleasepool(|pool| {
let _ref = Id::autorelease(cloned, pool);
expected.autorelease += 1;
expected.assert_current();
});
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_clone() {
let obj = __RcTestObject::new();
assert_retain_count(&obj, 1);
let mut expected = __ThreadTestData::current();
expected.assert_current();
assert_retain_count(&obj, 1);
let cloned = obj.clone();
expected.retain += 1;
expected.assert_current();
assert_retain_count(&cloned, 2);
assert_retain_count(&obj, 2);
drop(obj);
expected.release += 1;
expected.assert_current();
assert_retain_count(&cloned, 1);
drop(cloned);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_retain_autoreleased_works_as_retain() {
let obj = __RcTestObject::new();
let mut expected = __ThreadTestData::current();
let ptr = Id::as_ptr(&obj) as *mut __RcTestObject;
let _obj2 = unsafe { Id::retain_autoreleased(ptr) }.unwrap();
expected.retain += 1;
expected.assert_current();
}
#[test]
fn test_cast() {
let obj: Id<__RcTestObject> = __RcTestObject::new();
let expected = __ThreadTestData::current();
let obj: Id<AnyObject> = unsafe { Id::cast(obj) };
expected.assert_current();
let _obj: Id<__RcTestObject> = unsafe { Id::cast(obj) };
expected.assert_current();
}
#[repr(C)]
struct MyObject<'a> {
inner: NSObject,
p: PhantomData<&'a str>,
}
#[allow(unused)]
fn assert_id_variance<'b>(obj: Id<MyObject<'static>>) -> Id<MyObject<'b>> {
obj
}
#[test]
fn test_size_of() {
let ptr_size = size_of::<&NSObject>();
assert_eq!(size_of::<Id<NSObject>>(), ptr_size);
assert_eq!(size_of::<Option<Id<NSObject>>>(), ptr_size);
}
}