diff --git a/Cargo.toml b/Cargo.toml index b958ec885..cdd936995 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ members = [ "objc2_exception", "objc2_foundation", "objc2_foundation_derive", - "objc2_id", "objc2_sys", "objc2_test_utils", ] diff --git a/objc2/README.md b/objc2/README.md index 8e41f546c..2e3af7584 100644 --- a/objc2/README.md +++ b/objc2/README.md @@ -29,31 +29,33 @@ unsafe { The utilities of the `rc` module provide ARC-like semantics for working with Objective-C's reference counted objects in Rust. -A `StrongPtr` retains an object and releases the object when dropped. -A `WeakPtr` will not retain the object, but can be upgraded to a `StrongPtr` -and safely fails if the object has been deallocated. + +An `Id` retains an object and releases the object when dropped. +A `WeakId` will not retain the object, but can be upgraded to an `Id` and +safely fails if the object has been deallocated. ```rust , no_run use objc2::{class, msg_send}; -use objc2::rc::{autoreleasepool, StrongPtr}; +use objc2::rc::{autoreleasepool, Id, Shared, WeakId}; +use objc2::runtime::Object; -// StrongPtr will release the object when dropped -let obj = unsafe { - StrongPtr::new(msg_send![class!(NSObject), new]) +// Id will release the object when dropped +let obj: Id = unsafe { + Id::new(msg_send![class!(NSObject), new]) }; // Cloning retains the object an additional time let cloned = obj.clone(); -autoreleasepool(|_| { - // Autorelease consumes the StrongPtr, but won't +autoreleasepool(|pool| { + // Autorelease consumes the Id, but won't // actually release until the end of an autoreleasepool - cloned.autorelease(); + let obj_ref: &Object = cloned.autorelease(pool); }); // Weak references won't retain the object -let weak = obj.weak(); +let weak = WeakId::new(&obj); drop(obj); -assert!(weak.load().is_null()); +assert!(weak.load().is_none()); ``` ## Declaring classes diff --git a/objc2/examples/introspection.rs b/objc2/examples/introspection.rs index 189796928..d4225117e 100644 --- a/objc2/examples/introspection.rs +++ b/objc2/examples/introspection.rs @@ -1,4 +1,6 @@ -use objc2::rc::StrongPtr; +use core::ptr::NonNull; + +use objc2::rc::{Id, Owned}; use objc2::runtime::{Class, Object}; use objc2::{class, msg_send, sel, Encode}; @@ -14,15 +16,15 @@ fn main() { } // Allocate an instance - let obj = unsafe { + let obj: Id = unsafe { let obj: *mut Object = msg_send![cls, alloc]; - let obj: *mut Object = msg_send![obj, init]; - StrongPtr::new(obj) + let obj: NonNull = msg_send![obj, init]; + Id::new(obj) }; println!("NSObject address: {:p}", obj); // Access an ivar of the object - let isa: *const Class = unsafe { *(**obj).get_ivar("isa") }; + let isa: *const Class = unsafe { *obj.get_ivar("isa") }; println!("NSObject isa: {:?}", isa); // Inspect a method of the class @@ -33,6 +35,6 @@ fn main() { assert!(*hash_return == usize::ENCODING); // Invoke a method on the object - let hash: usize = unsafe { msg_send![*obj, hash] }; + let hash: usize = unsafe { msg_send![obj, hash] }; println!("NSObject hash: {}", hash); } diff --git a/objc2/src/exception.rs b/objc2/src/exception.rs index 487dd70dd..50013c70c 100644 --- a/objc2/src/exception.rs +++ b/objc2/src/exception.rs @@ -1,5 +1,8 @@ -use crate::rc::StrongPtr; +use core::ptr::NonNull; + +use crate::rc::Id; use crate::runtime::Object; +use objc2_exception::{r#try, Exception}; // Comment copied from `objc2_exception` @@ -18,6 +21,6 @@ use crate::runtime::Object; /// undefined behaviour until `C-unwind` is stabilized, see [RFC-2945]. /// /// [RFC-2945]: https://rust-lang.github.io/rfcs/2945-c-unwind-abi.html -pub unsafe fn catch_exception(closure: impl FnOnce() -> R) -> Result { - objc2_exception::r#try(closure).map_err(|exception| StrongPtr::new(exception as *mut Object)) +pub unsafe fn catch_exception(closure: impl FnOnce() -> R) -> Result> { + r#try(closure).map_err(|e| Id::new(NonNull::new(e).unwrap())) } diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index 3be5291cc..fb9214d44 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -66,13 +66,15 @@ macro_rules! sel { Sends a message to an object. The first argument can be any type that dereferences to a type that implements -[`Message`], like a reference, a pointer, or an `objc2_id::Id` to an object. +[`Message`], like a reference, a pointer, or an [`rc::Id`] to an +object. The syntax is similar to the message syntax in Objective-C. Variadic arguments are not currently supported. [`Message`]: crate::Message +[`rc::Id`]: crate::rc::Id # Panics diff --git a/objc2/src/rc/autorelease.rs b/objc2/src/rc/autorelease.rs index e29dd03a5..98a280100 100644 --- a/objc2/src/rc/autorelease.rs +++ b/objc2/src/rc/autorelease.rs @@ -13,13 +13,6 @@ use crate::runtime::{objc_autoreleasePoolPop, objc_autoreleasePoolPush}; /// /// And this is not [`Sync`], since you can only autorelease a reference to a /// pool on the current thread. -/// -/// See [the clang documentation][clang-arc] and [the apple article on memory -/// management][memory-mgmt] for more information on automatic reference -/// counting. -/// -/// [clang-arc]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html -/// [memory-mgmt]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html pub struct AutoreleasePool { context: *mut c_void, } diff --git a/objc2/src/rc/id.rs b/objc2/src/rc/id.rs new file mode 100644 index 000000000..7d275384d --- /dev/null +++ b/objc2/src/rc/id.rs @@ -0,0 +1,583 @@ +use alloc::borrow; +use core::fmt; +use core::hash; +use core::iter::FusedIterator; +use core::marker::PhantomData; +use core::mem::ManuallyDrop; +use core::ops::{Deref, DerefMut}; +use core::ptr::NonNull; + +use super::AutoreleasePool; +use super::{Owned, Ownership, Shared}; +use crate::Message; + +/// An pointer for Objective-C reference counted objects. +/// +/// [`Id`] strongly references or "retains" the given object `T`, and +/// "releases" it again when dropped, thereby ensuring it will be deallocated +/// at the right time. +/// +/// An [`Id`] can either be [`Owned`] or [`Shared`], represented with the `O` +/// type parameter. +/// +/// If owned, it is guaranteed that there are no other references to the +/// object, and the [`Id`] can therefore be mutably dereferenced. +/// +/// If shared, however, it can only be immutably dereferenced because there +/// may be other references to the object, since a shared [`Id`] can be cloned +/// to provide exactly that. +/// +/// An [`Id`] can be safely "downgraded", that is, turned into to a +/// `Id` using `From`/`Into`. The opposite is not safely possible, +/// but the unsafe option [`Id::from_shared`] is provided. +/// +/// `Option>` is guaranteed to have the same size as a pointer to the +/// object. +/// +/// # Comparison to `std` types +/// +/// `Id` can be thought of as the Objective-C equivalent of [`Box`] +/// from the standard library: It is a unique pointer to some allocated +/// object, and that means you're allowed to get a mutable reference to it. +/// +/// Likewise, `Id` is the Objective-C equivalent of [`Arc`]: It is +/// a reference-counting pointer that, when cloned, increases the reference +/// count. +/// +/// [`Box`]: alloc::boxed::Box +/// [`Arc`]: alloc::sync::Arc +/// +/// # Caveats +/// +/// If the inner type implements [`Drop`], that implementation will not be +/// called, since there is no way to ensure that the Objective-C runtime will +/// do so. If you need to run some code when the object is destroyed, +/// implement the `dealloc` method instead. +/// +/// # Examples +/// +/// ```no_run +/// use objc2::msg_send; +/// use objc2::runtime::{Class, Object}; +/// use objc2::rc::{Id, Owned, Shared, WeakId}; +/// +/// let cls = Class::get("NSObject").unwrap(); +/// let obj: Id = unsafe { +/// Id::new(msg_send![cls, new]) +/// }; +/// // obj will be released when it goes out of scope +/// +/// // share the object so we can clone it +/// let obj: Id<_, Shared> = obj.into(); +/// let another_ref = obj.clone(); +/// // dropping our other reference will decrement the retain count +/// drop(another_ref); +/// +/// let weak = WeakId::new(&obj); +/// assert!(weak.load().is_some()); +/// // After the object is deallocated, our weak pointer returns none +/// drop(obj); +/// assert!(weak.load().is_none()); +/// ``` +/// +/// ```no_run +/// # use objc2::{class, msg_send}; +/// # use objc2::runtime::Object; +/// # use objc2::rc::{Id, Owned, Shared}; +/// # type T = Object; +/// let mut owned: Id; +/// # owned = unsafe { Id::new(msg_send![class!(NSObject), new]) }; +/// let mut_ref: &mut T = &mut *owned; +/// // Do something with `&mut T` here +/// +/// let shared: Id = owned.into(); +/// let cloned: Id = shared.clone(); +/// // Do something with `&T` here +/// ``` +#[repr(transparent)] +// TODO: Figure out if `Message` bound on `T` would be better here? +// TODO: Add `?Sized + ptr::Thin` bound on `T` to allow for extern types +// TODO: Consider changing the name of Id -> Retain +pub struct Id { + /// A pointer to the contained object. The pointer is always retained. + /// + /// It is important that this is `NonNull`, since we want to dereference + /// it later, and be able to use the null-pointer optimization. + /// + /// Additionally, covariance is correct because we're either the unique + /// owner of `T` (O = Owned), or `T` is immutable (O = Shared). + ptr: NonNull, + /// Necessary for dropck even though we never actually run T's destructor, + /// because it might have a `dealloc` that assumes that contained + /// references outlive the type. + /// + /// See + item: PhantomData, + /// To prevent warnings about unused type parameters. + own: PhantomData, +} + +// TODO: Maybe make most of these functions "associated" functions instead? +impl Id { + /// Constructs an [`Id`] to an object that already has +1 retain count. + /// + /// This is useful when you have a retain count that has been handed off + /// from somewhere else, usually Objective-C methods like `init`, `alloc`, + /// `new`, `copy`, or methods with the `ns_returns_retained` attribute. + /// + /// Since most of the above methods create new objects, and you therefore + /// hold unique access to the object, you would often set the ownership to + /// be [`Owned`]. + /// + /// But some immutable objects (like `NSString`) don't always return + /// unique references, so in those case you would use [`Shared`]. + /// + /// # Safety + /// + /// The caller must ensure the given object has +1 retain count, and that + /// the object pointer otherwise follows the same safety requirements as + /// in [`Id::retain`]. + /// + /// # Example + /// + /// ```no_run + /// # use objc2::{class, msg_send}; + /// # use objc2::runtime::{Class, Object}; + /// # use objc2::rc::{Id, Owned}; + /// let cls: &Class; + /// # let cls = class!(NSObject); + /// let obj: &mut Object = unsafe { msg_send![cls, alloc] }; + /// let obj: Id = unsafe { Id::new(msg_send![obj, init]) }; + /// // Or in this case simply just: + /// let obj: Id = unsafe { Id::new(msg_send![cls, new]) }; + /// ``` + /// + /// ```no_run + /// # use objc2::{class, msg_send}; + /// # use objc2::runtime::Object; + /// # use objc2::rc::{Id, Shared}; + /// # type NSString = Object; + /// let cls = class!(NSString); + /// // NSString is immutable, so don't create an owned reference to it + /// let obj: Id = unsafe { Id::new(msg_send![cls, new]) }; + /// ``` + #[inline] + // Note: We don't take a reference as a parameter since it would be too + // easy to accidentally create two aliasing mutable references. + pub unsafe fn new(ptr: NonNull) -> Id { + // SAFETY: Upheld by the caller + Id { + ptr, + item: PhantomData, + own: PhantomData, + } + } + + /// Retains the given object pointer. + /// + /// This is useful when you have been given a pointer to an object from + /// some API, and you would like to ensure that the object stays around + /// so that you can work with it. + /// + /// This is rarely used to construct owned [`Id`]s, see [`Id::new`] for + /// that. + /// + /// # Safety + /// + /// The caller must ensure that the ownership is correct; that is, there + /// must be no [`Owned`] pointers or mutable references to the same + /// object, and when creating owned [`Id`]s, there must be no other + /// pointers or references to the object. + /// + /// Additionally, the pointer must be valid as a reference (aligned, + /// dereferencable and initialized, see the [`std::ptr`] module for more + /// information). + /// + /// [`std::ptr`]: core::ptr + // + // This would be illegal: + // ```no_run + // let owned: Id; + // // Lifetime information is discarded + // let retained: Id = unsafe { Id::retain(&*owned) }; + // // Which means we can still mutate `Owned`: + // let x: &mut T = &mut *owned; + // // While we have an immutable reference + // let y: &T = &*retained; + // ``` + #[doc(alias = "objc_retain")] + #[cfg_attr(debug_assertions, inline)] + pub unsafe fn retain(ptr: NonNull) -> Id { + let ptr = ptr.as_ptr() as *mut objc2_sys::objc_object; + // SAFETY: The caller upholds that the pointer is valid + let res = objc2_sys::objc_retain(ptr); + debug_assert_eq!(res, ptr, "objc_retain did not return the same pointer"); + // SAFETY: Non-null upheld by the caller, and `objc_retain` always + // returns the same pointer, see: + // https://clang.llvm.org/docs/AutomaticReferenceCounting.html#arc-runtime-objc-retain + Id::new(NonNull::new_unchecked(res as *mut T)) + } + + #[cfg_attr(debug_assertions, inline)] + fn autorelease_inner(self) -> *mut T { + // Note that this (and the actual `autorelease`) is not an associated + // function. This breaks the guideline that smart pointers shouldn't + // add inherent methods, but since autoreleasing only works on already + // retained objects it is hard to imagine a case where the inner type + // has a method with the same name. + + let ptr = ManuallyDrop::new(self).ptr.as_ptr() as *mut objc2_sys::objc_object; + // SAFETY: The `ptr` is guaranteed to be valid and have at least one + // retain count. + // And because of the ManuallyDrop, we don't call the Drop + // implementation, so the object won't also be released there. + let res = unsafe { objc2_sys::objc_autorelease(ptr) }; + debug_assert_eq!(res, ptr, "objc_autorelease did not return the same pointer"); + res as *mut T + } + + // TODO: objc_retainAutoreleasedReturnValue + // TODO: objc_autoreleaseReturnValue + // TODO: objc_retainAutorelease + // TODO: objc_retainAutoreleaseReturnValue + // TODO: objc_autoreleaseReturnValue + // TODO: objc_autoreleaseReturnValue +} + +// TODO: Consider something like this +// #[cfg(block)] +// impl Id { +// #[doc(alias = "objc_retainBlock")] +// pub unsafe fn retain_block(block: NonNull) -> Self { +// todo!() +// } +// } + +impl Id { + /// Autoreleases the owned [`Id`], returning a mutable reference bound to + /// the pool. + /// + /// The object is not immediately released, but will be when the innermost + /// / current autorelease pool (given as a parameter) is drained. + #[doc(alias = "objc_autorelease")] + #[must_use = "If you don't intend to use the object any more, just drop it as usual"] + #[inline] + pub fn autorelease<'p>(self, pool: &'p AutoreleasePool) -> &'p mut T { + let ptr = self.autorelease_inner(); + // SAFETY: The pointer is valid as a reference, and we've consumed + // the unique access to the `Id` so mutability is safe. + unsafe { pool.ptr_as_mut(ptr) } + } + + /// Promote a shared [`Id`] to an owned one, allowing it to be mutated. + /// + /// # Safety + /// + /// The caller must ensure that there are no other pointers to the same + /// object (which also means that the given [`Id`] should have a retain + /// count of exactly 1 all cases, except when autoreleases are involved). + #[inline] + pub unsafe fn from_shared(obj: Id) -> Self { + // Note: We can't debug_assert retainCount because of autoreleases + let ptr = ManuallyDrop::new(obj).ptr; + // SAFETY: The pointer is valid + // Ownership rules are upheld by the caller + >::new(ptr) + } +} + +impl Id { + /// Autoreleases the shared [`Id`], returning an aliased reference bound + /// to the pool. + /// + /// The object is not immediately released, but will be when the innermost + /// / current autorelease pool (given as a parameter) is drained. + #[doc(alias = "objc_autorelease")] + #[must_use = "If you don't intend to use the object any more, just drop it as usual"] + #[inline] + pub fn autorelease<'p>(self, pool: &'p AutoreleasePool) -> &'p T { + let ptr = self.autorelease_inner(); + // SAFETY: The pointer is valid as a reference + unsafe { pool.ptr_as_ref(ptr) } + } +} + +impl From> for Id { + /// Downgrade from an owned to a shared [`Id`], allowing it to be cloned. + #[inline] + fn from(obj: Id) -> Self { + let ptr = ManuallyDrop::new(obj).ptr; + // SAFETY: The pointer is valid, and ownership is simply decreased + unsafe { >::new(ptr) } + } +} + +impl Clone for Id { + /// Makes a clone of the shared object. + /// + /// This increases the object's reference count. + #[doc(alias = "objc_retain")] + #[doc(alias = "retain")] + #[inline] + fn clone(&self) -> Self { + // SAFETY: The pointer is valid + unsafe { Id::retain(self.ptr) } + } +} + +/// `#[may_dangle]` (see [this][dropck_eyepatch]) doesn't apply here since we +/// don't run `T`'s destructor (rather, we want to discourage having `T`s with +/// a destructor); and even if we did run the destructor, it would not be safe +/// to add since we cannot verify that a `dealloc` method doesn't access +/// borrowed data. +/// +/// [dropck_eyepatch]: https://doc.rust-lang.org/nightly/nomicon/dropck.html#an-escape-hatch +impl Drop for Id { + /// Releases the retained object. + /// + /// The contained object's destructor (if it has one) is never run! + #[doc(alias = "objc_release")] + #[doc(alias = "release")] + #[inline] + fn drop(&mut self) { + // We could technically run the destructor for `T` when `O = Owned`, + // and when `O = Shared` with (retainCount == 1), but that would be + // confusing and inconsistent since we cannot guarantee that it's run. + + // SAFETY: The `ptr` is guaranteed to be valid and have at least one + // retain count + unsafe { objc2_sys::objc_release(self.ptr.as_ptr() as *mut _) }; + } +} + +/// The `Send` implementation requires `T: Sync` because `Id` give +/// access to `&T`. +/// +/// Additiontally, it requires `T: Send` because if `T: !Send`, you could +/// clone a `Id`, send it to another thread, and drop the clone +/// last, making `dealloc` get called on the other thread, and violate +/// `T: !Send`. +unsafe impl Send for Id {} + +/// The `Sync` implementation requires `T: Sync` because `&Id` give +/// access to `&T`. +/// +/// Additiontally, it requires `T: Send`, because if `T: !Send`, you could +/// clone a `&Id` from another thread, and drop the clone last, +/// making `dealloc` get called on the other thread, and violate `T: !Send`. +unsafe impl Sync for Id {} + +/// `Id` are `Send` if `T` is `Send` because they give the same +/// access as having a T directly. +unsafe impl Send for Id {} + +/// `Id` are `Sync` if `T` is `Sync` because they give the same +/// access as having a `T` directly. +unsafe impl Sync for Id {} + +impl Deref for Id { + type Target = T; + + /// Obtain an immutable reference to the object. + fn deref(&self) -> &T { + // SAFETY: The pointer's validity is verified when the type is created + unsafe { self.ptr.as_ref() } + } +} + +impl DerefMut for Id { + /// Obtain a mutable reference to the object. + fn deref_mut(&mut self) -> &mut T { + // SAFETY: The pointer's validity is verified when the type is created + // Additionally, the owned `Id` is the unique owner of the object, so + // mutability is safe. + unsafe { self.ptr.as_mut() } + } +} + +impl PartialEq for Id { + #[inline] + fn eq(&self, other: &Self) -> bool { + (**self).eq(&**other) + } + + #[inline] + fn ne(&self, other: &Self) -> bool { + (**self).ne(&**other) + } +} + +impl Eq for Id {} + +impl PartialOrd for Id { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + (**self).partial_cmp(&**other) + } + #[inline] + fn lt(&self, other: &Self) -> bool { + (**self).lt(&**other) + } + #[inline] + fn le(&self, other: &Self) -> bool { + (**self).le(&**other) + } + #[inline] + fn ge(&self, other: &Self) -> bool { + (**self).ge(&**other) + } + #[inline] + fn gt(&self, other: &Self) -> bool { + (**self).gt(&**other) + } +} + +impl Ord for Id { + #[inline] + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + (**self).cmp(&**other) + } +} + +impl hash::Hash for Id { + fn hash(&self, state: &mut H) { + (**self).hash(state) + } +} + +impl fmt::Display for Id { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl fmt::Debug for Id { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + (**self).fmt(f) + } +} + +impl fmt::Pointer for Id { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Pointer::fmt(&self.ptr.as_ptr(), f) + } +} + +impl Iterator for Id { + type Item = I::Item; + fn next(&mut self) -> Option { + (**self).next() + } + fn size_hint(&self) -> (usize, Option) { + (**self).size_hint() + } + fn nth(&mut self, n: usize) -> Option { + (**self).nth(n) + } +} + +impl DoubleEndedIterator for Id { + fn next_back(&mut self) -> Option { + (**self).next_back() + } + fn nth_back(&mut self, n: usize) -> Option { + (**self).nth_back(n) + } +} + +impl ExactSizeIterator for Id { + fn len(&self) -> usize { + (**self).len() + } +} + +impl FusedIterator for Id {} + +impl borrow::Borrow for Id { + fn borrow(&self) -> &T { + &**self + } +} + +impl borrow::BorrowMut for Id { + fn borrow_mut(&mut self) -> &mut T { + &mut **self + } +} + +impl AsRef for Id { + fn as_ref(&self) -> &T { + &**self + } +} + +impl AsMut for Id { + fn as_mut(&mut self) -> &mut T { + &mut **self + } +} + +// impl Future for Id { +// type Output = F::Output; +// fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { +// F::poll(Pin::new(&mut *self), cx) +// } +// } + +// This is valid without `T: Unpin` because we don't implement any projection. +// +// See https://doc.rust-lang.org/1.54.0/src/alloc/boxed.rs.html#1652-1675 +// and the `Arc` implementation. +impl Unpin for Id {} + +// TODO: When stabilized impl Fn traits & CoerceUnsized + +#[cfg(test)] +mod tests { + use core::ptr::NonNull; + + use super::{Id, Owned, Shared}; + use crate::rc::autoreleasepool; + use crate::runtime::Object; + use crate::{class, msg_send}; + + fn retain_count(obj: &Object) -> usize { + unsafe { msg_send![obj, retainCount] } + } + + #[test] + fn test_autorelease() { + let obj: Id = unsafe { Id::new(msg_send![class!(NSObject), new]) }; + + let cloned = obj.clone(); + + autoreleasepool(|pool| { + let _ref = obj.autorelease(pool); + assert_eq!(retain_count(&*cloned), 2); + }); + + // make sure that the autoreleased value has been released + assert_eq!(retain_count(&*cloned), 1); + } + + #[test] + fn test_clone() { + let cls = class!(NSObject); + let obj: Id = unsafe { + let obj: *mut Object = msg_send![cls, alloc]; + let obj: *mut Object = msg_send![obj, init]; + Id::new(NonNull::new_unchecked(obj)) + }; + assert!(retain_count(&obj) == 1); + + let obj: Id<_, Shared> = obj.into(); + assert!(retain_count(&obj) == 1); + + let cloned = obj.clone(); + assert!(retain_count(&cloned) == 2); + assert!(retain_count(&obj) == 2); + + drop(obj); + assert!(retain_count(&cloned) == 1); + } +} diff --git a/objc2/src/rc/mod.rs b/objc2/src/rc/mod.rs index 317a30c42..da20300dc 100644 --- a/objc2/src/rc/mod.rs +++ b/objc2/src/rc/mod.rs @@ -1,116 +1,67 @@ -/*! -Utilities for reference counting Objective-C objects. - -The utilities of the `rc` module provide ARC-like semantics for working with -Objective-C's reference counted objects in Rust. -A `StrongPtr` retains an object and releases the object when dropped. -A `WeakPtr` will not retain the object, but can be upgraded to a `StrongPtr` -and safely fails if the object has been deallocated. - -These utilities are not intended to provide a fully safe interface, but can be -useful when writing higher-level Rust wrappers for Objective-C code. - -For more information on Objective-C's reference counting, see Apple's documentation: - - -# Example - -``` no_run -# use objc2::{class, msg_send}; -# use objc2::rc::{autoreleasepool, StrongPtr}; -// StrongPtr will release the object when dropped -let obj = unsafe { - StrongPtr::new(msg_send![class!(NSObject), new]) -}; - -// Cloning retains the object an additional time -let cloned = obj.clone(); -autoreleasepool(|_| { - // Autorelease consumes the StrongPtr, but won't - // actually release until the end of an autoreleasepool - cloned.autorelease(); -}); - -// Weak references won't retain the object -let weak = obj.weak(); -drop(obj); -assert!(weak.load().is_null()); -``` -*/ +//! Utilities for reference counting Objective-C objects. +//! +//! The utilities of the `rc` module provide ARC-like semantics for working +//! with Objective-C's reference counted objects in Rust. +//! +//! A smart pointer [`Id`] is provided to ensure that Objective-C objects are +//! retained and released at the proper times. +//! +//! To enforce aliasing rules, an `Id` can be either owned or shared; if it is +//! owned, meaning the `Id` is the only reference to the object, it can be +//! mutably dereferenced. An owned `Id` can be downgraded to a shared `Id` +//! which can be cloned to allow multiple references. +//! +//! Weak references may be created using the [`WeakId`] struct. +//! +//! See [the clang documentation][clang-arc] and [the Apple article on memory +//! management][mem-mgmt] (similar document exists [for Core Foundation][mem-cf]) +//! for more information on automatic and manual reference counting. +//! +//! It can also be useful to [enable Malloc Debugging][mem-debug] if you're trying +//! to figure out if/where your application has memory errors and leaks. +//! +//! +//! [clang-arc]: https://clang.llvm.org/docs/AutomaticReferenceCounting.html +//! [mem-mgmt]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html +//! [mem-cf]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/CFMemoryMgmt.html +//! [mem-debug]: https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/MallocDebug.html mod autorelease; -mod strong; -mod weak; +mod id; +mod ownership; +mod weak_id; pub use self::autorelease::{autoreleasepool, AutoreleasePool, AutoreleaseSafe}; -pub use self::strong::StrongPtr; -pub use self::weak::WeakPtr; +pub use self::id::Id; +pub use self::ownership::{Owned, Ownership, Shared}; +pub use self::weak_id::WeakId; -// These tests use NSObject, which isn't present for GNUstep -#[cfg(all(test, target_vendor = "apple"))] +#[cfg(test)] mod tests { - use super::autoreleasepool; - use super::StrongPtr; - use crate::runtime::Object; - - #[test] - fn test_strong_clone() { - fn retain_count(obj: *mut Object) -> usize { - unsafe { msg_send![obj, retainCount] } - } + use core::mem::size_of; - let obj = unsafe { StrongPtr::new(msg_send![class!(NSObject), new]) }; - assert!(retain_count(*obj) == 1); + use super::{Id, Owned, Shared, WeakId}; - let cloned = obj.clone(); - assert!(retain_count(*cloned) == 2); - assert!(retain_count(*obj) == 2); - - drop(obj); - assert!(retain_count(*cloned) == 1); + pub struct TestType { + _data: [u8; 0], // TODO: `UnsafeCell`? } #[test] - fn test_weak() { - let obj = unsafe { StrongPtr::new(msg_send![class!(NSObject), new]) }; - let weak = obj.weak(); - - let strong = weak.load(); - assert!(*strong == *obj); - drop(strong); - - drop(obj); - assert!(weak.load().is_null()); - } - - #[test] - fn test_weak_copy() { - let obj = unsafe { StrongPtr::new(msg_send![class!(NSObject), new]) }; - let weak = obj.weak(); - - let weak2 = weak.clone(); - - let strong = weak.load(); - let strong2 = weak2.load(); - assert!(*strong == *obj); - assert!(*strong2 == *obj); - } - - #[test] - fn test_autorelease() { - let obj = unsafe { StrongPtr::new(msg_send![class!(NSObject), new]) }; - - fn retain_count(obj: *mut Object) -> usize { - unsafe { msg_send![obj, retainCount] } - } - let cloned = obj.clone(); - - autoreleasepool(|_| { - obj.autorelease(); - assert!(retain_count(*cloned) == 2); - }); - - // make sure that the autoreleased value has been released - assert!(retain_count(*cloned) == 1); + fn test_size_of() { + assert_eq!(size_of::>(), size_of::<&TestType>()); + assert_eq!(size_of::>(), size_of::<&TestType>()); + assert_eq!( + size_of::>>(), + size_of::<&TestType>() + ); + assert_eq!( + size_of::>>(), + size_of::<&TestType>() + ); + + assert_eq!( + size_of::>>(), + size_of::<*const ()>() + ); } } diff --git a/objc2/src/rc/ownership.rs b/objc2/src/rc/ownership.rs new file mode 100644 index 000000000..1cb0b8dd4 --- /dev/null +++ b/objc2/src/rc/ownership.rs @@ -0,0 +1,24 @@ +/// A type used to mark that a struct owns the object(s) it contains, +/// so it has the sole references to them. +pub enum Owned {} + +/// A type used to mark that the object(s) a struct contains are shared, +/// so there may be other references to them. +pub enum Shared {} + +mod private { + pub trait Sealed {} + + impl Sealed for super::Owned {} + impl Sealed for super::Shared {} +} + +/// A type that marks what type of ownership a struct has over the object(s) +/// it contains; specifically, either [`Owned`] or [`Shared`]. +/// +/// This trait is sealed and not meant to be implemented outside of the this +/// crate. +pub trait Ownership: private::Sealed + 'static {} + +impl Ownership for Owned {} +impl Ownership for Shared {} diff --git a/objc2/src/rc/strong.rs b/objc2/src/rc/strong.rs deleted file mode 100644 index eb2264443..000000000 --- a/objc2/src/rc/strong.rs +++ /dev/null @@ -1,77 +0,0 @@ -use core::fmt; -use core::mem; -use core::ops::Deref; - -use super::WeakPtr; -use crate::runtime::{self, Object}; - -/// A pointer that strongly references an object, ensuring it won't be deallocated. -pub struct StrongPtr(*mut Object); - -impl StrongPtr { - /// Constructs a [`StrongPtr`] to a newly created object that already has a - /// +1 retain count. This will not retain the object. - /// When dropped, the object will be released. - /// - /// # Safety - /// - /// The caller must ensure the given object pointer is valid. - pub unsafe fn new(ptr: *mut Object) -> Self { - StrongPtr(ptr) - } - - /// Retains the given object and constructs a [`StrongPtr`] to it. - /// When dropped, the object will be released. - /// - /// # Safety - /// - /// The caller must ensure the given object pointer is valid. - pub unsafe fn retain(ptr: *mut Object) -> Self { - Self(runtime::objc_retain(ptr as _) as _) - } - - /// Autoreleases self, meaning that the object is not immediately released, - /// but will be when the autorelease pool is drained. A pointer to the - /// object is returned, but its validity is no longer ensured. - pub fn autorelease(self) -> *mut Object { - let ptr = self.0; - mem::forget(self); - unsafe { - runtime::objc_autorelease(ptr as _); - } - ptr - } - - /// Returns a [`WeakPtr`] to self. - pub fn weak(&self) -> WeakPtr { - unsafe { WeakPtr::new(self.0) } - } -} - -impl Drop for StrongPtr { - fn drop(&mut self) { - unsafe { - runtime::objc_release(self.0 as _); - } - } -} - -impl Clone for StrongPtr { - fn clone(&self) -> StrongPtr { - unsafe { StrongPtr::retain(self.0) } - } -} - -impl Deref for StrongPtr { - type Target = *mut Object; - - fn deref(&self) -> &*mut Object { - &self.0 - } -} - -impl fmt::Pointer for StrongPtr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Pointer::fmt(&self.0, f) - } -} diff --git a/objc2/src/rc/weak.rs b/objc2/src/rc/weak.rs deleted file mode 100644 index 448810a2e..000000000 --- a/objc2/src/rc/weak.rs +++ /dev/null @@ -1,54 +0,0 @@ -use alloc::boxed::Box; -use core::cell::UnsafeCell; -use core::ptr; - -use super::StrongPtr; -use crate::runtime::{self, Object}; - -// Our pointer must have the same address even if we are moved, so Box it. -// Although loading the WeakPtr may modify the pointer, it is thread safe, -// so we must use an UnsafeCell to get a *mut without self being mutable. - -/// A pointer that weakly references an object, allowing to safely check -/// whether it has been deallocated. -pub struct WeakPtr(Box>); - -impl WeakPtr { - /// Constructs a [`WeakPtr`] to the given object. - /// - /// # Safety - /// - /// The caller must ensure the given object pointer is valid. - pub unsafe fn new(obj: *mut Object) -> Self { - let ptr = Box::new(UnsafeCell::new(ptr::null_mut())); - runtime::objc_initWeak(ptr.get() as _, obj as _); - WeakPtr(ptr) - } - - /// Loads the object self points to, returning a [`StrongPtr`]. - /// If the object has been deallocated, the returned pointer will be null. - pub fn load(&self) -> StrongPtr { - unsafe { - let ptr = runtime::objc_loadWeakRetained(self.0.get() as _); - StrongPtr::new(ptr as _) - } - } -} - -impl Drop for WeakPtr { - fn drop(&mut self) { - unsafe { - runtime::objc_destroyWeak(self.0.get() as _); - } - } -} - -impl Clone for WeakPtr { - fn clone(&self) -> Self { - let ptr = Box::new(UnsafeCell::new(ptr::null_mut())); - unsafe { - runtime::objc_copyWeak(ptr.get() as _, self.0.get() as _); - } - WeakPtr(ptr) - } -} diff --git a/objc2/src/rc/weak_id.rs b/objc2/src/rc/weak_id.rs new file mode 100644 index 000000000..a115fd914 --- /dev/null +++ b/objc2/src/rc/weak_id.rs @@ -0,0 +1,168 @@ +use alloc::boxed::Box; +use core::cell::UnsafeCell; +use core::fmt; +use core::marker::PhantomData; +use core::ptr; +use core::ptr::NonNull; + +use super::{Id, Shared}; +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. + inner: Box>, + /// TODO: Variance and dropck + item: PhantomData, +} + +impl WeakId { + /// Construct a new [`WeakId`] referencing the given shared [`Id`]. + #[doc(alias = "objc_initWeak")] + 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(&**obj as *const T as *mut T) } + } + + /// # Safety + /// + /// The object must be valid or null. + unsafe fn new_inner(obj: *mut T) -> Self { + let inner = Box::new(UnsafeCell::new(ptr::null_mut())); + // SAFETY: `ptr` will never move, and the caller verifies `obj` + objc2_sys::objc_initWeak(inner.get() as _, obj as _); + Self { + inner, + item: PhantomData, + } + } + + /// Load a shared (and retained) [`Id`] if the object still exists. + /// + /// Returns [`None`] if the object has been deallocated. + #[doc(alias = "upgrade")] + #[doc(alias = "objc_loadWeak")] + #[doc(alias = "objc_loadWeakRetained")] + #[inline] + pub fn load(&self) -> Option> { + let ptr: *mut *mut objc2_sys::objc_object = self.inner.get() as _; + let obj = unsafe { objc2_sys::objc_loadWeakRetained(ptr) } as *mut T; + NonNull::new(obj).map(|obj| unsafe { Id::new(obj) }) + } +} + +impl Drop for WeakId { + /// Drops the `WeakId` pointer. + #[doc(alias = "objc_destroyWeak")] + fn drop(&mut self) { + unsafe { + objc2_sys::objc_destroyWeak(self.inner.get() as _); + } + } +} + +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 { + objc2_sys::objc_copyWeak(ptr.get() as _, self.inner.get() as _); + } + Self { + inner: ptr, + item: PhantomData, + } + } +} + +impl Default for WeakId { + /// Constructs a new `WeakId` that doesn't reference any object. + /// + /// Calling [`Self::load`] on the return value always gives [`None`]. + fn default() -> Self { + // SAFETY: The pointer is null + unsafe { Self::new_inner(ptr::null_mut()) } + } +} + +/// 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 {} + +#[cfg(test)] +mod tests { + use core::ptr::NonNull; + + use super::WeakId; + use super::{Id, Shared}; + use crate::runtime::Object; + use crate::{class, msg_send}; + + #[test] + fn test_weak() { + let cls = class!(NSObject); + let obj: Id = unsafe { + let obj: *mut Object = msg_send![cls, alloc]; + let obj: *mut Object = msg_send![obj, init]; + Id::new(NonNull::new_unchecked(obj)) + }; + + let weak = WeakId::new(&obj); + let strong = weak.load().unwrap(); + let strong_ptr: *const Object = &*strong; + let obj_ptr: *const Object = &*obj; + assert_eq!(strong_ptr, obj_ptr); + drop(strong); + + drop(obj); + assert!(weak.load().is_none()); + } + + #[test] + fn test_weak_clone() { + let obj: Id = unsafe { Id::new(msg_send![class!(NSObject), new]) }; + let weak = WeakId::new(&obj); + + let weak2 = weak.clone(); + + let strong = weak.load().unwrap(); + let strong2 = weak2.load().unwrap(); + let strong_ptr: *const Object = &*strong; + let strong2_ptr: *const Object = &*strong2; + let obj_ptr: *const Object = &*obj; + assert_eq!(strong_ptr, obj_ptr); + assert_eq!(strong2_ptr, obj_ptr); + } + + #[test] + fn test_weak_default() { + let weak: WeakId = WeakId::default(); + assert!(weak.load().is_none()); + drop(weak); + } +} diff --git a/objc2_foundation/Cargo.toml b/objc2_foundation/Cargo.toml index 939a32315..746a45933 100644 --- a/objc2_foundation/Cargo.toml +++ b/objc2_foundation/Cargo.toml @@ -24,4 +24,3 @@ block = ["objc2_block"] [dependencies] objc2_block = { path = "../objc2_block", optional = true } objc2 = { path = "../objc2" } -objc2_id = { path = "../objc2_id" } diff --git a/objc2_foundation/src/array.rs b/objc2_foundation/src/array.rs index f537fdade..94c15bc21 100644 --- a/objc2_foundation/src/array.rs +++ b/objc2_foundation/src/array.rs @@ -3,11 +3,12 @@ use core::cmp::Ordering; use core::ffi::c_void; use core::marker::PhantomData; use core::ops::{Index, Range}; +use core::ptr::NonNull; +use objc2::rc::{Id, Owned, Ownership, Shared}; use objc2::runtime::{Class, Object}; use objc2::{class, msg_send}; use objc2::{Encode, Encoding}; -use objc2_id::{Id, Owned, Ownership, ShareId, Shared}; use super::{INSCopying, INSFastEnumeration, INSMutableCopying, INSObject, NSEnumerator}; @@ -70,7 +71,7 @@ unsafe impl Encode for NSRange { Encoding::Struct("_NSRange", &[usize::ENCODING, usize::ENCODING]); } -unsafe fn from_refs(refs: &[&A::Item]) -> Id +unsafe fn from_refs(refs: &[&A::Item]) -> Id where A: INSArray, { @@ -78,7 +79,7 @@ where let obj: *mut A = msg_send![cls, alloc]; let obj: *mut A = msg_send![obj, initWithObjects:refs.as_ptr() count:refs.len()]; - Id::from_retained_ptr(obj) + Id::new(NonNull::new_unchecked(obj)) } pub trait INSArray: INSObject { @@ -125,7 +126,7 @@ pub trait INSArray: INSObject { } } - fn from_vec(vec: Vec>) -> Id { + fn from_vec(vec: Vec>) -> Id { let refs: Vec<&Self::Item> = vec.iter().map(|obj| &**obj).collect(); unsafe { from_refs(&refs) } } @@ -144,14 +145,11 @@ pub trait INSArray: INSObject { self.objects_in_range(0..self.count()) } - fn into_vec(array: Id) -> Vec> { + fn into_vec(array: Id) -> Vec> { array .to_vec() .into_iter() - .map(|obj| unsafe { - let obj_ptr: *const Self::Item = obj; - Id::from_ptr(obj_ptr as *mut Self::Item) - }) + .map(|obj| unsafe { Id::retain(obj.into()) }) .collect() } @@ -165,15 +163,15 @@ pub trait INSArray: INSObject { } } - fn shared_object_at(&self, index: usize) -> ShareId + fn shared_object_at(&self, index: usize) -> Id where Self: INSArray, { let obj = self.object_at(index); - unsafe { Id::from_ptr(obj as *const _ as *mut Self::Item) } + unsafe { Id::retain(obj.into()) } } - fn from_slice(slice: &[ShareId]) -> Id + fn from_slice(slice: &[Id]) -> Id where Self: INSArray, { @@ -181,25 +179,22 @@ pub trait INSArray: INSObject { unsafe { from_refs(&refs) } } - fn to_shared_vec(&self) -> Vec> + fn to_shared_vec(&self) -> Vec> where Self: INSArray, { self.to_vec() .into_iter() - .map(|obj| unsafe { - let obj_ptr: *const Self::Item = obj; - Id::from_ptr(obj_ptr as *mut Self::Item) - }) + .map(|obj| unsafe { Id::retain(obj.into()) }) .collect() } } -pub struct NSArray { +pub struct NSArray { item: PhantomData>, } -object_impl!(NSArray); +object_impl!(NSArray); impl INSObject for NSArray where @@ -276,7 +271,7 @@ pub trait INSMutableArray: INSArray { ) -> Id { let old_obj = unsafe { let obj = self.object_at(index); - Id::from_ptr(obj as *const _ as *mut Self::Item) + Id::retain(obj.into()) }; unsafe { let _: () = msg_send![self, replaceObjectAtIndex:index @@ -288,7 +283,7 @@ pub trait INSMutableArray: INSArray { fn remove_object_at(&mut self, index: usize) -> Id { let obj = unsafe { let obj = self.object_at(index); - Id::from_ptr(obj as *const _ as *mut Self::Item) + Id::retain(obj.into()) }; unsafe { let _: () = msg_send![self, removeObjectAtIndex: index]; @@ -299,7 +294,7 @@ pub trait INSMutableArray: INSArray { fn remove_last_object(&mut self) -> Id { let obj = self .last_object() - .map(|obj| unsafe { Id::from_ptr(obj as *const _ as *mut Self::Item) }); + .map(|obj| unsafe { Id::retain(obj.into()) }); unsafe { let _: () = msg_send![self, removeLastObject]; } @@ -345,11 +340,11 @@ pub trait INSMutableArray: INSArray { } } -pub struct NSMutableArray { +pub struct NSMutableArray { item: PhantomData>, } -object_impl!(NSMutableArray); +object_impl!(NSMutableArray); impl INSObject for NSMutableArray where @@ -420,9 +415,9 @@ mod tests { use super::{INSArray, INSMutableArray, NSArray, NSMutableArray}; use crate::{INSObject, INSString, NSObject, NSString}; - use objc2_id::Id; + use objc2::rc::{Id, Owned}; - fn sample_array(len: usize) -> Id> { + fn sample_array(len: usize) -> Id, Owned> { let mut vec = Vec::with_capacity(len); for _ in 0..len { vec.push(NSObject::new()); @@ -446,7 +441,7 @@ mod tests { assert!(array.first_object().unwrap() == array.object_at(0)); assert!(array.last_object().unwrap() == array.object_at(3)); - let empty_array: Id> = INSObject::new(); + let empty_array: Id, Owned> = INSObject::new(); assert!(empty_array.first_object().is_none()); assert!(empty_array.last_object().is_none()); } diff --git a/objc2_foundation/src/data.rs b/objc2_foundation/src/data.rs index 1e752357c..422d69f8e 100644 --- a/objc2_foundation/src/data.rs +++ b/objc2_foundation/src/data.rs @@ -1,14 +1,14 @@ #[cfg(feature = "block")] use alloc::vec::Vec; -use core::ffi::c_void; use core::ops::Range; use core::slice; +use core::{ffi::c_void, ptr::NonNull}; use super::{INSCopying, INSMutableCopying, INSObject, NSRange}; use objc2::msg_send; +use objc2::rc::{Id, Owned}; #[cfg(feature = "block")] use objc2_block::{Block, ConcreteBlock}; -use objc2_id::Id; pub trait INSData: INSObject { fn len(&self) -> usize { @@ -30,19 +30,19 @@ pub trait INSData: INSObject { unsafe { slice::from_raw_parts(ptr, len) } } - fn with_bytes(bytes: &[u8]) -> Id { + fn with_bytes(bytes: &[u8]) -> Id { let cls = Self::class(); let bytes_ptr = bytes.as_ptr() as *const c_void; unsafe { let obj: *mut Self = msg_send![cls, alloc]; let obj: *mut Self = msg_send![obj, initWithBytes:bytes_ptr length:bytes.len()]; - Id::from_retained_ptr(obj) + Id::new(NonNull::new_unchecked(obj)) } } #[cfg(feature = "block")] - fn from_vec(bytes: Vec) -> Id { + fn from_vec(bytes: Vec) -> Id { let capacity = bytes.capacity(); let dealloc = ConcreteBlock::new(move |bytes: *mut c_void, len: usize| unsafe { // Recreate the Vec and let it drop @@ -60,7 +60,7 @@ pub trait INSData: INSObject { length:bytes.len() deallocator:dealloc]; core::mem::forget(bytes); - Id::from_retained_ptr(obj) + Id::new(NonNull::new_unchecked(obj)) } } } diff --git a/objc2_foundation/src/dictionary.rs b/objc2_foundation/src/dictionary.rs index 4ff9f1997..180f2b27e 100644 --- a/objc2_foundation/src/dictionary.rs +++ b/objc2_foundation/src/dictionary.rs @@ -2,15 +2,15 @@ use alloc::vec::Vec; use core::cmp::min; use core::marker::PhantomData; use core::ops::Index; -use core::ptr; +use core::ptr::{self, NonNull}; +use objc2::rc::{Id, Owned, Ownership, Shared}; use objc2::runtime::Class; use objc2::{class, msg_send}; -use objc2_id::{Id, Owned, Ownership, ShareId}; use super::{INSCopying, INSFastEnumeration, INSObject, NSArray, NSEnumerator, NSSharedArray}; -unsafe fn from_refs(keys: &[&T], vals: &[&D::Value]) -> Id +unsafe fn from_refs(keys: &[&T], vals: &[&D::Value]) -> Id where D: INSDictionary, T: INSCopying, @@ -21,7 +21,7 @@ where let obj: *mut D = msg_send![obj, initWithObjects:vals.as_ptr() forKeys:keys.as_ptr() count:count]; - Id::from_retained_ptr(obj) + Id::new(NonNull::new_unchecked(obj)) } pub trait INSDictionary: INSObject { @@ -93,14 +93,14 @@ pub trait INSDictionary: INSObject { } } - fn keys_array(&self) -> Id> { + fn keys_array(&self) -> Id, Owned> { unsafe { - let keys: *mut NSSharedArray = msg_send![self, allKeys]; - Id::from_ptr(keys) + let keys = msg_send![self, allKeys]; + Id::retain(NonNull::new_unchecked(keys)) } } - fn from_keys_and_objects(keys: &[&T], vals: Vec>) -> Id + fn from_keys_and_objects(keys: &[&T], vals: Vec>) -> Id where T: INSCopying, { @@ -108,17 +108,17 @@ pub trait INSDictionary: INSObject { unsafe { from_refs(keys, &vals_refs) } } - fn into_values_array(dict: Id) -> Id> { + fn into_values_array(dict: Id) -> Id, Owned> { unsafe { let vals = msg_send![dict, allValues]; - Id::from_ptr(vals) + Id::retain(NonNull::new_unchecked(vals)) } } } pub struct NSDictionary { - key: PhantomData>, - obj: PhantomData>, + key: PhantomData>, + obj: PhantomData>, } object_impl!(NSDictionary); @@ -166,12 +166,12 @@ where #[cfg(test)] mod tests { use alloc::vec; - use objc2_id::Id; + use objc2::rc::{Owned, Id}; use super::{INSDictionary, NSDictionary}; use crate::{INSArray, INSObject, INSString, NSObject, NSString}; - fn sample_dict(key: &str) -> Id> { + fn sample_dict(key: &str) -> Id, Owned> { let string = NSString::from_str(key); let obj = NSObject::new(); NSDictionary::from_keys_and_objects(&[&*string], vec![obj]) diff --git a/objc2_foundation/src/enumerator.rs b/objc2_foundation/src/enumerator.rs index 16e80c581..8bd3fe9c9 100644 --- a/objc2_foundation/src/enumerator.rs +++ b/objc2_foundation/src/enumerator.rs @@ -1,12 +1,13 @@ use core::marker::PhantomData; use core::mem; use core::ptr; +use core::ptr::NonNull; use core::slice; use std::os::raw::c_ulong; +use objc2::rc::{Id, Owned}; use objc2::runtime::Object; use objc2::{msg_send, Encode, Encoding, RefEncode}; -use objc2_id::Id; use super::INSObject; @@ -14,7 +15,7 @@ pub struct NSEnumerator<'a, T> where T: INSObject, { - id: Id, + id: Id, item: PhantomData<&'a T>, } @@ -30,7 +31,7 @@ where /// ownership. pub unsafe fn from_ptr(ptr: *mut Object) -> NSEnumerator<'a, T> { NSEnumerator { - id: Id::from_ptr(ptr), + id: Id::retain(NonNull::new(ptr).unwrap()), item: PhantomData, } } diff --git a/objc2_foundation/src/macros.rs b/objc2_foundation/src/macros.rs index 09023c3de..8f41ee6c2 100644 --- a/objc2_foundation/src/macros.rs +++ b/objc2_foundation/src/macros.rs @@ -51,13 +51,13 @@ macro_rules! object_impl { ($name:ident) => ( object_impl!($name,); ); - ($name:ident<$($t:ident),+>) => ( - object_impl!($name, $($t),+); + ($name:ident<$($t:ident$(: $b:ident)?),+>) => ( + object_impl!($name, $($t$(: $b)?),+); ); - ($name:ident, $($t:ident),*) => ( - unsafe impl<$($t),*> ::objc2::Message for $name<$($t),*> { } + ($name:ident, $($t:ident$(: $b:ident)?),*) => ( + unsafe impl<$($t$(:($b))?),*> ::objc2::Message for $name<$($t),*> { } - unsafe impl<$($t),*> ::objc2::RefEncode for $name<$($t),*> { + unsafe impl<$($t$(: $b)?),*> ::objc2::RefEncode for $name<$($t),*> { const ENCODING_REF: ::objc2::Encoding<'static> = ::objc2::Encoding::Object; } ); diff --git a/objc2_foundation/src/object.rs b/objc2_foundation/src/object.rs index 00572ba3a..850a00019 100644 --- a/objc2_foundation/src/object.rs +++ b/objc2_foundation/src/object.rs @@ -1,9 +1,10 @@ use core::any::Any; +use core::ptr::NonNull; use objc2::msg_send; +use objc2::rc::{Id, Owned, Shared}; use objc2::runtime::{Class, BOOL, NO}; use objc2::Message; -use objc2_id::{Id, ShareId}; use super::NSString; @@ -28,10 +29,11 @@ pub trait INSObject: Any + Sized + Message { result != NO } - fn description(&self) -> ShareId { + fn description(&self) -> Id { unsafe { let result: *mut NSString = msg_send![self, description]; - Id::from_ptr(result) + // TODO: Verify that description always returns a non-null string + Id::retain(NonNull::new_unchecked(result)) } } @@ -40,13 +42,9 @@ pub trait INSObject: Any + Sized + Message { result != NO } - fn new() -> Id { + fn new() -> Id { let cls = Self::class(); - unsafe { - let obj: *mut Self = msg_send![cls, alloc]; - let obj: *mut Self = msg_send![obj, init]; - Id::from_retained_ptr(obj) - } + unsafe { Id::new(msg_send![cls, new]) } } } diff --git a/objc2_foundation/src/string.rs b/objc2_foundation/src/string.rs index 2b67df022..5e98a8b9d 100644 --- a/objc2_foundation/src/string.rs +++ b/objc2_foundation/src/string.rs @@ -1,21 +1,22 @@ use core::ffi::c_void; use core::fmt; +use core::ptr::NonNull; use core::slice; use core::str; use std::os::raw::c_char; use objc2::msg_send; -use objc2_id::{Id, ShareId}; +use objc2::rc::{Id, Owned, Shared}; use super::INSObject; pub trait INSCopying: INSObject { type Output: INSObject; - fn copy(&self) -> ShareId { + fn copy(&self) -> Id { unsafe { let obj: *mut Self::Output = msg_send![self, copy]; - Id::from_retained_ptr(obj) + Id::new(NonNull::new_unchecked(obj)) } } } @@ -23,10 +24,10 @@ pub trait INSCopying: INSObject { pub trait INSMutableCopying: INSObject { type Output: INSObject; - fn mutable_copy(&self) -> Id { + fn mutable_copy(&self) -> Id { unsafe { let obj: *mut Self::Output = msg_send![self, mutableCopy]; - Id::from_retained_ptr(obj) + Id::new(NonNull::new_unchecked(obj)) } } } @@ -57,7 +58,7 @@ pub trait INSString: INSObject { } } - fn from_str(string: &str) -> Id { + fn from_str(string: &str) -> Id { let cls = Self::class(); let bytes = string.as_ptr() as *const c_void; unsafe { @@ -65,7 +66,7 @@ pub trait INSString: INSObject { let obj: *mut Self = msg_send![obj, initWithBytes:bytes length:string.len() encoding:UTF8_ENCODING]; - Id::from_retained_ptr(obj) + Id::new(NonNull::new_unchecked(obj)) } } } diff --git a/objc2_foundation/src/value.rs b/objc2_foundation/src/value.rs index 1cb37a8cc..9f844aa7e 100644 --- a/objc2_foundation/src/value.rs +++ b/objc2_foundation/src/value.rs @@ -3,14 +3,15 @@ use core::any::Any; use core::ffi::c_void; use core::marker::PhantomData; use core::mem::MaybeUninit; +use core::ptr::NonNull; use core::str; use std::ffi::{CStr, CString}; use std::os::raw::c_char; +use objc2::rc::{Id, Owned}; use objc2::runtime::Class; use objc2::Encode; use objc2::{class, msg_send}; -use objc2_id::Id; use super::{INSCopying, INSObject}; @@ -35,7 +36,7 @@ pub trait INSValue: INSObject { } } - fn from_value(value: Self::Value) -> Id { + fn from_value(value: Self::Value) -> Id { let cls = Self::class(); let value_ptr: *const Self::Value = &value; let bytes = value_ptr as *const c_void; @@ -44,7 +45,7 @@ pub trait INSValue: INSObject { let obj: *mut Self = msg_send![cls, alloc]; let obj: *mut Self = msg_send![obj, initWithBytes:bytes objCType:encoding.as_ptr()]; - Id::from_retained_ptr(obj) + Id::new(NonNull::new_unchecked(obj)) } } } diff --git a/objc2_id/Cargo.toml b/objc2_id/Cargo.toml deleted file mode 100644 index 7fb326346..000000000 --- a/objc2_id/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "objc2_id" -version = "0.1.1" # Remember to update html_root_url in lib.rs -authors = ["Steven Sheldon", "Mads Marquart "] -edition = "2018" - -description = "Smart pointers for Objective-C reference counting." -keywords = ["objective-c", "macos", "ios", "retain", "release"] -categories = [ - "api-bindings", - "development-tools::ffi", - "os::macos-apis", -] -readme = "README.md" -repository = "https://github.com/madsmtm/objc2" -documentation = "https://docs.rs/objc2_id/" -license = "MIT" - -[dependencies] -objc2 = { path = "../objc2" } diff --git a/objc2_id/README.md b/objc2_id/README.md deleted file mode 100644 index 91c77a85f..000000000 --- a/objc2_id/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# `objc2_id` - -[![Latest version](https://badgen.net/crates/v/objc2_id)](https://crates.io/crates/objc2_id) -[![License](https://badgen.net/badge/license/MIT/blue)](../LICENSE.txt) -[![Documentation](https://docs.rs/objc2_id/badge.svg)](https://docs.rs/objc2_id/) -[![CI Status](https://github.com/madsmtm/objc2/workflows/CI/badge.svg)](https://github.com/madsmtm/objc2/actions) - -Rust smart pointers for Objective-C reference counting. - -To ensure that Objective-C objects are retained and released -at the proper times, we can use the Id struct. - -To enforce aliasing rules, an `Id` can be either owned or shared; if it is -owned, meaning the `Id` is the only reference to the object, it can be mutably -dereferenced. An owned `Id` can be downgraded to a ShareId -which can be cloned to allow multiple references. - -Weak references may be created using the WeakId struct. - -``` rust -use objc2::runtime::{Class, Object}; -use objc2_id::{Id, WeakId}; - -let cls = Class::get("NSObject").unwrap(); -let obj: Id = unsafe { - Id::from_retained_ptr(msg_send![cls, new]) -}; -// obj will be released when it goes out of scope - -// share the object so we can clone it -let obj = obj.share(); -let another_ref = obj.clone(); -// dropping our other reference will decrement the retain count -drop(another_ref); - -let weak = WeakId::new(&obj); -assert!(weak.load().is_some()); -// After the object is deallocated, our weak pointer returns none -drop(obj); -assert!(weak.load().is_none()); -``` diff --git a/objc2_id/src/id.rs b/objc2_id/src/id.rs deleted file mode 100644 index f7ff02760..000000000 --- a/objc2_id/src/id.rs +++ /dev/null @@ -1,291 +0,0 @@ -use core::any::Any; -use core::fmt; -use core::hash; -use core::marker::PhantomData; -use core::ops::{Deref, DerefMut}; - -use objc2::rc::{StrongPtr, WeakPtr}; -use objc2::runtime::Object; -use objc2::Message; - -/// A type used to mark that a struct owns the object(s) it contains, -/// so it has the sole references to them. -pub enum Owned {} -/// A type used to mark that the object(s) a struct contains are shared, -/// so there may be other references to them. -pub enum Shared {} - -/// A type that marks what type of ownership a struct has over the object(s) -/// it contains; specifically, either [`Owned`] or [`Shared`]. -pub trait Ownership: Any {} -impl Ownership for Owned {} -impl Ownership for Shared {} - -/// A pointer type for Objective-C's reference counted objects. -/// -/// The object of an [`Id`] is retained and sent a `release` message when -/// the [`Id`] is dropped. -/// -/// An [`Id`] may be either [`Owned`] or [`Shared`], represented by the types -/// [`Id`] and [`ShareId`], respectively. -/// If owned, there are no other references to the object and the [`Id`] can -/// be mutably dereferenced. -/// [`ShareId`], however, can only be immutably dereferenced because there may -/// be other references to the object, but a [`ShareId`] can be cloned to -/// provide more references to the object. -/// An owned [`Id`] can be "downgraded" freely to a [`ShareId`], but there is -/// no way to safely upgrade back. -pub struct Id { - ptr: StrongPtr, - item: PhantomData, - own: PhantomData, -} - -impl Id -where - T: Message, - O: Ownership, -{ - unsafe fn new(ptr: StrongPtr) -> Id { - Id { - ptr, - item: PhantomData, - own: PhantomData, - } - } - - /// Constructs an [`Id`] from a pointer to an unretained object and - /// retains it. - /// - /// # Panics - /// - /// Panics if the pointer is null. - /// - /// # Safety - /// - /// The pointer must be to a valid object and the caller must ensure the - /// ownership is correct. - pub unsafe fn from_ptr(ptr: *mut T) -> Id { - assert!( - !ptr.is_null(), - "Attempted to construct an Id from a null pointer" - ); - Id::new(StrongPtr::retain(ptr as *mut Object)) - } - - /// Constructs an [`Id`] from a pointer to a retained object; this won't - /// retain the pointer, so the caller must ensure the object has a +1 - /// retain count. - /// - /// # Panics - /// - /// Panics if the pointer is null. - /// - /// # Safety - /// - /// The pointer must be to a valid object and the caller must ensure the - /// ownership is correct. - pub unsafe fn from_retained_ptr(ptr: *mut T) -> Id { - assert!( - !ptr.is_null(), - "Attempted to construct an Id from a null pointer" - ); - Id::new(StrongPtr::new(ptr as *mut Object)) - } -} - -impl Id -where - T: Message, -{ - /// Downgrade an owned [`Id`] to a [`ShareId`], allowing it to be cloned. - pub fn share(self) -> ShareId { - let Id { ptr, .. } = self; - unsafe { Id::new(ptr) } - } -} - -impl Clone for Id -where - T: Message, -{ - fn clone(&self) -> ShareId { - unsafe { Id::new(self.ptr.clone()) } - } -} - -/// The `Send` implementation requires `T: Sync` because `Id` give -/// access to `&T`. -/// -/// Additiontally, it requires `T: Send` because if `T: !Send`, you could -/// clone a `Id`, send it to another thread, and drop the clone -/// last, making `dealloc` get called on the other thread, and violate -/// `T: !Send`. -unsafe impl Send for Id {} - -/// The `Sync` implementation requires `T: Sync` because `&Id` give -/// access to `&T`. -/// -/// Additiontally, it requires `T: Send`, because if `T: !Send`, you could -/// clone a `&Id` from another thread, and drop the clone last, -/// making `dealloc` get called on the other thread, and violate `T: !Send`. -unsafe impl Sync for Id {} - -/// `Id` are `Send` if `T` is `Send` because they give the same -/// access as having a T directly. -unsafe impl Send for Id {} - -/// `Id` are `Sync` if `T` is `Sync` because they give the same -/// access as having a `T` directly. -unsafe impl Sync for Id {} - -impl Deref for Id { - type Target = T; - - fn deref(&self) -> &T { - unsafe { &*(*self.ptr as *mut T) } - } -} - -impl DerefMut for Id { - fn deref_mut(&mut self) -> &mut T { - unsafe { &mut *(*self.ptr as *mut T) } - } -} - -impl PartialEq for Id -where - T: PartialEq, -{ - fn eq(&self, other: &Id) -> bool { - self.deref() == other.deref() - } -} - -impl Eq for Id where T: Eq {} - -impl hash::Hash for Id -where - T: hash::Hash, -{ - fn hash(&self, state: &mut H) - where - H: hash::Hasher, - { - self.deref().hash(state) - } -} - -impl fmt::Debug for Id -where - T: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.deref().fmt(f) - } -} - -impl fmt::Pointer for Id { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt::Pointer::fmt(&self.ptr, f) - } -} - -/// A convenient alias for a shared [`Id`]. -pub type ShareId = Id; - -/// A pointer type for a weak reference to an Objective-C reference counted -/// object. -pub struct WeakId { - ptr: WeakPtr, - item: PhantomData, -} - -impl WeakId -where - T: Message, -{ - /// Construct a new [`WeakId`] referencing the given [`ShareId`]. - pub fn new(obj: &ShareId) -> WeakId { - WeakId { - ptr: obj.ptr.weak(), - item: PhantomData, - } - } - - /// Load a [`ShareId`] from the [`WeakId`] if the object still exists. - /// - /// Returns [`None`] if the object has been deallocated. - pub fn load(&self) -> Option> { - let obj = self.ptr.load(); - if obj.is_null() { - None - } else { - Some(unsafe { Id::new(obj) }) - } - } -} - -/// 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 {} - -#[cfg(test)] -mod tests { - use super::{Id, ShareId, WeakId}; - use objc2::runtime::Object; - use objc2::{class, msg_send}; - - #[cfg(not(target_vendor = "apple"))] - #[test] - fn ensure_linkage() { - unsafe { crate::get_class_to_force_linkage() }; - } - - fn retain_count(obj: &Object) -> usize { - unsafe { msg_send![obj, retainCount] } - } - - #[test] - fn test_clone() { - let cls = class!(NSObject); - let obj: Id = unsafe { - let obj: *mut Object = msg_send![cls, alloc]; - let obj: *mut Object = msg_send![obj, init]; - Id::from_retained_ptr(obj) - }; - assert!(retain_count(&obj) == 1); - - let obj = obj.share(); - assert!(retain_count(&obj) == 1); - - let cloned = obj.clone(); - assert!(retain_count(&cloned) == 2); - assert!(retain_count(&obj) == 2); - - drop(obj); - assert!(retain_count(&cloned) == 1); - } - - #[test] - fn test_weak() { - let cls = class!(NSObject); - let obj: ShareId = unsafe { - let obj: *mut Object = msg_send![cls, alloc]; - let obj: *mut Object = msg_send![obj, init]; - Id::from_retained_ptr(obj) - }; - - let weak = WeakId::new(&obj); - let strong = weak.load().unwrap(); - let strong_ptr: *const Object = &*strong; - let obj_ptr: *const Object = &*obj; - assert!(strong_ptr == obj_ptr); - drop(strong); - - drop(obj); - assert!(weak.load().is_none()); - } -} diff --git a/objc2_id/src/lib.rs b/objc2_id/src/lib.rs deleted file mode 100644 index 68ec9272d..000000000 --- a/objc2_id/src/lib.rs +++ /dev/null @@ -1,66 +0,0 @@ -/*! -Rust smart pointers for Objective-C reference counting. - -To ensure that Objective-C objects are retained and released -at the proper times, we can use the [`Id`] struct. - -To enforce aliasing rules, an [`Id`] can be either owned or shared; if it is -owned, meaning the [`Id`] is the only reference to the object, it can be -mutably dereferenced. An owned [`Id`] can be downgraded to a [`ShareId`] which -can be cloned to allow multiple references. - -Weak references may be created using the [`WeakId`] struct. - -```no_run -# use objc2::msg_send; -use objc2::runtime::{Class, Object}; -use objc2_id::{Id, WeakId}; - -let cls = Class::get("NSObject").unwrap(); -let obj: Id = unsafe { - Id::from_retained_ptr(msg_send![cls, new]) -}; -// obj will be released when it goes out of scope - -// share the object so we can clone it -let obj = obj.share(); -let another_ref = obj.clone(); -// dropping our other reference will decrement the retain count -drop(another_ref); - -let weak = WeakId::new(&obj); -assert!(weak.load().is_some()); -// After the object is deallocated, our weak pointer returns none -drop(obj); -assert!(weak.load().is_none()); -``` -*/ - -// This crate is, but its dependencies are not -#![no_std] -// Update in Cargo.toml as well. -#![doc(html_root_url = "https://docs.rs/objc2_id/0.1.1")] - -pub use id::{Id, Owned, Ownership, ShareId, Shared, WeakId}; - -mod id; - -// TODO: Remove the need for this hack - -#[cfg(not(target_vendor = "apple"))] -use objc2::runtime::Class; - -#[cfg(not(target_vendor = "apple"))] -#[link(name = "gnustep-base", kind = "dylib")] -extern "C" {} - -#[cfg(not(target_vendor = "apple"))] -extern "C" { - static _OBJC_CLASS_NSObject: Class; -} - -#[cfg(not(target_vendor = "apple"))] -#[allow(dead_code)] -unsafe fn get_class_to_force_linkage() -> &'static Class { - &_OBJC_CLASS_NSObject -}