diff --git a/objc2/CHANGELOG.md b/objc2/CHANGELOG.md index 4fa53b556..55d8c12dc 100644 --- a/objc2/CHANGELOG.md +++ b/objc2/CHANGELOG.md @@ -16,7 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). `objc2-foundation`. ### Changed -* Change selector syntax in `declare_class!` macro to be more Rust-like. +* **BREAKING**: Change selector syntax in `declare_class!` macro to be more Rust-like. ## 0.3.0-beta.1 - 2022-07-19 diff --git a/objc2/CHANGELOG_FOUNDATION.md b/objc2/CHANGELOG_FOUNDATION.md index b7362eb31..fcfdc80f9 100644 --- a/objc2/CHANGELOG_FOUNDATION.md +++ b/objc2/CHANGELOG_FOUNDATION.md @@ -20,6 +20,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). of the `NSValue` matches the encoding of the given type. * Added functions `get_range`, `get_point`, `get_size` and `get_rect` to `NSValue` to help safely returning various types it will commonly contain. +* `NSArray` and `NSMutableArray` now have sensible defaults for the ownership + of the objects they contain. ### Changed * **BREAKING**: Moved from external crate `objc2_foundation` into @@ -27,6 +29,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * **BREAKING**: Made `NSValue` not generic any more. While we loose some type-safety from this, it makes `NSValue` much more useful in the real world! +* **BREAKING**: Made `NSArray::new` generic over ownership. ### Fixed * Made `Debug` impls for all objects print something useful. diff --git a/objc2/src/__macro_helpers.rs b/objc2/src/__macro_helpers.rs index eb1081691..a2cb7bd69 100644 --- a/objc2/src/__macro_helpers.rs +++ b/objc2/src/__macro_helpers.rs @@ -274,15 +274,13 @@ mod tests { #[test] fn test_alloc_with_zone() { - let expected = ThreadTestData::current(); + let mut expected = ThreadTestData::current(); let cls = RcTestObject::class(); let zone: *const NSZone = ptr::null(); let _obj: Id, Owned> = unsafe { msg_send_id![cls, allocWithZone: zone].unwrap() }; - // `+[NSObject alloc]` delegates to `+[NSObject allocWithZone:]`, but - // `RcTestObject` only catches `alloc`. - // expected.alloc += 1; + expected.alloc += 1; expected.assert_current(); } @@ -325,9 +323,18 @@ mod tests { expected.init += 1; expected.assert_current(); - // TODO: - // let copy: Id = unsafe { msg_send_id![&obj, copy].unwrap() }; - // let mutable_copy: Id = unsafe { msg_send_id![&obj, mutableCopy].unwrap() }; + let _copy: Id = unsafe { msg_send_id![&obj, copy].unwrap() }; + expected.copy += 1; + expected.alloc += 1; + expected.init += 1; + expected.assert_current(); + + let _mutable_copy: Id = + unsafe { msg_send_id![&obj, mutableCopy].unwrap() }; + expected.mutable_copy += 1; + expected.alloc += 1; + expected.init += 1; + expected.assert_current(); let _self: Id = unsafe { msg_send_id![&obj, self].unwrap() }; expected.retain += 1; @@ -337,8 +344,8 @@ mod tests { unsafe { msg_send_id![&obj, description] }; expected.assert_current(); }); - expected.release += 3; - expected.dealloc += 2; + expected.release += 5; + expected.dealloc += 4; expected.assert_current(); } diff --git a/objc2/src/foundation/array.rs b/objc2/src/foundation/array.rs index 6ae44109c..25592b7fa 100644 --- a/objc2/src/foundation/array.rs +++ b/objc2/src/foundation/array.rs @@ -14,55 +14,122 @@ use crate::Message; use crate::{__inner_extern_class, msg_send, msg_send_id}; __inner_extern_class! { - /// TODO + /// An immutable ordered collection of objects. /// - /// You can have a `Id, Owned>`, which allows mutable access - /// to the elements (without modifying the array itself), and - /// `Id, Shared>` which allows sharing the array. + /// This is the Objective-C equivalent of a "boxed slice" (`Box<[T]>`), + /// so effectively a `Vec` where you can't change the number of + /// elements. /// - /// `Id, Shared>` is possible, but pretty useless. - /// TODO: Can we make it impossible? Should we? + /// The type of the contained objects is described by the generic + /// parameter `T`, and the ownership of the objects is described with the + /// generic parameter `O`. /// - /// What about `Id, Owned>`? + /// + /// # Ownership + /// + /// While `NSArray` _itself_ is immutable, i.e. the number of objects it + /// contains can't change, it is still possible to modify the contained + /// objects themselves, if you know you're the sole owner of them - + /// quite similar to how you can modify elements in `Box<[T]>`. + /// + /// To mutate the contained objects the ownership must be `O = Owned`. A + /// summary of what the different "types" of arrays allow you to do can be + /// found below. `Array` refers to either `NSArray` or `NSMutableArray`. + /// - `Id, Owned>`: Allows you to mutate the + /// objects, and the array itself. + /// - `Id, Owned>`: Allows you to mutate the + /// array itself, but not it's contents. + /// - `Id, Owned>`: Allows you to mutate the objects, + /// but not the array itself. + /// - `Id, Owned>`: Effectively the same as the below. + /// - `Id, Shared>`: Allows you to copy the array, but + /// does not allow you to modify it in any way. + /// - `Id, Shared>`: Pretty useless compared to the + /// others, avoid this. + /// + /// See [Apple's documentation][apple-doc]. + /// + /// [apple-doc]: https://developer.apple.com/documentation/foundation/nsarray?language=objc // `T: PartialEq` bound correct because `NSArray` does deep (instead of // shallow) equality comparisons. #[derive(PartialEq, Eq, Hash)] - unsafe pub struct NSArray: NSObject { + unsafe pub struct NSArray: NSObject { item: PhantomData>, notunwindsafe: PhantomData<&'static mut ()>, } } // SAFETY: Same as Id (which is what NSArray effectively stores). -// -// TODO: Properly verify this -unsafe impl Sync for NSArray {} -unsafe impl Send for NSArray {} -unsafe impl Sync for NSArray {} -unsafe impl Send for NSArray {} +unsafe impl Sync for NSArray {} +unsafe impl Send for NSArray {} +unsafe impl Sync for NSArray {} +unsafe impl Send for NSArray {} // Also same as Id -impl RefUnwindSafe for NSArray {} -impl UnwindSafe for NSArray {} -impl UnwindSafe for NSArray {} - -pub(crate) unsafe fn from_refs(cls: &Class, refs: &[&T]) -> *mut Object { - let obj: *mut Object = unsafe { msg_send![cls, alloc] }; +impl RefUnwindSafe for NSArray {} +impl UnwindSafe for NSArray {} +impl UnwindSafe for NSArray {} + +#[track_caller] +pub(crate) unsafe fn with_objects( + cls: &Class, + objects: &[&T], +) -> Id { unsafe { - msg_send![ - obj, - initWithObjects: refs.as_ptr(), - count: refs.len(), + msg_send_id![ + msg_send_id![cls, alloc], + initWithObjects: objects.as_ptr(), + count: objects.len(), ] + .expect("unexpected NULL array") } } -impl NSArray { +/// Generic creation methods. +impl NSArray { + /// Get an empty array. pub fn new() -> Id { - unsafe { msg_send_id![Self::class(), new].unwrap() } + // SAFETY: + // - `new` may not create a new object, but instead return a shared + // instance. We remedy this by returning `Id`. + // - `O` don't actually matter here! E.g. `NSArray` is + // perfectly legal, since the array doesn't have any elements, and + // hence the notion of ownership over the elements is void. + unsafe { msg_send_id![Self::class(), new].expect("unexpected NULL NSArray") } + } + + pub fn from_vec(vec: Vec>) -> Id { + // SAFETY: + // `initWithObjects:` may choose to deduplicate arrays (I could + // imagine it having a special case for arrays with one `NSNumber` + // object), and returning mutable references to those would be + // unsound! + // However, when we know that we have ownership over the variables, we + // also know that there cannot be another array in existence with the + // same objects, so `Id, Owned>` is safe to return. + // + // In essence, we can choose between always returning `Id` + // or `Id`, and the latter is probably the most useful, as we + // would like to know when we're the only owner of the array, to + // allow mutation of the array's items. + unsafe { with_objects(Self::class(), vec.as_slice_ref()) } } } +/// Creation methods that produce shared arrays. +impl NSArray { + pub fn from_slice(slice: &[Id]) -> Id { + // SAFETY: Taking `&T` would not be sound, since the `&T` could come + // from an `Id` that would now no longer be owned! + // + // (Note that NSArray internally retains all the objects it is given, + // effectively making the safety requirements the same as + // `Id::retain`). + unsafe { with_objects(Self::class(), slice.as_slice_ref()) } + } +} + +/// Generic accessor methods. impl NSArray { #[doc(alias = "count")] pub fn len(&self) -> usize { @@ -102,13 +169,6 @@ impl NSArray { } } - // The `NSArray` itself (length and number of items) is always immutable, - // but we would like to know when we're the only owner of the array, to - // allow mutation of the array's items. - pub fn from_vec(vec: Vec>) -> Id { - unsafe { Id::new(from_refs(Self::class(), vec.as_slice_ref()).cast()).unwrap() } - } - pub fn objects_in_range(&self, range: Range) -> Vec<&T> { let range = NSRange::from(range); let mut vec = Vec::with_capacity(range.length); @@ -133,11 +193,8 @@ impl NSArray { } } +/// Accessor methods that work on shared arrays. impl NSArray { - pub fn from_slice(slice: &[Id]) -> Id { - unsafe { Id::new(from_refs(Self::class(), slice.as_slice_ref()).cast()).unwrap() } - } - #[doc(alias = "objectAtIndex:")] pub fn get_retained(&self, index: usize) -> Id { let obj = self.get(index).unwrap(); @@ -153,6 +210,7 @@ impl NSArray { } } +/// Accessor methods that work on owned arrays. impl NSArray { #[doc(alias = "objectAtIndex:")] pub fn get_mut(&mut self, index: usize) -> Option<&mut T> { @@ -176,13 +234,15 @@ impl NSArray { } } -// Copying only possible when ItemOwnership = Shared - +/// This is implemented as a shallow copy. +/// +/// As such, it is only possible when the array's contents are `Shared`. unsafe impl NSCopying for NSArray { type Ownership = Shared; type Output = NSArray; } +/// This is implemented as a shallow copy. unsafe impl NSMutableCopying for NSArray { type Output = NSMutableArray; } @@ -221,7 +281,7 @@ impl IndexMut for NSArray { } } -impl DefaultId for NSArray { +impl DefaultId for NSArray { type Ownership = Shared; #[inline] @@ -244,7 +304,7 @@ mod tests { use super::*; use crate::foundation::{NSNumber, NSString}; - use crate::rc::autoreleasepool; + use crate::rc::{RcTestObject, ThreadTestData}; fn sample_array(len: usize) -> Id, Owned> { let mut vec = Vec::with_capacity(len); @@ -262,19 +322,15 @@ mod tests { NSArray::from_vec(vec) } - fn retain_count(obj: &NSObject) -> usize { - unsafe { msg_send![obj, retainCount] } - } - #[test] fn test_two_empty() { - let _empty_array1 = NSArray::::new(); - let _empty_array2 = NSArray::::new(); + let _empty_array1 = NSArray::::new(); + let _empty_array2 = NSArray::::new(); } #[test] fn test_len() { - let empty_array = NSArray::::new(); + let empty_array = NSArray::::new(); assert_eq!(empty_array.len(), 0); let array = sample_array(4); @@ -311,31 +367,82 @@ mod tests { assert_eq!(array.first(), array.get(0)); assert_eq!(array.last(), array.get(3)); - let empty_array = >::new(); + let empty_array = >::new(); assert!(empty_array.first().is_none()); assert!(empty_array.last().is_none()); } #[test] - fn test_get_does_not_autorelease() { - let obj: Id<_, Shared> = NSObject::new().into(); + fn test_retains_stored() { + let obj = Id::from_owned(RcTestObject::new()); + let mut expected = ThreadTestData::current(); - assert_eq!(retain_count(&obj), 1); + let input = [obj.clone(), obj.clone()]; + expected.retain += 2; + expected.assert_current(); - let array = NSArray::from_slice(&[obj.clone()]); + let array = NSArray::from_slice(&input); + expected.retain += 2; + expected.assert_current(); - assert_eq!(retain_count(&obj), 2); + let _obj = array.first().unwrap(); + expected.assert_current(); + + drop(array); + expected.release += 2; + expected.assert_current(); - autoreleasepool(|_pool| { - let obj2 = array.first().unwrap(); - assert_eq!(retain_count(obj2), 2); - }); + let array = NSArray::from_vec(Vec::from(input)); + expected.retain += 2; + expected.release += 2; + expected.assert_current(); - assert_eq!(retain_count(&obj), 2); + let _obj = array.get(0).unwrap(); + let _obj = array.get(1).unwrap(); + assert!(array.get(2).is_none()); + expected.assert_current(); drop(array); + expected.release += 2; + expected.assert_current(); - assert_eq!(retain_count(&obj), 1); + drop(obj); + expected.release += 1; + expected.dealloc += 1; + expected.assert_current(); + } + + #[test] + fn test_nscopying_uses_retain() { + let obj = Id::from_owned(RcTestObject::new()); + let array = NSArray::from_slice(&[obj]); + let mut expected = ThreadTestData::current(); + + let _copy = array.copy(); + expected.assert_current(); + + let _copy = array.mutable_copy(); + expected.retain += 1; + expected.assert_current(); + } + + #[test] + fn test_iter_no_retain() { + let obj = Id::from_owned(RcTestObject::new()); + let array = NSArray::from_slice(&[obj]); + let mut expected = ThreadTestData::current(); + + let iter = array.iter(); + expected.retain += if cfg!(feature = "gnustep-1-7") { 0 } else { 1 }; + expected.assert_current(); + + assert_eq!(iter.count(), 1); + expected.autorelease += if cfg!(feature = "gnustep-1-7") { 0 } else { 1 }; + expected.assert_current(); + + let iter = array.iter_fast(); + assert_eq!(iter.count(), 1); + expected.assert_current(); } #[test] diff --git a/objc2/src/foundation/data.rs b/objc2/src/foundation/data.rs index 342283fd0..56b801bab 100644 --- a/objc2/src/foundation/data.rs +++ b/objc2/src/foundation/data.rs @@ -55,7 +55,7 @@ impl NSData { } pub fn with_bytes(bytes: &[u8]) -> Id { - unsafe { Id::new(data_with_bytes(Self::class(), bytes).cast()).unwrap() } + unsafe { Id::cast(with_slice(Self::class(), bytes)) } } #[cfg(feature = "block")] @@ -72,7 +72,7 @@ impl NSData { #[cfg(not(feature = "gnustep-1-7"))] let cls = Self::class(); - unsafe { Id::new(data_from_vec(cls, bytes).cast()).unwrap() } + unsafe { Id::cast(with_vec(cls, bytes)) } } } @@ -137,20 +137,20 @@ impl<'a> IntoIterator for &'a NSData { } } -pub(crate) unsafe fn data_with_bytes(cls: &Class, bytes: &[u8]) -> *mut Object { +pub(crate) unsafe fn with_slice(cls: &Class, bytes: &[u8]) -> Id { let bytes_ptr: *const c_void = bytes.as_ptr().cast(); unsafe { - let obj: *mut Object = msg_send![cls, alloc]; - msg_send![ - obj, + msg_send_id![ + msg_send_id![cls, alloc], initWithBytes: bytes_ptr, length: bytes.len(), ] + .expect("unexpected NULL data") } } #[cfg(feature = "block")] -pub(crate) unsafe fn data_from_vec(cls: &Class, bytes: Vec) -> *mut Object { +pub(crate) unsafe fn with_vec(cls: &Class, bytes: Vec) -> Id { use core::mem::ManuallyDrop; use block2::{Block, ConcreteBlock}; @@ -168,13 +168,13 @@ pub(crate) unsafe fn data_from_vec(cls: &Class, bytes: Vec) -> *mut Object { let bytes_ptr: *mut c_void = bytes.as_mut_ptr().cast(); unsafe { - let obj: *mut Object = msg_send![cls, alloc]; - msg_send![ - obj, + msg_send_id![ + msg_send_id![cls, alloc], initWithBytesNoCopy: bytes_ptr, length: bytes.len(), deallocator: dealloc, ] + .expect("unexpected NULL data") } } diff --git a/objc2/src/foundation/dictionary.rs b/objc2/src/foundation/dictionary.rs index 75693a9b4..1bdc14e12 100644 --- a/objc2/src/foundation/dictionary.rs +++ b/objc2/src/foundation/dictionary.rs @@ -12,7 +12,7 @@ use crate::{__inner_extern_class, msg_send, msg_send_id, Message}; __inner_extern_class! { #[derive(PartialEq, Eq, Hash)] - unsafe pub struct NSDictionary: NSObject { + unsafe pub struct NSDictionary: NSObject { key: PhantomData>, obj: PhantomData>, } @@ -20,12 +20,12 @@ __inner_extern_class! { // TODO: SAFETY // Approximately same as `NSArray` -unsafe impl Sync for NSDictionary {} -unsafe impl Send for NSDictionary {} +unsafe impl Sync for NSDictionary {} +unsafe impl Send for NSDictionary {} // Approximately same as `NSArray` -impl UnwindSafe for NSDictionary {} -impl RefUnwindSafe for NSDictionary {} +impl UnwindSafe for NSDictionary {} +impl RefUnwindSafe for NSDictionary {} impl NSDictionary { pub fn new() -> Id { diff --git a/objc2/src/foundation/mod.rs b/objc2/src/foundation/mod.rs index b831d5b55..1a8ef171c 100644 --- a/objc2/src/foundation/mod.rs +++ b/objc2/src/foundation/mod.rs @@ -132,7 +132,7 @@ mod tests { assert_auto_traits::(); assert_auto_traits::(); assert_auto_traits::(); - assert_auto_traits::>(); + assert_auto_traits::>(); // TODO: Figure out if Send + Sync is safe? // assert_auto_traits::>(); // assert_auto_traits::>>(); diff --git a/objc2/src/foundation/mutable_array.rs b/objc2/src/foundation/mutable_array.rs index 0fa392e74..10f8d7379 100644 --- a/objc2/src/foundation/mutable_array.rs +++ b/objc2/src/foundation/mutable_array.rs @@ -5,7 +5,7 @@ use core::fmt; use core::marker::PhantomData; use core::ops::{Index, IndexMut}; -use super::array::from_refs; +use super::array::with_objects; use super::{ NSArray, NSComparisonResult, NSCopying, NSFastEnumeration, NSFastEnumerator, NSMutableCopying, NSObject, @@ -15,10 +15,14 @@ use crate::Message; use crate::{__inner_extern_class, msg_send, msg_send_id}; __inner_extern_class! { - // TODO: Ensure that this deref to NSArray is safe! - // This "inherits" NSArray, and has the same `Send`/`Sync` impls as that. + /// A growable ordered collection of objects. + /// + /// See the documentation for [`NSArray`] and/or [Apple's + /// documentation][apple-doc] for more information. + /// + /// [apple-doc]: https://developer.apple.com/documentation/foundation/nsmutablearray?language=objc #[derive(PartialEq, Eq, Hash)] - unsafe pub struct NSMutableArray: NSArray, NSObject { + unsafe pub struct NSMutableArray: NSArray, NSObject { p: PhantomData<*mut ()>, } } @@ -26,27 +30,36 @@ __inner_extern_class! { // SAFETY: Same as NSArray // // Put here because rustdoc doesn't show these otherwise -unsafe impl Sync for NSMutableArray {} -unsafe impl Send for NSMutableArray {} -unsafe impl Sync for NSMutableArray {} -unsafe impl Send for NSMutableArray {} +unsafe impl Sync for NSMutableArray {} +unsafe impl Send for NSMutableArray {} +unsafe impl Sync for NSMutableArray {} +unsafe impl Send for NSMutableArray {} +/// Generic creation methods. impl NSMutableArray { pub fn new() -> Id { - unsafe { msg_send_id![Self::class(), new].unwrap() } + // SAFETY: Same as `NSArray::new`, except mutable arrays are always + // unique. + unsafe { msg_send_id![Self::class(), new].expect("unexpected NULL NSMutableArray") } } pub fn from_vec(vec: Vec>) -> Id { - unsafe { Id::new(from_refs(Self::class(), vec.as_slice_ref()).cast()).unwrap() } + // SAFETY: Same as `NSArray::from_vec`, except mutable arrays are + // always unique. + unsafe { with_objects(Self::class(), vec.as_slice_ref()) } } } +/// Creation methods that produce shared arrays. impl NSMutableArray { pub fn from_slice(slice: &[Id]) -> Id { - unsafe { Id::new(from_refs(Self::class(), slice.as_slice_ref()).cast()).unwrap() } + // SAFETY: Same as `NSArray::from_slice`, except mutable arrays are + // always unique. + unsafe { with_objects(Self::class(), slice.as_slice_ref()) } } } +/// Generic accessor methods. impl NSMutableArray { #[doc(alias = "addObject:")] pub fn push(&mut self, obj: Id) { @@ -154,11 +167,13 @@ impl NSMutableArray { // Copying only possible when ItemOwnership = Shared +/// This is implemented as a shallow copy. unsafe impl NSCopying for NSMutableArray { type Ownership = Shared; type Output = NSArray; } +/// This is implemented as a shallow copy. unsafe impl NSMutableCopying for NSMutableArray { type Output = NSMutableArray; } @@ -226,30 +241,41 @@ mod tests { use super::*; use crate::foundation::NSString; - use crate::rc::autoreleasepool; + use crate::rc::{autoreleasepool, RcTestObject, ThreadTestData}; #[test] fn test_adding() { let mut array = NSMutableArray::new(); - let obj = NSObject::new(); - array.push(obj); - + let obj1 = RcTestObject::new(); + let obj2 = RcTestObject::new(); + let mut expected = ThreadTestData::current(); + + array.push(obj1); + expected.retain += 1; + expected.release += 1; + expected.assert_current(); assert_eq!(array.len(), 1); assert_eq!(array.get(0), array.get(0)); - let obj = NSObject::new(); - array.insert(0, obj); + array.insert(0, obj2); + expected.retain += 1; + expected.release += 1; + expected.assert_current(); assert_eq!(array.len(), 2); } #[test] fn test_replace() { let mut array = NSMutableArray::new(); - let obj = NSObject::new(); - array.push(obj); - - let obj = NSObject::new(); - let old_obj = array.replace(0, obj); + let obj1 = RcTestObject::new(); + let obj2 = RcTestObject::new(); + array.push(obj1); + let mut expected = ThreadTestData::current(); + + let old_obj = array.replace(0, obj2); + expected.retain += 2; + expected.release += 2; + expected.assert_current(); assert_ne!(&*old_obj, array.get(0).unwrap()); } @@ -257,16 +283,26 @@ mod tests { fn test_remove() { let mut array = NSMutableArray::new(); for _ in 0..4 { - array.push(NSObject::new()); + array.push(RcTestObject::new()); } + let mut expected = ThreadTestData::current(); - let _ = array.remove(1); + let _obj = array.remove(1); + expected.retain += 1; + expected.release += 1; + expected.assert_current(); assert_eq!(array.len(), 3); - let _ = array.pop(); + let _obj = array.pop(); + expected.retain += 1; + expected.release += 1; + expected.assert_current(); assert_eq!(array.len(), 2); array.clear(); + expected.release += 2; + expected.dealloc += 2; + expected.assert_current(); assert_eq!(array.len(), 0); } diff --git a/objc2/src/foundation/mutable_data.rs b/objc2/src/foundation/mutable_data.rs index fd60ec806..7e580ef64 100644 --- a/objc2/src/foundation/mutable_data.rs +++ b/objc2/src/foundation/mutable_data.rs @@ -6,7 +6,7 @@ use core::ops::{Index, IndexMut, Range}; use core::slice::{self, SliceIndex}; use std::io; -use super::data::data_with_bytes; +use super::data::with_slice; use super::{NSCopying, NSData, NSMutableCopying, NSObject, NSRange}; use crate::rc::{DefaultId, Id, Owned, Shared}; use crate::{extern_class, msg_send, msg_send_id}; @@ -30,12 +30,12 @@ impl NSMutableData { } pub fn with_bytes(bytes: &[u8]) -> Id { - unsafe { Id::new(data_with_bytes(Self::class(), bytes).cast()).unwrap() } + unsafe { Id::from_shared(Id::cast(with_slice(Self::class(), bytes))) } } #[cfg(feature = "block")] pub fn from_vec(bytes: Vec) -> Id { - unsafe { Id::new(super::data::data_from_vec(Self::class(), bytes).cast()).unwrap() } + unsafe { Id::from_shared(Id::cast(super::data::with_vec(Self::class(), bytes))) } } // TODO: Use malloc_buf/mbox and `initWithBytesNoCopy:...`? diff --git a/objc2/src/foundation/value.rs b/objc2/src/foundation/value.rs index c081a1500..9df659e8f 100644 --- a/objc2/src/foundation/value.rs +++ b/objc2/src/foundation/value.rs @@ -227,9 +227,10 @@ impl fmt::Debug for NSValue { #[cfg(test)] mod tests { use alloc::format; - use core::slice; + use core::{ptr, slice}; use super::*; + use crate::rc::{RcTestObject, ThreadTestData}; #[test] fn basic() { @@ -237,6 +238,27 @@ mod tests { assert_eq!(unsafe { val.get::() }, 13); } + #[test] + fn does_not_retain() { + let obj = RcTestObject::new(); + let expected = ThreadTestData::current(); + + let val = NSValue::new::<*const RcTestObject>(&*obj); + expected.assert_current(); + + assert!(ptr::eq(unsafe { val.get::<*const RcTestObject>() }, &*obj)); + expected.assert_current(); + + let _clone = val.clone(); + expected.assert_current(); + + let _copy = val.copy(); + expected.assert_current(); + + drop(val); + expected.assert_current(); + } + #[test] fn test_equality() { let val1 = NSValue::new(123u32); diff --git a/objc2/src/macros/declare_class.rs b/objc2/src/macros/declare_class.rs index ac68bf9fa..ce1fcb7d3 100644 --- a/objc2/src/macros/declare_class.rs +++ b/objc2/src/macros/declare_class.rs @@ -516,8 +516,6 @@ macro_rules! declare_class { $v fn class() -> &'static $crate::runtime::Class { use $crate::__macro_helpers::Once; - use $crate::declare::ClassBuilder; - use $crate::runtime::{Class, Protocol}; static REGISTER_CLASS: Once = Once::new(); REGISTER_CLASS.call_once(|| { @@ -527,7 +525,7 @@ macro_rules! declare_class { stringify!($name), ". Perhaps a class with that name already exists?", ); - let mut builder = ClassBuilder::new(stringify!($name), superclass).expect(err_str); + let mut builder = $crate::declare::ClassBuilder::new(stringify!($name), superclass).expect(err_str); $( builder.add_ivar::<<$ivar as $crate::declare::IvarType>::Type>( @@ -539,7 +537,7 @@ macro_rules! declare_class { // Implement protocol if any specified $( let err_str = concat!("could not find protocol ", stringify!($protocol)); - builder.add_protocol(Protocol::get(stringify!($protocol)).expect(err_str)); + builder.add_protocol($crate::runtime::Protocol::get(stringify!($protocol)).expect(err_str)); )? // Implement methods @@ -558,7 +556,7 @@ macro_rules! declare_class { }); // We just registered the class, so it should be available - Class::get(stringify!($name)).unwrap() + $crate::runtime::Class::get(stringify!($name)).unwrap() } } diff --git a/objc2/src/macros/extern_class.rs b/objc2/src/macros/extern_class.rs index 46a74b966..7a37e3949 100644 --- a/objc2/src/macros/extern_class.rs +++ b/objc2/src/macros/extern_class.rs @@ -189,14 +189,14 @@ macro_rules! __inner_extern_class { // TODO: Expose this variant in the `object` macro. ( $(#[$m:meta])* - unsafe $v:vis struct $name:ident<$($t:ident $(: $b:ident)?),*>: $($inheritance_chain:ty),+ { + unsafe $v:vis struct $name:ident<$($t:ident $(: $b:ident $(= $default:ty)?)?),*>: $($inheritance_chain:ty),+ { $($field_vis:vis $field:ident: $field_ty:ty,)* } ) => { $crate::__inner_extern_class! { @__inner $(#[$m])* - unsafe $v struct $name<$($t $(: $b)?),*>: $($inheritance_chain,)+ $crate::runtime::Object { + unsafe $v struct $name<$($t $(: $b $(= $default)?)?),*>: $($inheritance_chain,)+ $crate::runtime::Object { $($field_vis $field: $field_ty,)* } } @@ -217,14 +217,14 @@ macro_rules! __inner_extern_class { ( @__inner $(#[$m:meta])* - unsafe $v:vis struct $name:ident<$($t:ident $(: $b:ident)?),*>: $superclass:ty $(, $inheritance_rest:ty)* { + unsafe $v:vis struct $name:ident<$($t:ident $(: $b:ident $(= $default:ty)?)?),*>: $superclass:ty $(, $inheritance_rest:ty)* { $($field_vis:vis $field:ident: $field_ty:ty,)* } ) => { $(#[$m])* // TODO: repr(transparent) when the inner pointer is no longer a ZST. #[repr(C)] - $v struct $name<$($t $(: $b)?),*> { + $v struct $name<$($t $(: $b $(= $default)?)?),*> { __inner: $superclass, // Additional fields (should only be zero-sized PhantomData or ivars). $($field_vis $field: $field_ty,)* diff --git a/objc2/src/rc/test_object.rs b/objc2/src/rc/test_object.rs index 2f37c1822..f9091c1cb 100644 --- a/objc2/src/rc/test_object.rs +++ b/objc2/src/rc/test_object.rs @@ -1,12 +1,11 @@ use core::cell::RefCell; -use core::ops::{Deref, DerefMut}; -use std::sync::Once; +use core::mem::ManuallyDrop; +use core::ptr; use super::{Id, Owned}; -use crate::declare::ClassBuilder; -use crate::runtime::{Bool, Class, Object, Sel}; -use crate::{class, msg_send, msg_send_bool, sel}; -use crate::{Encoding, Message, RefEncode}; +use crate::foundation::{NSObject, NSZone}; +use crate::runtime::Bool; +use crate::{declare_class, msg_send, msg_send_bool}; #[derive(Debug, Clone, Default, PartialEq, Eq)] pub(crate) struct ThreadTestData { @@ -14,6 +13,8 @@ pub(crate) struct ThreadTestData { pub(crate) dealloc: usize, pub(crate) init: usize, pub(crate) retain: usize, + pub(crate) copy: usize, + pub(crate) mutable_copy: usize, pub(crate) release: usize, pub(crate) autorelease: usize, pub(crate) try_retain: usize, @@ -47,96 +48,87 @@ std::thread_local! { pub(crate) static TEST_DATA: RefCell = RefCell::new(Default::default()); } -/// A helper object that counts how many times various reference-counting -/// primitives are called. -#[repr(C)] -pub(crate) struct RcTestObject { - inner: Object, -} +declare_class! { + /// A helper object that counts how many times various reference-counting + /// primitives are called. + #[derive(Debug, PartialEq)] + unsafe pub(crate) struct RcTestObject: NSObject {} + + unsafe impl { + #[sel(alloc)] + fn alloc() -> *mut Self { + TEST_DATA.with(|data| data.borrow_mut().alloc += 1); + let superclass = NSObject::class().metaclass(); + let zone: *const NSZone = ptr::null(); + unsafe { msg_send![super(Self::class(), superclass), allocWithZone: zone] } + } -unsafe impl RefEncode for RcTestObject { - const ENCODING_REF: Encoding<'static> = Object::ENCODING_REF; -} + #[sel(allocWithZone:)] + fn alloc_with_zone(zone: *const NSZone) -> *mut Self { + TEST_DATA.with(|data| data.borrow_mut().alloc += 1); + let superclass = NSObject::class().metaclass(); + unsafe { msg_send![super(Self::class(), superclass), allocWithZone: zone] } + } -unsafe impl Message for RcTestObject {} + #[sel(init)] + fn init(&mut self) -> *mut Self { + TEST_DATA.with(|data| data.borrow_mut().init += 1); + unsafe { msg_send![super(self, NSObject::class()), init] } + } -unsafe impl Send for RcTestObject {} -unsafe impl Sync for RcTestObject {} + #[sel(retain)] + fn retain(&self) -> *mut Self { + TEST_DATA.with(|data| data.borrow_mut().retain += 1); + unsafe { msg_send![super(self, NSObject::class()), retain] } + } -impl Deref for RcTestObject { - type Target = Object; - fn deref(&self) -> &Self::Target { - &self.inner - } -} + #[sel(release)] + fn release(&self) { + TEST_DATA.with(|data| data.borrow_mut().release += 1); + unsafe { msg_send![super(self, NSObject::class()), release] } + } -impl DerefMut for RcTestObject { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inner - } -} + #[sel(autorelease)] + fn autorelease(&self) -> *mut Self { + TEST_DATA.with(|data| data.borrow_mut().autorelease += 1); + unsafe { msg_send![super(self, NSObject::class()), autorelease] } + } -impl RcTestObject { - pub(crate) fn class() -> &'static Class { - static REGISTER_CLASS: Once = Once::new(); - - REGISTER_CLASS.call_once(|| { - extern "C" fn alloc(cls: &Class, _cmd: Sel) -> *mut RcTestObject { - TEST_DATA.with(|data| data.borrow_mut().alloc += 1); - let superclass = class!(NSObject).metaclass(); - unsafe { msg_send![super(cls, superclass), alloc] } - } - extern "C" fn init(this: &mut RcTestObject, _cmd: Sel) -> *mut RcTestObject { - TEST_DATA.with(|data| data.borrow_mut().init += 1); - unsafe { msg_send![super(this, class!(NSObject)), init] } - } - extern "C" fn retain(this: &RcTestObject, _cmd: Sel) -> *mut RcTestObject { - TEST_DATA.with(|data| data.borrow_mut().retain += 1); - unsafe { msg_send![super(this, class!(NSObject)), retain] } - } - extern "C" fn release(this: &RcTestObject, _cmd: Sel) { - TEST_DATA.with(|data| data.borrow_mut().release += 1); - unsafe { msg_send![super(this, class!(NSObject)), release] } - } - extern "C" fn autorelease(this: &RcTestObject, _cmd: Sel) -> *mut RcTestObject { - TEST_DATA.with(|data| data.borrow_mut().autorelease += 1); - unsafe { msg_send![super(this, class!(NSObject)), autorelease] } - } - unsafe extern "C" fn dealloc(_this: *mut RcTestObject, _cmd: Sel) { - TEST_DATA.with(|data| data.borrow_mut().dealloc += 1); - // Don't call superclass - } - unsafe extern "C" fn try_retain(this: &RcTestObject, _cmd: Sel) -> Bool { - TEST_DATA.with(|data| data.borrow_mut().try_retain += 1); - let res = unsafe { msg_send_bool![super(this, class!(NSObject)), _tryRetain] }; - if !res { - TEST_DATA.with(|data| data.borrow_mut().try_retain -= 1); - TEST_DATA.with(|data| data.borrow_mut().try_retain_fail += 1); - } - Bool::from(res) - } + #[sel(dealloc)] + unsafe fn dealloc(&mut self) { + TEST_DATA.with(|data| data.borrow_mut().dealloc += 1); + // Don't call superclass + } - let mut builder = ClassBuilder::new("RcTestObject", class!(NSObject)).unwrap(); - unsafe { - builder.add_class_method(sel!(alloc), alloc as extern "C" fn(_, _) -> _); - builder.add_method(sel!(init), init as extern "C" fn(_, _) -> _); - builder.add_method(sel!(retain), retain as extern "C" fn(_, _) -> _); - builder.add_method( - sel!(_tryRetain), - try_retain as unsafe extern "C" fn(_, _) -> _, - ); - builder.add_method(sel!(release), release as extern "C" fn(_, _)); - builder.add_method(sel!(autorelease), autorelease as extern "C" fn(_, _) -> _); - builder.add_method(sel!(dealloc), dealloc as unsafe extern "C" fn(_, _)); + #[sel(_tryRetain)] + unsafe fn try_retain(&self) -> Bool { + TEST_DATA.with(|data| data.borrow_mut().try_retain += 1); + let res = unsafe { msg_send_bool![super(self, NSObject::class()), _tryRetain] }; + if !res { + TEST_DATA.with(|data| data.borrow_mut().try_retain -= 1); + TEST_DATA.with(|data| data.borrow_mut().try_retain_fail += 1); } + Bool::from(res) + } - let _cls = builder.register(); - }); + #[sel(copyWithZone:)] + fn copy_with_zone(&self, _zone: *const NSZone) -> *const Self { + TEST_DATA.with(|data| data.borrow_mut().copy += 1); + Id::consume_as_ptr(ManuallyDrop::new(Self::new())) + } - // Can't use `class!` here since `RcTestObject` is dynamically created. - Class::get("RcTestObject").unwrap() + #[sel(mutableCopyWithZone:)] + fn mutable_copy_with_zone(&self, _zone: *const NSZone) -> *const Self { + TEST_DATA.with(|data| data.borrow_mut().mutable_copy += 1); + Id::consume_as_ptr(ManuallyDrop::new(Self::new())) + } } +} +unsafe impl Send for RcTestObject {} +unsafe impl Sync for RcTestObject {} + +impl RcTestObject { pub(crate) fn new() -> Id { // Use msg_send! - msg_send_id! is tested elsewhere! unsafe { Id::new(msg_send![Self::class(), new]) }.unwrap() diff --git a/test-ui/ui/nsarray_bound_not_send_sync.stderr b/test-ui/ui/nsarray_bound_not_send_sync.stderr index a6c04f106..76903c6ec 100644 --- a/test-ui/ui/nsarray_bound_not_send_sync.stderr +++ b/test-ui/ui/nsarray_bound_not_send_sync.stderr @@ -7,7 +7,7 @@ error[E0277]: `UnsafeCell, PhantomPinned)>>` = help: within `objc2::runtime::Object`, the trait `Sync` is not implemented for `UnsafeCell, PhantomPinned)>>` = note: required because it appears within the type `objc_object` = note: required because it appears within the type `objc2::runtime::Object` - = note: required because of the requirements on the impl of `Sync` for `NSArray` + = note: required because of the requirements on the impl of `Sync` for `NSArray` note: required by a bound in `needs_sync` --> ui/nsarray_bound_not_send_sync.rs | @@ -26,7 +26,7 @@ error[E0277]: `*const UnsafeCell<()>` cannot be sent between threads safely = note: required because it appears within the type `UnsafeCell, PhantomPinned)>>` = note: required because it appears within the type `objc_object` = note: required because it appears within the type `objc2::runtime::Object` - = note: required because of the requirements on the impl of `Sync` for `NSArray` + = note: required because of the requirements on the impl of `Sync` for `NSArray` note: required by a bound in `needs_sync` --> ui/nsarray_bound_not_send_sync.rs | @@ -42,7 +42,7 @@ error[E0277]: `UnsafeCell, PhantomPinned)>>` = help: within `objc2::runtime::Object`, the trait `Sync` is not implemented for `UnsafeCell, PhantomPinned)>>` = note: required because it appears within the type `objc_object` = note: required because it appears within the type `objc2::runtime::Object` - = note: required because of the requirements on the impl of `Send` for `NSArray` + = note: required because of the requirements on the impl of `Send` for `NSArray` note: required by a bound in `needs_send` --> ui/nsarray_bound_not_send_sync.rs | @@ -61,7 +61,7 @@ error[E0277]: `*const UnsafeCell<()>` cannot be sent between threads safely = note: required because it appears within the type `UnsafeCell, PhantomPinned)>>` = note: required because it appears within the type `objc_object` = note: required because it appears within the type `objc2::runtime::Object` - = note: required because of the requirements on the impl of `Send` for `NSArray` + = note: required because of the requirements on the impl of `Send` for `NSArray` note: required by a bound in `needs_send` --> ui/nsarray_bound_not_send_sync.rs | diff --git a/tests/src/exception.rs b/tests/src/exception.rs index 8f48fa160..1a162d2fb 100644 --- a/tests/src/exception.rs +++ b/tests/src/exception.rs @@ -3,7 +3,7 @@ use alloc::format; use objc2::exception::{catch, throw}; use objc2::foundation::{NSArray, NSException, NSString}; use objc2::msg_send; -use objc2::rc::{autoreleasepool, Id}; +use objc2::rc::{autoreleasepool, Id, Shared}; use objc2::runtime::Object; #[track_caller] @@ -122,7 +122,7 @@ fn raise_catch() { fn catch_actual() { let res = unsafe { catch(|| { - let arr: Id, _> = NSArray::new(); + let arr: Id, Shared> = NSArray::new(); let _obj: *mut Object = msg_send![&arr, objectAtIndex: 0usize]; }) };