diff --git a/objc2/src/declare.rs b/objc2/src/declare.rs index 69a16f293..3309bacb2 100644 --- a/objc2/src/declare.rs +++ b/objc2/src/declare.rs @@ -56,8 +56,12 @@ pub trait MethodImplementation { macro_rules! method_decl_impl { (-$s:ident, $r:ident, $f:ty, $($t:ident),*) => ( - impl<$s, $r $(, $t)*> MethodImplementation for $f - where $s: Message, $r: Encode $(, $t: Encode)* { + impl<$s, $r, $($t),*> MethodImplementation for $f + where + $s: Message, + $r: Encode, + $($t: Encode,)* + { type Callee = $s; type Ret = $r; type Args = ($($t,)*); @@ -68,8 +72,8 @@ macro_rules! method_decl_impl { } ); ($($t:ident),*) => ( - method_decl_impl!(-T, R, extern fn(&T, Sel $(, $t)*) -> R, $($t),*); - method_decl_impl!(-T, R, extern fn(&mut T, Sel $(, $t)*) -> R, $($t),*); + method_decl_impl!(-T, R, extern "C" fn(&T, Sel $(, $t)*) -> R, $($t),*); + method_decl_impl!(-T, R, extern "C" fn(&mut T, Sel $(, $t)*) -> R, $($t),*); ); } diff --git a/objc2/src/rc/autorelease.rs b/objc2/src/rc/autorelease.rs index b23bba35b..66153591d 100644 --- a/objc2/src/rc/autorelease.rs +++ b/objc2/src/rc/autorelease.rs @@ -58,6 +58,23 @@ impl AutoreleasePool { Self { context } } + /// This will be removed in a future version. + #[cfg_attr( + all(debug_assertions, not(feature = "unstable_autoreleasesafe")), + inline + )] + #[doc(hidden)] + pub fn __verify_is_inner(&self) { + #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] + POOLS.with(|c| { + assert_eq!( + c.borrow().last(), + Some(&self.context), + "Tried to use lifetime from pool that was not innermost" + ) + }); + } + /// Returns a shared reference to the given autoreleased pointer object. /// /// This is the preferred way to make references from autoreleased @@ -70,20 +87,10 @@ impl AutoreleasePool { /// /// This is equivalent to `&*ptr`, and shares the unsafety of that, except /// the lifetime is bound to the pool instead of being unbounded. - #[cfg_attr( - all(debug_assertions, not(feature = "unstable_autoreleasesafe")), - inline - )] + #[inline] #[allow(clippy::needless_lifetimes)] pub unsafe fn ptr_as_ref<'p, T>(&'p self, ptr: *const T) -> &'p T { - #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] - POOLS.with(|c| { - assert_eq!( - c.borrow().last(), - Some(&self.context), - "Tried to create shared reference with a lifetime from a pool that was not the innermost pool" - ) - }); + self.__verify_is_inner(); // SAFETY: Checked by the caller &*ptr } @@ -100,21 +107,11 @@ impl AutoreleasePool { /// /// This is equivalent to `&mut *ptr`, and shares the unsafety of that, /// except the lifetime is bound to the pool instead of being unbounded. - #[cfg_attr( - all(debug_assertions, not(feature = "unstable_autoreleasesafe")), - inline - )] + #[inline] #[allow(clippy::needless_lifetimes)] #[allow(clippy::mut_from_ref)] pub unsafe fn ptr_as_mut<'p, T>(&'p self, ptr: *mut T) -> &'p mut T { - #[cfg(all(debug_assertions, not(feature = "unstable_autoreleasesafe")))] - POOLS.with(|c| { - assert_eq!( - c.borrow().last(), - Some(&self.context), - "Tried to create unique reference with a lifetime from a pool that was not the innermost pool") - } - ); + self.__verify_is_inner(); // SAFETY: Checked by the caller &mut *ptr } diff --git a/objc2_block/src/lib.rs b/objc2_block/src/lib.rs index 1c6ea8c97..150120d78 100644 --- a/objc2_block/src/lib.rs +++ b/objc2_block/src/lib.rs @@ -231,19 +231,24 @@ macro_rules! concrete_block_impl { ); ($f:ident, $($a:ident : $t:ident),*) => ( impl<$($t: Encode,)* R: Encode, X> IntoConcreteBlock<($($t,)*)> for X - where X: Fn($($t,)*) -> R { + where + X: Fn($($t,)*) -> R, + { type Ret = R; fn into_concrete_block(self) -> ConcreteBlock<($($t,)*), R, X> { unsafe extern fn $f<$($t,)* R, X>( - block_ptr: *mut ConcreteBlock<($($t,)*), R, X> - $(, $a: $t)*) -> R - where X: Fn($($t,)*) -> R { + block_ptr: *mut ConcreteBlock<($($t,)*), R, X> + $(, $a: $t)* + ) -> R + where + X: Fn($($t,)*) -> R + { let block = &*block_ptr; (block.closure)($($a),*) } - let f: unsafe extern fn(*mut ConcreteBlock<($($t,)*), R, X> $(, $a: $t)*) -> R = $f; + let f: unsafe extern "C" fn(*mut ConcreteBlock<($($t,)*), R, X> $(, $a: $t)*) -> R = $f; unsafe { ConcreteBlock::with_invoke(mem::transmute(f), self) } @@ -397,10 +402,7 @@ impl ConcreteBlock { } } -impl ConcreteBlock -where - F: 'static, -{ +impl ConcreteBlock { /// Copy self onto the heap as an `RcBlock`. pub fn copy(self) -> RcBlock { unsafe { @@ -415,10 +417,7 @@ where } } -impl Clone for ConcreteBlock -where - F: Clone, -{ +impl Clone for ConcreteBlock { fn clone(&self) -> Self { unsafe { ConcreteBlock::with_invoke(mem::transmute(self.base.invoke), self.closure.clone()) diff --git a/objc2_foundation/examples/basic_usage.rs b/objc2_foundation/examples/basic_usage.rs index 6af8fb83c..68963f36e 100644 --- a/objc2_foundation/examples/basic_usage.rs +++ b/objc2_foundation/examples/basic_usage.rs @@ -1,6 +1,6 @@ +use objc2::rc::autoreleasepool; use objc2_foundation::{ - INSArray, INSCopying, INSDictionary, INSObject, INSString, NSArray, NSDictionary, NSObject, - NSString, + INSArray, INSCopying, INSDictionary, INSString, NSArray, NSDictionary, NSObject, NSString, }; fn main() { @@ -17,7 +17,7 @@ fn main() { for obj in array.object_enumerator() { println!("{:?}", obj); } - println!("{}", array.count()); + println!("{}", array.len()); // Turn the NSArray back into a Vec let mut objs = NSArray::into_vec(array); @@ -25,14 +25,17 @@ fn main() { // Create an NSString from a str slice let string = NSString::from_str("Hello, world!"); - println!("{}", string.as_str()); - let string2 = string.copy(); - println!("{}", string2.as_str()); + // Use an autoreleasepool to get the `str` contents of the NSString + autoreleasepool(|pool| { + println!("{}", string.as_str(pool)); + let string2 = string.copy(); + println!("{}", string2.as_str(pool)); + }); // Create a dictionary mapping strings to objects let keys = &[&*string]; let vals = vec![obj]; let dict = NSDictionary::from_keys_and_objects(keys, vals); - println!("{:?}", dict.object_for(&string)); - println!("{}", dict.count()); + println!("{:?}", dict.get(&string)); + println!("{}", dict.len()); } diff --git a/objc2_foundation/examples/class_with_lifetime.rs b/objc2_foundation/examples/class_with_lifetime.rs new file mode 100644 index 000000000..63230f156 --- /dev/null +++ b/objc2_foundation/examples/class_with_lifetime.rs @@ -0,0 +1,104 @@ +use std::marker::PhantomData; +use std::ptr::NonNull; +use std::sync::Once; + +use objc2::declare::ClassDecl; +use objc2::rc::{Id, Owned, Shared}; +use objc2::runtime::{Class, Object, Sel}; +use objc2::{msg_send, sel}; +use objc2::{Encoding, Message, RefEncode}; +use objc2_foundation::{INSObject, NSObject}; + +#[repr(C)] +pub struct MyObject<'a> { + _priv: [u8; 0], + // `init` defaults ivars to all zeroes, so allow for that here + // TODO: Verify this claim! + p: PhantomData>, +} + +unsafe impl RefEncode for MyObject<'_> { + const ENCODING_REF: Encoding<'static> = Encoding::Object; +} + +unsafe impl Message for MyObject<'_> {} + +impl<'a> MyObject<'a> { + fn new(number_ptr: &'a mut u8) -> Id { + unsafe { + let obj: *mut Self = msg_send![Self::class(), alloc]; + let obj: *mut Self = msg_send![obj, initWithPtr: number_ptr]; + Id::new(NonNull::new_unchecked(obj)) + } + } + + fn get(&self) -> Option<&'a u8> { + unsafe { + let obj = &*(self as *const _ as *const Object); + *obj.get_ivar("_number_ptr") + } + } + + fn write(&mut self, number: u8) { + let ptr: &mut Option<&'a mut u8> = unsafe { + let obj = &mut *(self as *mut _ as *mut Object); + obj.get_mut_ivar("_number_ptr") + }; + if let Some(ptr) = ptr { + **ptr = number; + } + } +} + +static MYOBJECT_REGISTER_CLASS: Once = Once::new(); + +unsafe impl INSObject for MyObject<'_> { + type Ownership = Owned; + + fn class() -> &'static Class { + MYOBJECT_REGISTER_CLASS.call_once(|| { + let superclass = NSObject::class(); + let mut decl = ClassDecl::new("MyObject", superclass).unwrap(); + decl.add_ivar::<&mut u8>("_number_ptr"); + + extern "C" fn init_with_ptr(this: &mut Object, _cmd: Sel, ptr: *mut u8) -> *mut Object { + unsafe { + this.set_ivar("_number_ptr", ptr); + } + this + } + + unsafe { + let init_with_ptr: extern "C" fn(&mut Object, Sel, *mut u8) -> *mut Object = + init_with_ptr; + decl.add_method(sel!(initWithPtr:), init_with_ptr); + } + + decl.register(); + }); + + Class::get("MyObject").unwrap() + } +} + +fn main() { + let mut number = 54; + let mut obj = MyObject::new(&mut number); + + println!("Number: {}", obj.get().unwrap()); + + obj.write(7); + // Won't compile, since `obj` holds a mutable reference to number + // println!("Number: {}", number); + println!("Number: {}", obj.get().unwrap()); + + let obj: Id<_, Shared> = obj.into(); + let obj2 = obj.clone(); + + println!("Number: {}", obj.get().unwrap()); + println!("Number: {}", obj2.get().unwrap()); + + drop(obj); + drop(obj2); + println!("Number: {}", number); +} diff --git a/objc2_foundation/examples/custom_class.rs b/objc2_foundation/examples/custom_class.rs index e1f3916b1..e2b5b4666 100644 --- a/objc2_foundation/examples/custom_class.rs +++ b/objc2_foundation/examples/custom_class.rs @@ -1,6 +1,8 @@ +use std::ptr::NonNull; use std::sync::Once; use objc2::declare::ClassDecl; +use objc2::rc::{Id, Owned}; use objc2::runtime::{Class, Object, Sel}; use objc2::{msg_send, sel}; use objc2::{Encoding, Message, RefEncode}; @@ -21,6 +23,11 @@ unsafe impl RefEncode for MYObject { } impl MYObject { + fn new() -> Id::Ownership> { + let cls = Self::class(); + unsafe { Id::new(NonNull::new_unchecked(msg_send![cls, new])) } + } + fn number(&self) -> u32 { unsafe { let obj = &*(self as *const _ as *const Object); @@ -40,7 +47,9 @@ unsafe impl Message for MYObject {} static MYOBJECT_REGISTER_CLASS: Once = Once::new(); -impl INSObject for MYObject { +unsafe impl INSObject for MYObject { + type Ownership = Owned; + fn class() -> &'static Class { MYOBJECT_REGISTER_CLASS.call_once(|| { let superclass = NSObject::class(); diff --git a/objc2_foundation/src/array.rs b/objc2_foundation/src/array.rs index d1b6dcb97..5c0a84946 100644 --- a/objc2_foundation/src/array.rs +++ b/objc2_foundation/src/array.rs @@ -8,73 +8,13 @@ use core::ptr::NonNull; use objc2::rc::{Id, Owned, Ownership, Shared, SliceId}; use objc2::runtime::{Class, Object}; use objc2::{class, msg_send}; -use objc2::{Encode, Encoding}; -use super::{INSCopying, INSFastEnumeration, INSMutableCopying, INSObject, NSEnumerator}; +use super::{ + INSCopying, INSFastEnumeration, INSMutableCopying, INSObject, NSComparisonResult, NSEnumerator, + NSRange, +}; -#[repr(isize)] -#[derive(Clone, Copy)] -pub enum NSComparisonResult { - Ascending = -1, - Same = 0, - Descending = 1, -} - -unsafe impl Encode for NSComparisonResult { - const ENCODING: Encoding<'static> = isize::ENCODING; -} - -impl NSComparisonResult { - pub fn from_ordering(order: Ordering) -> NSComparisonResult { - match order { - Ordering::Less => NSComparisonResult::Ascending, - Ordering::Equal => NSComparisonResult::Same, - Ordering::Greater => NSComparisonResult::Descending, - } - } - - pub fn as_ordering(&self) -> Ordering { - match *self { - NSComparisonResult::Ascending => Ordering::Less, - NSComparisonResult::Same => Ordering::Equal, - NSComparisonResult::Descending => Ordering::Greater, - } - } -} - -#[repr(C)] -#[derive(Clone, Copy)] -pub struct NSRange { - pub location: usize, - pub length: usize, -} - -impl NSRange { - pub fn from_range(range: Range) -> NSRange { - assert!(range.end >= range.start); - NSRange { - location: range.start, - length: range.end - range.start, - } - } - - pub fn as_range(&self) -> Range { - Range { - start: self.location, - end: self.location + self.length, - } - } -} - -unsafe impl Encode for NSRange { - const ENCODING: Encoding<'static> = - Encoding::Struct("_NSRange", &[usize::ENCODING, usize::ENCODING]); -} - -unsafe fn from_refs(refs: &[&A::Item]) -> Id -where - A: INSArray, -{ +unsafe fn from_refs(refs: &[&A::Item]) -> Id { let cls = A::class(); let obj: *mut A = msg_send![cls, alloc]; let obj: *mut A = msg_send![ @@ -85,56 +25,92 @@ where Id::new(NonNull::new_unchecked(obj)) } -pub trait INSArray: INSObject { +pub unsafe trait INSArray: INSObject { type Item: INSObject; - type Own: Ownership; + type ItemOwnership: Ownership; - fn count(&self) -> usize { + unsafe_def_fn!(fn new); + + #[doc(alias = "count")] + fn len(&self) -> usize { unsafe { msg_send![self, count] } } - fn object_at(&self, index: usize) -> &Self::Item { - unsafe { - let obj: *const Self::Item = msg_send![self, objectAtIndex: index]; - &*obj + #[doc(alias = "objectAtIndex:")] + fn get(&self, index: usize) -> Option<&Self::Item> { + // TODO: Replace this check with catching the thrown NSRangeException + if index < self.len() { + // SAFETY: The index is checked to be in bounds. + Some(unsafe { msg_send![self, objectAtIndex: index] }) + } else { + None } } - fn first_object(&self) -> Option<&Self::Item> { - unsafe { - let obj: *const Self::Item = msg_send![self, firstObject]; - if obj.is_null() { - None - } else { - Some(&*obj) - } + #[doc(alias = "objectAtIndex:")] + fn get_mut(&mut self, index: usize) -> Option<&mut Self::Item> + where + Self: INSArray, + { + // TODO: Replace this check with catching the thrown NSRangeException + if index < self.len() { + // SAFETY: The index is checked to be in bounds. + Some(unsafe { msg_send![self, objectAtIndex: index] }) + } else { + None } } - fn last_object(&self) -> Option<&Self::Item> { - unsafe { - let obj: *const Self::Item = msg_send![self, lastObject]; - if obj.is_null() { - None - } else { - Some(&*obj) - } - } + #[doc(alias = "objectAtIndex:")] + fn get_retained(&self, index: usize) -> Id + where + Self: INSArray, + { + let obj = self.get(index).unwrap(); + // SAFETY: The object is originally shared (see `where` bound). + unsafe { Id::retain(obj.into()) } + } + + #[doc(alias = "firstObject")] + fn first(&self) -> Option<&Self::Item> { + unsafe { msg_send![self, firstObject] } + } + + #[doc(alias = "firstObject")] + fn first_mut(&mut self) -> Option<&mut Self::Item> + where + Self: INSArray, + { + unsafe { msg_send![self, firstObject] } + } + + #[doc(alias = "lastObject")] + fn last(&self) -> Option<&Self::Item> { + unsafe { msg_send![self, lastObject] } + } + + #[doc(alias = "lastObject")] + fn last_mut(&mut self) -> Option<&mut Self::Item> + where + Self: INSArray, + { + unsafe { msg_send![self, lastObject] } } - fn object_enumerator(&self) -> NSEnumerator { + #[doc(alias = "objectEnumerator")] + fn iter<'a>(&'a self) -> NSEnumerator<'a, Self::Item> { unsafe { let result: *mut Object = msg_send![self, objectEnumerator]; NSEnumerator::from_ptr(result) } } - fn from_vec(vec: Vec>) -> Id { + fn from_vec(vec: Vec>) -> Id { unsafe { from_refs(vec.as_slice_ref()) } } fn objects_in_range(&self, range: Range) -> Vec<&Self::Item> { - let range = NSRange::from_range(range); + let range = NSRange::from(range); let mut vec = Vec::with_capacity(range.length); unsafe { let _: () = msg_send![self, getObjects: vec.as_ptr(), range: range]; @@ -144,10 +120,11 @@ pub trait INSArray: INSObject { } fn to_vec(&self) -> Vec<&Self::Item> { - self.objects_in_range(0..self.count()) + self.objects_in_range(0..self.len()) } - fn into_vec(array: Id) -> Vec> { + // TODO: Take Id ? + fn into_vec(array: Id) -> Vec> { array .to_vec() .into_iter() @@ -155,34 +132,16 @@ pub trait INSArray: INSObject { .collect() } - fn mut_object_at(&mut self, index: usize) -> &mut Self::Item - where - Self: INSArray, - { - unsafe { - let result: *mut Self::Item = msg_send![self, objectAtIndex: index]; - &mut *result - } - } - - fn shared_object_at(&self, index: usize) -> Id - where - Self: INSArray, - { - let obj = self.object_at(index); - unsafe { Id::retain(obj.into()) } - } - - fn from_slice(slice: &[Id]) -> Id + fn from_slice(slice: &[Id]) -> Id where - Self: INSArray, + Self: INSArray, { unsafe { from_refs(slice.as_slice_ref()) } } fn to_shared_vec(&self) -> Vec> where - Self: INSArray, + Self: INSArray, { self.to_vec() .into_iter() @@ -191,87 +150,91 @@ pub trait INSArray: INSObject { } } -pub struct NSArray { +/// TODO +/// +/// 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. +/// +/// `Id, Shared>` is possible, but pretty useless. +/// TODO: Can we make it impossible? Should we? +pub struct NSArray { item: PhantomData>, } -object_impl!(NSArray); +object_impl!(unsafe NSArray); + +unsafe impl INSObject for 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. + /// + /// We only implement `INSCopying` when `O = Shared`, so this is safe. + type Ownership = O; -impl INSObject for NSArray -where - T: INSObject, - O: Ownership, -{ fn class() -> &'static Class { class!(NSArray) } } -impl INSArray for NSArray -where - T: INSObject, - O: Ownership, -{ +unsafe impl INSArray for NSArray { type Item = T; - type Own = O; + type ItemOwnership = O; } -impl INSCopying for NSArray -where - T: INSObject, -{ - type Output = NSSharedArray; +// Copying only possible when ItemOwnership = Shared + +unsafe impl INSCopying for NSArray { + type Output = NSArray; } -impl INSMutableCopying for NSArray -where - T: INSObject, -{ - type Output = NSMutableSharedArray; +unsafe impl INSMutableCopying for NSArray { + type Output = NSMutableArray; } -impl INSFastEnumeration for NSArray -where - T: INSObject, - O: Ownership, -{ +unsafe impl INSFastEnumeration for NSArray { type Item = T; } -impl Index for NSArray -where - T: INSObject, - O: Ownership, -{ +impl Index for NSArray { type Output = T; fn index(&self, index: usize) -> &T { - self.object_at(index) + self.get(index).unwrap() } } -pub type NSSharedArray = NSArray; - -pub trait INSMutableArray: INSArray { - fn add_object(&mut self, obj: Id) { - unsafe { - let _: () = msg_send![self, addObject: &*obj]; +pub unsafe trait INSMutableArray: INSArray { + #[doc(alias = "addObject:")] + fn push(&mut self, obj: Id) { + // SAFETY: The object is not nil + unsafe { msg_send![self, addObject: &*obj] } + } + + #[doc(alias = "insertObject:atIndex:")] + fn insert(&mut self, index: usize, obj: Id) { + // TODO: Replace this check with catching the thrown NSRangeException + let len = self.len(); + if index < len { + // SAFETY: The object is not nil and the index is checked to be in + // bounds. + unsafe { msg_send![self, insertObject: &*obj, atIndex: index] } + } else { + panic!( + "insertion index (is {}) should be <= len (is {})", + index, len + ); } } - fn insert_object_at(&mut self, index: usize, obj: Id) { - unsafe { - let _: () = msg_send![self, insertObject: &*obj, atIndex: index]; - } - } - - fn replace_object_at( + #[doc(alias = "replaceObjectAtIndex:withObject:")] + fn replace( &mut self, index: usize, - obj: Id, - ) -> Id { + obj: Id, + ) -> Id { let old_obj = unsafe { - let obj = self.object_at(index); + let obj = self.get(index).unwrap(); Id::retain(obj.into()) }; unsafe { @@ -284,10 +247,12 @@ pub trait INSMutableArray: INSArray { old_obj } - fn remove_object_at(&mut self, index: usize) -> Id { - let obj = unsafe { - let obj = self.object_at(index); - Id::retain(obj.into()) + #[doc(alias = "removeObjectAtIndex:")] + fn remove(&mut self, index: usize) -> Id { + let obj = if let Some(obj) = self.get(index) { + unsafe { Id::retain(obj.into()) } + } else { + panic!("removal index should be < len"); }; unsafe { let _: () = msg_send![self, removeObjectAtIndex: index]; @@ -295,24 +260,23 @@ pub trait INSMutableArray: INSArray { obj } - fn remove_last_object(&mut self) -> Id { - let obj = self - .last_object() - .map(|obj| unsafe { Id::retain(obj.into()) }); - unsafe { - let _: () = msg_send![self, removeLastObject]; - } - // removeLastObject would have failed if the array is empty, - // so we know this won't be None - obj.unwrap() + #[doc(alias = "removeLastObject")] + fn pop(&mut self) -> Option> { + self.last().map(|obj| { + let obj = unsafe { Id::retain(obj.into()) }; + unsafe { + let _: () = msg_send![self, removeLastObject]; + } + obj + }) } - fn remove_all_objects(&mut self) { - unsafe { - let _: () = msg_send![self, removeAllObjects]; - } + #[doc(alias = "removeAllObjects")] + fn clear(&mut self) { + unsafe { msg_send![self, removeAllObjects] } } + #[doc(alias = "sortUsingFunction:context:")] fn sort_by(&mut self, compare: F) where F: FnMut(&Self::Item, &Self::Item) -> Ordering, @@ -327,7 +291,7 @@ pub trait INSMutableArray: INSArray { // ownership, and that method only runs one function at a time. let closure: &mut F = unsafe { &mut *(context as *mut F) }; - NSComparisonResult::from_ordering((*closure)(obj1, obj2)) + NSComparisonResult::from((*closure)(obj1, obj2)) } let f: extern "C" fn(_, _, _) -> _ = compare_with_closure::; @@ -344,84 +308,61 @@ pub trait INSMutableArray: INSArray { } } -pub struct NSMutableArray { +pub struct NSMutableArray { item: PhantomData>, } -object_impl!(NSMutableArray); +object_impl!(unsafe NSMutableArray); + +unsafe impl INSObject for NSMutableArray { + type Ownership = Owned; -impl INSObject for NSMutableArray -where - T: INSObject, - O: Ownership, -{ fn class() -> &'static Class { class!(NSMutableArray) } } -impl INSArray for NSMutableArray -where - T: INSObject, - O: Ownership, -{ +unsafe impl INSArray for NSMutableArray { type Item = T; - type Own = O; + type ItemOwnership = O; } -impl INSMutableArray for NSMutableArray -where - T: INSObject, - O: Ownership, -{ -} +unsafe impl INSMutableArray for NSMutableArray {} + +// Copying only possible when ItemOwnership = Shared -impl INSCopying for NSMutableArray -where - T: INSObject, -{ - type Output = NSSharedArray; +unsafe impl INSCopying for NSMutableArray { + type Output = NSArray; } -impl INSMutableCopying for NSMutableArray -where - T: INSObject, -{ - type Output = NSMutableSharedArray; +unsafe impl INSMutableCopying for NSMutableArray { + type Output = NSMutableArray; } -impl INSFastEnumeration for NSMutableArray -where - T: INSObject, - O: Ownership, -{ +unsafe impl INSFastEnumeration for NSMutableArray { type Item = T; } -impl Index for NSMutableArray -where - T: INSObject, - O: Ownership, -{ +impl Index for NSMutableArray { type Output = T; fn index(&self, index: usize) -> &T { - self.object_at(index) + self.get(index).unwrap() } } -pub type NSMutableSharedArray = NSMutableArray; - #[cfg(test)] mod tests { use alloc::vec; use alloc::vec::Vec; + use objc2::msg_send; + use objc2::rc::{autoreleasepool, Id, Owned, Shared}; + use super::{INSArray, INSMutableArray, NSArray, NSMutableArray}; use crate::{INSObject, INSString, NSObject, NSString}; - use objc2::rc::{Id, Owned}; - fn sample_array(len: usize) -> Id, Owned> { + fn sample_array(len: usize) -> Id, Owned> { let mut vec = Vec::with_capacity(len); for _ in 0..len { vec.push(NSObject::new()); @@ -429,36 +370,62 @@ mod tests { NSArray::from_vec(vec) } + fn retain_count(obj: &T) -> usize { + unsafe { msg_send![obj, retainCount] } + } + #[test] - fn test_count() { - let empty_array = NSArray::::new(); - assert!(empty_array.count() == 0); + fn test_len() { + let empty_array = NSArray::::new(); + assert_eq!(empty_array.len(), 0); let array = sample_array(4); - assert!(array.count() == 4); + assert_eq!(array.len(), 4); } #[test] - fn test_object_at() { + fn test_get() { let array = sample_array(4); - assert!(array.object_at(0) != array.object_at(3)); - assert!(array.first_object().unwrap() == array.object_at(0)); - assert!(array.last_object().unwrap() == array.object_at(3)); + assert_ne!(array.get(0), array.get(3)); + assert_eq!(array.first(), array.get(0)); + assert_eq!(array.last(), array.get(3)); + + 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(); + + assert_eq!(retain_count(&*obj), 1); + + let array = NSArray::from_slice(&[obj.clone()]); + + assert_eq!(retain_count(&*obj), 2); + + autoreleasepool(|_pool| { + let obj2 = array.first().unwrap(); + assert_eq!(retain_count(obj2), 2); + }); + + assert_eq!(retain_count(&*obj), 2); + + drop(array); - let empty_array: Id, Owned> = INSObject::new(); - assert!(empty_array.first_object().is_none()); - assert!(empty_array.last_object().is_none()); + assert_eq!(retain_count(&*obj), 1); } #[test] - fn test_object_enumerator() { + fn test_iter() { let array = sample_array(4); - assert!(array.object_enumerator().count() == 4); + assert!(array.iter().count() == 4); assert!(array - .object_enumerator() + .iter() .enumerate() - .all(|(i, obj)| obj == array.object_at(i))); + .all(|(i, obj)| Some(obj) == array.get(i))); } #[test] @@ -466,15 +433,15 @@ mod tests { let array = sample_array(4); let middle_objs = array.objects_in_range(1..3); - assert!(middle_objs.len() == 2); - assert!(middle_objs[0] == array.object_at(1)); - assert!(middle_objs[1] == array.object_at(2)); + assert_eq!(middle_objs.len(), 2); + assert_eq!(middle_objs[0], array.get(1).unwrap()); + assert_eq!(middle_objs[1], array.get(2).unwrap()); let empty_objs = array.objects_in_range(1..1); assert!(empty_objs.is_empty()); let all_objs = array.objects_in_range(0..4); - assert!(all_objs.len() == 4); + assert_eq!(all_objs.len(), 4); } #[test] @@ -482,49 +449,49 @@ mod tests { let array = sample_array(4); let vec = INSArray::into_vec(array); - assert!(vec.len() == 4); + assert_eq!(vec.len(), 4); } #[test] - fn test_add_object() { + fn test_adding() { let mut array = NSMutableArray::new(); let obj = NSObject::new(); - array.add_object(obj); + array.push(obj); - assert!(array.count() == 1); - assert!(array.object_at(0) == array.object_at(0)); + assert_eq!(array.len(), 1); + assert_eq!(array.get(0), array.get(0)); let obj = NSObject::new(); - array.insert_object_at(0, obj); - assert!(array.count() == 2); + array.insert(0, obj); + assert_eq!(array.len(), 2); } #[test] - fn test_replace_object() { + fn test_replace() { let mut array = NSMutableArray::new(); let obj = NSObject::new(); - array.add_object(obj); + array.push(obj); let obj = NSObject::new(); - let old_obj = array.replace_object_at(0, obj); - assert!(&*old_obj != array.object_at(0)); + let old_obj = array.replace(0, obj); + assert_ne!(&*old_obj, array.get(0).unwrap()); } #[test] - fn test_remove_object() { + fn test_remove() { let mut array = NSMutableArray::new(); for _ in 0..4 { - array.add_object(NSObject::new()); + array.push(NSObject::new()); } - array.remove_object_at(1); - assert!(array.count() == 3); + let _ = array.remove(1); + assert_eq!(array.len(), 3); - array.remove_last_object(); - assert!(array.count() == 2); + let _ = array.pop(); + assert_eq!(array.len(), 2); - array.remove_all_objects(); - assert!(array.count() == 0); + array.clear(); + assert_eq!(array.len(), 0); } #[test] @@ -532,8 +499,10 @@ mod tests { let strings = vec![NSString::from_str("hello"), NSString::from_str("hi")]; let mut strings = NSMutableArray::from_vec(strings); - strings.sort_by(|s1, s2| s1.as_str().len().cmp(&s2.as_str().len())); - assert!(strings[0].as_str() == "hi"); - assert!(strings[1].as_str() == "hello"); + autoreleasepool(|pool| { + strings.sort_by(|s1, s2| s1.as_str(pool).len().cmp(&s2.as_str(pool).len())); + assert_eq!(strings[0].as_str(pool), "hi"); + assert_eq!(strings[1].as_str(pool), "hello"); + }); } } diff --git a/objc2_foundation/src/comparison_result.rs b/objc2_foundation/src/comparison_result.rs new file mode 100644 index 000000000..102a0e739 --- /dev/null +++ b/objc2_foundation/src/comparison_result.rs @@ -0,0 +1,45 @@ +use core::cmp::Ordering; + +use objc2::{Encode, Encoding, RefEncode}; + +#[repr(isize)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum NSComparisonResult { + Ascending = -1, + Same = 0, + Descending = 1, +} + +impl Default for NSComparisonResult { + fn default() -> Self { + Self::Same + } +} + +unsafe impl Encode for NSComparisonResult { + const ENCODING: Encoding<'static> = isize::ENCODING; +} + +unsafe impl RefEncode for NSComparisonResult { + const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING); +} + +impl From for NSComparisonResult { + fn from(order: Ordering) -> Self { + match order { + Ordering::Less => Self::Ascending, + Ordering::Equal => Self::Same, + Ordering::Greater => Self::Descending, + } + } +} + +impl From for Ordering { + fn from(comparison_result: NSComparisonResult) -> Self { + match comparison_result { + NSComparisonResult::Ascending => Self::Less, + NSComparisonResult::Same => Self::Equal, + NSComparisonResult::Descending => Self::Greater, + } + } +} diff --git a/objc2_foundation/src/copying.rs b/objc2_foundation/src/copying.rs new file mode 100644 index 000000000..c6a453707 --- /dev/null +++ b/objc2_foundation/src/copying.rs @@ -0,0 +1,39 @@ +use core::ptr::NonNull; + +use objc2::msg_send; +use objc2::rc::{Id, Owned}; + +use super::INSObject; + +pub unsafe trait INSCopying: INSObject { + /// This can be an [`Owned`] [`INSObject`] if and only if `copy` creates a + /// new instance, see the following example: + /// + /// ```ignore + /// let x: Id = MyObject::new(); + /// // This is valid only if `y` is a new instance. Otherwise `x` and `y` + /// // would be able to create aliasing mutable references! + /// let y: Id = x.copy(); + /// ``` + type Output: INSObject; + + fn copy(&self) -> Id::Ownership> { + unsafe { + let obj: *mut Self::Output = msg_send![self, copy]; + Id::new(NonNull::new_unchecked(obj)) + } + } +} + +pub unsafe trait INSMutableCopying: INSObject { + /// An [`Owned`] [`INSObject`] is required to be able to return an owned + /// [`Id`]. + type Output: INSObject; + + fn mutable_copy(&self) -> Id { + unsafe { + let obj: *mut Self::Output = msg_send![self, mutableCopy]; + Id::new(NonNull::new_unchecked(obj)) + } + } +} diff --git a/objc2_foundation/src/data.rs b/objc2_foundation/src/data.rs index e9d417b3b..d2759d79c 100644 --- a/objc2_foundation/src/data.rs +++ b/objc2_foundation/src/data.rs @@ -1,14 +1,16 @@ #[cfg(feature = "block")] use alloc::vec::Vec; -use core::ops::Range; +use core::ops::{Deref, DerefMut, 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}; +use objc2::rc::{Id, Owned, Shared}; + +pub unsafe trait INSData: INSObject { + unsafe_def_fn!(fn new); -pub trait INSData: INSObject { fn len(&self) -> usize { unsafe { msg_send![self, length] } } @@ -27,7 +29,7 @@ pub trait INSData: INSObject { } } - 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 { @@ -42,10 +44,13 @@ pub trait INSData: INSObject { } #[cfg(feature = "block")] - fn from_vec(bytes: Vec) -> Id { + fn from_vec(bytes: Vec) -> Id { + use core::mem::ManuallyDrop; + use objc2_block::{Block, ConcreteBlock}; let capacity = bytes.capacity(); + let dealloc = ConcreteBlock::new(move |bytes: *mut c_void, len: usize| unsafe { // Recreate the Vec and let it drop let _ = Vec::from_raw_parts(bytes as *mut u8, len, capacity); @@ -53,9 +58,6 @@ pub trait INSData: INSObject { let dealloc = dealloc.copy(); let dealloc: &Block<(*mut c_void, usize), ()> = &dealloc; - let mut bytes = bytes; - let bytes_ptr = bytes.as_mut_ptr() as *mut c_void; - // GNUStep's NSData `initWithBytesNoCopy:length:deallocator:` has a // bug; it forgets to assign the input buffer and length to the // instance before it swizzles to NSDataWithDeallocatorBlock. @@ -72,71 +74,73 @@ pub trait INSData: INSObject { }; #[cfg(not(gnustep))] let cls = Self::class(); + + let mut bytes = ManuallyDrop::new(bytes); + unsafe { let obj: *mut Self = msg_send![cls, alloc]; let obj: *mut Self = msg_send![ obj, - initWithBytesNoCopy: bytes_ptr, + initWithBytesNoCopy: bytes.as_mut_ptr() as *mut c_void, length: bytes.len(), deallocator: dealloc, ]; - core::mem::forget(bytes); Id::new(NonNull::new_unchecked(obj)) } } } -object_struct!(NSData); +object_struct!(unsafe NSData, Shared); -impl INSData for NSData {} +unsafe impl INSData for NSData {} -impl INSCopying for NSData { +unsafe impl INSCopying for NSData { type Output = NSData; } -impl INSMutableCopying for NSData { +unsafe impl INSMutableCopying for NSData { type Output = NSMutableData; } -pub trait INSMutableData: INSData { +impl Deref for NSData { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + self.bytes() + } +} + +pub unsafe trait INSMutableData: INSData { fn bytes_mut(&mut self) -> &mut [u8] { let ptr: *mut c_void = unsafe { msg_send![self, mutableBytes] }; // The bytes pointer may be null for length zero - let (ptr, len) = if ptr.is_null() { - (0x1 as *mut u8, 0) + if ptr.is_null() { + &mut [] } else { - (ptr as *mut u8, self.len()) - }; - unsafe { slice::from_raw_parts_mut(ptr, len) } + unsafe { slice::from_raw_parts_mut(ptr as *mut u8, self.len()) } + } } + /// Expands with zeroes, or truncates the buffer. fn set_len(&mut self, len: usize) { - unsafe { - let _: () = msg_send![self, setLength: len]; - } + unsafe { msg_send![self, setLength: len] } } fn append(&mut self, bytes: &[u8]) { let bytes_ptr = bytes.as_ptr() as *const c_void; - unsafe { - let _: () = msg_send![ - self, - appendBytes: bytes_ptr, - length:bytes.len(), - ]; - } + unsafe { msg_send![self, appendBytes: bytes_ptr, length: bytes.len()] } } fn replace_range(&mut self, range: Range, bytes: &[u8]) { - let range = NSRange::from_range(range); - let bytes_ptr = bytes.as_ptr() as *const c_void; + let range = NSRange::from(range); + let ptr = bytes.as_ptr() as *const c_void; unsafe { - let _: () = msg_send![ + msg_send![ self, - replaceBytesInRange:range, - withBytes:bytes_ptr, - length:bytes.len(), - ]; + replaceBytesInRange: range, + withBytes: ptr, + length: bytes.len(), + ] } } @@ -146,24 +150,37 @@ pub trait INSMutableData: INSData { } } -object_struct!(NSMutableData); +object_struct!(unsafe NSMutableData, Owned); -impl INSData for NSMutableData {} +unsafe impl INSData for NSMutableData {} -impl INSMutableData for NSMutableData {} +unsafe impl INSMutableData for NSMutableData {} -impl INSCopying for NSMutableData { +unsafe impl INSCopying for NSMutableData { type Output = NSData; } -impl INSMutableCopying for NSMutableData { +unsafe impl INSMutableCopying for NSMutableData { type Output = NSMutableData; } +impl Deref for NSMutableData { + type Target = [u8]; + + fn deref(&self) -> &[u8] { + self.bytes() + } +} + +impl DerefMut for NSMutableData { + fn deref_mut(&mut self) -> &mut [u8] { + self.bytes_mut() + } +} + #[cfg(test)] mod tests { use super::{INSData, INSMutableData, NSData, NSMutableData}; - use crate::INSObject; #[cfg(feature = "block")] use alloc::vec; @@ -171,8 +188,8 @@ mod tests { fn test_bytes() { let bytes = [3, 7, 16, 52, 112, 19]; let data = NSData::with_bytes(&bytes); - assert!(data.len() == bytes.len()); - assert!(data.bytes() == bytes); + assert_eq!(data.len(), bytes.len()); + assert_eq!(data.bytes(), bytes); } #[test] @@ -185,43 +202,43 @@ mod tests { fn test_bytes_mut() { let mut data = NSMutableData::with_bytes(&[7, 16]); data.bytes_mut()[0] = 3; - assert!(data.bytes() == [3, 16]); + assert_eq!(data.bytes(), [3, 16]); } #[test] fn test_set_len() { let mut data = NSMutableData::with_bytes(&[7, 16]); data.set_len(4); - assert!(data.len() == 4); - assert!(data.bytes() == [7, 16, 0, 0]); + assert_eq!(data.len(), 4); + assert_eq!(data.bytes(), [7, 16, 0, 0]); data.set_len(1); - assert!(data.len() == 1); - assert!(data.bytes() == [7]); + assert_eq!(data.len(), 1); + assert_eq!(data.bytes(), [7]); } #[test] fn test_append() { let mut data = NSMutableData::with_bytes(&[7, 16]); data.append(&[3, 52]); - assert!(data.len() == 4); - assert!(data.bytes() == [7, 16, 3, 52]); + assert_eq!(data.len(), 4); + assert_eq!(data.bytes(), [7, 16, 3, 52]); } #[test] fn test_replace() { let mut data = NSMutableData::with_bytes(&[7, 16]); data.replace_range(0..0, &[3]); - assert!(data.bytes() == [3, 7, 16]); + assert_eq!(data.bytes(), [3, 7, 16]); data.replace_range(1..2, &[52, 13]); - assert!(data.bytes() == [3, 52, 13, 16]); + assert_eq!(data.bytes(), [3, 52, 13, 16]); data.replace_range(2..4, &[6]); - assert!(data.bytes() == [3, 52, 6]); + assert_eq!(data.bytes(), [3, 52, 6]); data.set_bytes(&[8, 17]); - assert!(data.bytes() == [8, 17]); + assert_eq!(data.bytes(), [8, 17]); } #[cfg(feature = "block")] diff --git a/objc2_foundation/src/dictionary.rs b/objc2_foundation/src/dictionary.rs index d119ba8a4..e940aafb0 100644 --- a/objc2_foundation/src/dictionary.rs +++ b/objc2_foundation/src/dictionary.rs @@ -8,9 +8,9 @@ use objc2::rc::{Id, Owned, Ownership, Shared, SliceId}; use objc2::runtime::Class; use objc2::{class, msg_send}; -use super::{INSCopying, INSFastEnumeration, INSObject, NSArray, NSEnumerator, NSSharedArray}; +use super::{INSCopying, INSFastEnumeration, INSObject, NSArray, NSEnumerator}; -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, @@ -27,28 +27,26 @@ where Id::new(NonNull::new_unchecked(obj)) } -pub trait INSDictionary: INSObject { +pub unsafe trait INSDictionary: INSObject { type Key: INSObject; type Value: INSObject; - type Own: Ownership; + type ValueOwnership: Ownership; - fn count(&self) -> usize { + unsafe_def_fn!(fn new); + + #[doc(alias = "count")] + fn len(&self) -> usize { unsafe { msg_send![self, count] } } - fn object_for(&self, key: &Self::Key) -> Option<&Self::Value> { - unsafe { - let obj: *mut Self::Value = msg_send![self, objectForKey: key]; - if obj.is_null() { - None - } else { - Some(&*obj) - } - } + #[doc(alias = "objectForKey:")] + fn get(&self, key: &Self::Key) -> Option<&Self::Value> { + unsafe { msg_send![self, objectForKey: key] } } + #[doc(alias = "getObjects:andKeys:")] fn keys(&self) -> Vec<&Self::Key> { - let len = self.count(); + let len = self.len(); let mut keys = Vec::with_capacity(len); unsafe { let _: () = msg_send![ @@ -61,8 +59,9 @@ pub trait INSDictionary: INSObject { keys } + #[doc(alias = "getObjects:andKeys:")] fn values(&self) -> Vec<&Self::Value> { - let len = self.count(); + let len = self.len(); let mut vals = Vec::with_capacity(len); unsafe { let _: () = msg_send![ @@ -75,8 +74,9 @@ pub trait INSDictionary: INSObject { vals } + #[doc(alias = "getObjects:andKeys:")] fn keys_and_objects(&self) -> (Vec<&Self::Key>, Vec<&Self::Value>) { - let len = self.count(); + let len = self.len(); let mut keys = Vec::with_capacity(len); let mut objs = Vec::with_capacity(len); unsafe { @@ -91,21 +91,23 @@ pub trait INSDictionary: INSObject { (keys, objs) } - fn key_enumerator(&self) -> NSEnumerator { + #[doc(alias = "keyEnumerator")] + fn iter_keys<'a>(&'a self) -> NSEnumerator<'a, Self::Key> { unsafe { let result = msg_send![self, keyEnumerator]; NSEnumerator::from_ptr(result) } } - fn object_enumerator(&self) -> NSEnumerator { + #[doc(alias = "objectEnumerator")] + fn iter_values<'a>(&'a self) -> NSEnumerator<'a, Self::Value> { unsafe { let result = msg_send![self, objectEnumerator]; NSEnumerator::from_ptr(result) } } - fn keys_array(&self) -> Id, Owned> { + fn keys_array(&self) -> Id, Shared> { unsafe { let keys = msg_send![self, allKeys]; Id::retain(NonNull::new_unchecked(keys)) @@ -114,15 +116,17 @@ pub trait INSDictionary: INSObject { fn from_keys_and_objects( keys: &[&T], - vals: Vec>, - ) -> Id + vals: Vec>, + ) -> Id where T: INSCopying, { unsafe { from_refs(keys, &vals.as_slice_ref()) } } - fn into_values_array(dict: Id) -> Id, Owned> { + fn into_values_array( + dict: Id, + ) -> Id, Shared> { unsafe { let vals = msg_send![dict, allValues]; Id::retain(NonNull::new_unchecked(vals)) @@ -135,77 +139,63 @@ pub struct NSDictionary { obj: PhantomData>, } -object_impl!(NSDictionary); +object_impl!(unsafe NSDictionary); + +unsafe impl INSObject for NSDictionary { + type Ownership = Shared; -impl INSObject for NSDictionary -where - K: INSObject, - V: INSObject, -{ fn class() -> &'static Class { class!(NSDictionary) } } -impl INSDictionary for NSDictionary -where - K: INSObject, - V: INSObject, -{ +unsafe impl INSDictionary for NSDictionary { type Key = K; type Value = V; - type Own = Owned; + type ValueOwnership = Owned; } -impl INSFastEnumeration for NSDictionary -where - K: INSObject, - V: INSObject, -{ +unsafe impl INSFastEnumeration for NSDictionary { type Item = K; } -impl<'a, K, V> Index<&'a K> for NSDictionary -where - K: INSObject, - V: INSObject, -{ +impl<'a, K: INSObject, V: INSObject> Index<&'a K> for NSDictionary { type Output = V; fn index(&self, index: &K) -> &V { - self.object_for(index).unwrap() + self.get(index).unwrap() } } #[cfg(test)] mod tests { use alloc::vec; - use objc2::rc::{Id, Owned}; + use objc2::rc::{autoreleasepool, Id, Shared}; use super::{INSDictionary, NSDictionary}; - use crate::{INSArray, INSObject, INSString, NSObject, NSString}; + use crate::{INSArray, INSString, NSObject, NSString}; - fn sample_dict(key: &str) -> Id, Owned> { + fn sample_dict(key: &str) -> Id, Shared> { let string = NSString::from_str(key); let obj = NSObject::new(); NSDictionary::from_keys_and_objects(&[&*string], vec![obj]) } #[test] - fn test_count() { + fn test_len() { let dict = sample_dict("abcd"); - assert!(dict.count() == 1); + assert_eq!(dict.len(), 1); } #[test] - fn test_object_for() { + fn test_get() { let dict = sample_dict("abcd"); let string = NSString::from_str("abcd"); - assert!(dict.object_for(&string).is_some()); + assert!(dict.get(&string).is_some()); let string = NSString::from_str("abcde"); - assert!(dict.object_for(&string).is_none()); + assert!(dict.get(&string).is_none()); } #[test] @@ -213,8 +203,10 @@ mod tests { let dict = sample_dict("abcd"); let keys = dict.keys(); - assert!(keys.len() == 1); - assert!(keys[0].as_str() == "abcd"); + assert_eq!(keys.len(), 1); + autoreleasepool(|pool| { + assert_eq!(keys[0].as_str(pool), "abcd"); + }); } #[test] @@ -222,7 +214,7 @@ mod tests { let dict = sample_dict("abcd"); let vals = dict.values(); - assert!(vals.len() == 1); + assert_eq!(vals.len(), 1); } #[test] @@ -230,23 +222,27 @@ mod tests { let dict = sample_dict("abcd"); let (keys, objs) = dict.keys_and_objects(); - assert!(keys.len() == 1); - assert!(objs.len() == 1); - assert!(keys[0].as_str() == "abcd"); - assert!(objs[0] == dict.object_for(keys[0]).unwrap()); + assert_eq!(keys.len(), 1); + assert_eq!(objs.len(), 1); + autoreleasepool(|pool| { + assert_eq!(keys[0].as_str(pool), "abcd"); + }); + assert_eq!(objs[0], dict.get(keys[0]).unwrap()); } #[test] - fn test_key_enumerator() { + fn test_iter_keys() { let dict = sample_dict("abcd"); - assert!(dict.key_enumerator().count() == 1); - assert!(dict.key_enumerator().next().unwrap().as_str() == "abcd"); + assert_eq!(dict.iter_keys().count(), 1); + autoreleasepool(|pool| { + assert_eq!(dict.iter_keys().next().unwrap().as_str(pool), "abcd"); + }); } #[test] - fn test_object_enumerator() { + fn test_iter_values() { let dict = sample_dict("abcd"); - assert!(dict.object_enumerator().count() == 1); + assert_eq!(dict.iter_values().count(), 1); } #[test] @@ -254,10 +250,12 @@ mod tests { let dict = sample_dict("abcd"); let keys = dict.keys_array(); - assert!(keys.count() == 1); - assert!(keys.object_at(0).as_str() == "abcd"); + assert_eq!(keys.len(), 1); + autoreleasepool(|pool| { + assert_eq!(keys[0].as_str(pool), "abcd"); + }); - let objs = INSDictionary::into_values_array(dict); - assert!(objs.count() == 1); + // let objs = INSDictionary::into_values_array(dict); + // assert_eq!(objs.len(), 1); } } diff --git a/objc2_foundation/src/enumerator.rs b/objc2_foundation/src/enumerator.rs index b1d1fedfe..150c5772a 100644 --- a/objc2_foundation/src/enumerator.rs +++ b/objc2_foundation/src/enumerator.rs @@ -11,54 +11,38 @@ use objc2::{msg_send, Encode, Encoding, RefEncode}; use super::INSObject; -pub struct NSEnumerator<'a, T> -where - T: INSObject, -{ +pub struct NSEnumerator<'a, T: INSObject> { id: Id, item: PhantomData<&'a T>, } -impl<'a, T> NSEnumerator<'a, T> -where - T: INSObject, -{ +impl<'a, T: INSObject> NSEnumerator<'a, T> { /// TODO /// /// # Safety /// /// The object pointer must be a valid `NSEnumerator` with `Owned` /// ownership. - pub unsafe fn from_ptr(ptr: *mut Object) -> NSEnumerator<'a, T> { - NSEnumerator { + pub unsafe fn from_ptr(ptr: *mut Object) -> Self { + Self { id: Id::retain(NonNull::new(ptr).unwrap()), item: PhantomData, } } } -impl<'a, T> Iterator for NSEnumerator<'a, T> -where - T: INSObject, -{ +impl<'a, T: INSObject> Iterator for NSEnumerator<'a, T> { type Item = &'a T; fn next(&mut self) -> Option<&'a T> { - unsafe { - let obj: *mut T = msg_send![self.id, nextObject]; - if obj.is_null() { - None - } else { - Some(&*obj) - } - } + unsafe { msg_send![self.id, nextObject] } } } -pub trait INSFastEnumeration: INSObject { +pub unsafe trait INSFastEnumeration: INSObject { type Item: INSObject; - fn enumerator(&self) -> NSFastEnumerator { + fn enumerator<'a>(&'a self) -> NSFastEnumerator<'a, Self> { NSFastEnumerator::new(self) } } @@ -123,8 +107,8 @@ pub struct NSFastEnumerator<'a, C: 'a + INSFastEnumeration> { } impl<'a, C: INSFastEnumeration> NSFastEnumerator<'a, C> { - fn new(object: &C) -> NSFastEnumerator { - NSFastEnumerator { + fn new(object: &'a C) -> Self { + Self { object, ptr: ptr::null(), @@ -189,29 +173,25 @@ mod tests { #[test] fn test_enumerator() { - let vec = (0u32..4).map(NSValue::from_value).collect(); + let vec = (0u32..4).map(NSValue::new).collect(); let array = NSArray::from_vec(vec); - let enumerator = array.object_enumerator(); + let enumerator = array.iter(); assert!(enumerator.count() == 4); - let enumerator = array.object_enumerator(); - assert!(enumerator - .enumerate() - .all(|(i, obj)| obj.value() == i as u32)); + let enumerator = array.iter(); + assert!(enumerator.enumerate().all(|(i, obj)| obj.get() == i as u32)); } #[test] fn test_fast_enumerator() { - let vec = (0u32..4).map(NSValue::from_value).collect(); + let vec = (0u32..4).map(NSValue::new).collect(); let array = NSArray::from_vec(vec); let enumerator = array.enumerator(); assert!(enumerator.count() == 4); let enumerator = array.enumerator(); - assert!(enumerator - .enumerate() - .all(|(i, obj)| obj.value() == i as u32)); + assert!(enumerator.enumerate().all(|(i, obj)| obj.get() == i as u32)); } } diff --git a/objc2_foundation/src/lib.rs b/objc2_foundation/src/lib.rs index 5bb65a091..674160efe 100644 --- a/objc2_foundation/src/lib.rs +++ b/objc2_foundation/src/lib.rs @@ -10,15 +10,15 @@ extern crate std; #[doc = include_str!("../README.md")] extern "C" {} -pub use self::array::{ - INSArray, INSMutableArray, NSArray, NSComparisonResult, NSMutableArray, NSMutableSharedArray, - NSRange, NSSharedArray, -}; +pub use self::array::{INSArray, INSMutableArray, NSArray, NSMutableArray}; +pub use self::comparison_result::NSComparisonResult; +pub use self::copying::{INSCopying, INSMutableCopying}; pub use self::data::{INSData, INSMutableData, NSData, NSMutableData}; pub use self::dictionary::{INSDictionary, NSDictionary}; pub use self::enumerator::{INSFastEnumeration, NSEnumerator, NSFastEnumerator}; pub use self::object::{INSObject, NSObject}; -pub use self::string::{INSCopying, INSMutableCopying, INSString, NSString}; +pub use self::range::NSRange; +pub use self::string::{INSString, NSString}; pub use self::value::{INSValue, NSValue}; #[cfg(target_vendor = "apple")] @@ -33,9 +33,12 @@ extern "C" {} mod macros; mod array; +mod comparison_result; +mod copying; mod data; mod dictionary; mod enumerator; mod object; +mod range; mod string; mod value; diff --git a/objc2_foundation/src/macros.rs b/objc2_foundation/src/macros.rs index 8f41ee6c2..d7fbf0f3e 100644 --- a/objc2_foundation/src/macros.rs +++ b/objc2_foundation/src/macros.rs @@ -1,6 +1,13 @@ -#[macro_export] +/// TODO +/// +/// # Safety +/// +/// The given name must be a valid Objective-C class that inherits NSObject +/// and it's instances must have the raw encoding `Encoding::Object` (an +/// example: `NSAutoreleasePool` does not have this). Finally the ownership +/// must be correct for this class. macro_rules! object_struct { - ($name:ident) => { + (unsafe $name:ident, $ownership:ty) => { // TODO: `extern type` #[repr(C)] pub struct $name { @@ -13,7 +20,9 @@ macro_rules! object_struct { const ENCODING_REF: ::objc2::Encoding<'static> = ::objc2::Encoding::Object; } - impl $crate::INSObject for $name { + unsafe impl $crate::INSObject for $name { + type Ownership = $ownership; + fn class() -> &'static ::objc2::runtime::Class { ::objc2::class!($name) } @@ -29,10 +38,7 @@ macro_rules! object_struct { impl ::core::cmp::Eq for $name {} impl ::core::hash::Hash for $name { - fn hash(&self, state: &mut H) - where - H: ::core::hash::Hasher, - { + fn hash(&self, state: &mut H) { use $crate::INSObject; self.hash_code().hash(state); } @@ -41,20 +47,27 @@ macro_rules! object_struct { impl ::core::fmt::Debug for $name { fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { use $crate::{INSObject, INSString}; - ::core::fmt::Debug::fmt(self.description().as_str(), f) + ::objc2::rc::autoreleasepool(|pool| { + ::core::fmt::Debug::fmt(self.description().as_str(pool), f) + }) } } }; } +/// TODO +/// +/// # Safety +/// +/// The given type must be valid as an Objective-C object. TODO: More. macro_rules! object_impl { - ($name:ident) => ( - object_impl!($name,); + (unsafe $name:ident) => ( + object_impl!(unsafe $name,); ); - ($name:ident<$($t:ident$(: $b:ident)?),+>) => ( - object_impl!($name, $($t$(: $b)?),+); + (unsafe $name:ident<$($t:ident$(: $b:ident)?),+>) => ( + object_impl!(unsafe $name, $($t$(: $b)?),+); ); - ($name:ident, $($t:ident$(: $b:ident)?),*) => ( + (unsafe $name:ident, $($t:ident$(: $b:ident)?),*) => ( unsafe impl<$($t$(:($b))?),*> ::objc2::Message for $name<$($t),*> { } unsafe impl<$($t$(: $b)?),*> ::objc2::RefEncode for $name<$($t),*> { @@ -62,3 +75,12 @@ macro_rules! object_impl { } ); } + +macro_rules! unsafe_def_fn { + ($v:vis fn new) => { + $v fn new() -> Id::Ownership> { + let cls = ::class(); + unsafe { Id::new(NonNull::new_unchecked(msg_send![cls, new])) } + } + }; +} diff --git a/objc2_foundation/src/object.rs b/objc2_foundation/src/object.rs index 23567b420..d13fda438 100644 --- a/objc2_foundation/src/object.rs +++ b/objc2_foundation/src/object.rs @@ -1,8 +1,7 @@ -use core::any::Any; use core::ptr::NonNull; use objc2::msg_send; -use objc2::rc::{Id, Owned, Shared}; +use objc2::rc::{Id, Owned, Ownership, Shared}; use objc2::runtime::{Bool, Class}; use objc2::Message; @@ -14,17 +13,24 @@ treated as Sized. However, rust won't allow casting a dynamically-sized type pointer to an Object pointer, because dynamically-sized types can have fat pointers (two words) instead of real pointers. */ -pub trait INSObject: Any + Sized + Message { +pub unsafe trait INSObject: Sized + Message { + /// Indicates whether the type is mutable or immutable. + /// + /// [`Shared`] means that only a shared [`Id`] can ever be held to this + /// object. This is important for immutable types like `NSString`, because + /// sending the `copy` message (and others) does not create a new + /// instance, but instead just retains the instance. + /// + /// Most objects are mutable and hence can return [`Owned`] [`Id`]s. + type Ownership: Ownership; + fn class() -> &'static Class; fn hash_code(&self) -> usize { unsafe { msg_send![self, hash] } } - fn is_equal(&self, other: &T) -> bool - where - T: INSObject, - { + fn is_equal(&self, other: &T) -> bool { let result: Bool = unsafe { msg_send![self, isEqual: other] }; result.is_true() } @@ -41,20 +47,20 @@ pub trait INSObject: Any + Sized + Message { let result: Bool = unsafe { msg_send![self, isKindOfClass: cls] }; result.is_true() } - - fn new() -> Id { - let cls = Self::class(); - unsafe { Id::new(msg_send![cls, new]) } - } } -object_struct!(NSObject); +object_struct!(unsafe NSObject, Owned); + +impl NSObject { + unsafe_def_fn!(pub fn new); +} #[cfg(test)] mod tests { use super::{INSObject, NSObject}; use crate::{INSString, NSString}; use alloc::format; + use objc2::rc::autoreleasepool; #[test] fn test_is_equal() { @@ -70,7 +76,7 @@ mod tests { #[test] fn test_hash_code() { let obj = NSObject::new(); - assert!(obj.hash_code() == obj.hash_code()); + assert_eq!(obj.hash_code(), obj.hash_code()); } #[test] @@ -78,7 +84,9 @@ mod tests { let obj = NSObject::new(); let description = obj.description(); let expected = format!("", &*obj); - assert_eq!(description.as_str(), &*expected); + autoreleasepool(|pool| { + assert_eq!(description.as_str(pool), &*expected); + }); let expected = format!("\"\"", &*obj); assert_eq!(format!("{:?}", obj), expected); diff --git a/objc2_foundation/src/range.rs b/objc2_foundation/src/range.rs new file mode 100644 index 000000000..42c88e010 --- /dev/null +++ b/objc2_foundation/src/range.rs @@ -0,0 +1,58 @@ +use core::ops::Range; + +use objc2::{Encode, Encoding, RefEncode}; + +#[repr(C)] +// PartialEq is same as NSEqualRanges +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub struct NSRange { + pub location: usize, + pub length: usize, +} + +// impl NSRange { +// pub fn contains(&self, index: usize) -> bool { +// // Same as NSLocationInRange +// >::contains(self, &index) +// } +// } + +// impl RangeBounds for NSRange { +// fn start_bound(&self) -> Bound<&usize> { +// Bound::Included(&self.location) +// } +// fn end_bound(&self) -> Bound<&usize> { +// Bound::Excluded(&(self.location + self.length)) +// } +// } + +impl From> for NSRange { + fn from(range: Range) -> Self { + let length = range + .end + .checked_sub(range.start) + .expect("Range end < start"); + Self { + location: range.start, + length, + } + } +} + +impl From for Range { + fn from(nsrange: NSRange) -> Self { + Self { + start: nsrange.location, + end: nsrange.location + nsrange.length, + } + } +} + +unsafe impl Encode for NSRange { + const ENCODING: Encoding<'static> = + Encoding::Struct("_NSRange", &[usize::ENCODING, usize::ENCODING]); +} + +unsafe impl RefEncode for NSRange { + const ENCODING_REF: Encoding<'static> = Encoding::Pointer(&Self::ENCODING); +} diff --git a/objc2_foundation/src/string.rs b/objc2_foundation/src/string.rs index c71b1f4bb..b7502bb22 100644 --- a/objc2_foundation/src/string.rs +++ b/objc2_foundation/src/string.rs @@ -6,38 +6,19 @@ use core::str; use std::os::raw::c_char; use objc2::msg_send; -use objc2::rc::{Id, Owned, Shared}; +use objc2::rc::{autoreleasepool, AutoreleasePool}; +use objc2::rc::{Id, Shared}; -use super::INSObject; - -pub trait INSCopying: INSObject { - type Output: INSObject; - - fn copy(&self) -> Id { - unsafe { - let obj: *mut Self::Output = msg_send![self, copy]; - Id::new(NonNull::new_unchecked(obj)) - } - } -} - -pub trait INSMutableCopying: INSObject { - type Output: INSObject; - - fn mutable_copy(&self) -> Id { - unsafe { - let obj: *mut Self::Output = msg_send![self, mutableCopy]; - Id::new(NonNull::new_unchecked(obj)) - } - } -} +use super::{INSCopying, INSObject}; #[cfg(target_vendor = "apple")] const UTF8_ENCODING: usize = 4; #[cfg(not(target_vendor = "apple"))] const UTF8_ENCODING: i32 = 4; -pub trait INSString: INSObject { +pub unsafe trait INSString: INSObject { + unsafe_def_fn!(fn new); + fn len(&self) -> usize { unsafe { msg_send![self, lengthOfBytesUsingEncoding: UTF8_ENCODING] } } @@ -46,19 +27,61 @@ pub trait INSString: INSObject { self.len() == 0 } - fn as_str(&self) -> &str { - let bytes = unsafe { - let bytes: *const c_char = msg_send![self, UTF8String]; - bytes as *const u8 - }; + /// TODO + /// + /// ```compile_fail + /// # use objc2::rc::autoreleasepool; + /// # use objc2_foundation::{INSObject, INSString, NSString}; + /// autoreleasepool(|pool| { + /// let ns_string = NSString::new(); + /// let s = ns_string.as_str(pool); + /// drop(ns_string); + /// println!("{}", s); + /// }); + /// ``` + /// + /// ```compile_fail + /// # use objc2::rc::autoreleasepool; + /// # use objc2_foundation::{INSObject, INSString, NSString}; + /// let ns_string = NSString::new(); + /// let s = autoreleasepool(|pool| ns_string.as_str(pool)); + /// ``` + fn as_str<'r, 's: 'r, 'p: 'r>(&'s self, pool: &'p AutoreleasePool) -> &'r str { + // This is necessary until `auto` types stabilizes. + pool.__verify_is_inner(); + + // The documentation on `UTF8String` is a bit sparse, but with + // educated guesses and testing I've determined that NSString stores + // a pointer to the string data, sometimes with an UTF-8 encoding, + // (usual for ascii data), sometimes in other encodings (UTF-16?). + // + // `UTF8String` then checks the internal encoding: + // - If the data is UTF-8 encoded, it returns the internal pointer. + // - If the data is in another encoding, it creates a new allocation, + // writes the UTF-8 representation of the string into it, + // autoreleases the allocation and returns a pointer to it. + // + // So the lifetime of the returned pointer is either the same as the + // NSString OR the lifetime of the innermost @autoreleasepool. + let bytes: *const c_char = unsafe { msg_send![self, UTF8String] }; + let bytes = bytes as *const u8; let len = self.len(); - unsafe { - let bytes = slice::from_raw_parts(bytes, len); - str::from_utf8(bytes).unwrap() - } + + // SAFETY: + // The held AutoreleasePool is the innermost, and the reference is + // constrained both by the pool and the NSString. + // + // `len` is the length of the string in the UTF-8 encoding. + // + // `bytes` is a null-terminated C string (with length = len + 1), so + // it is never a NULL pointer. + let bytes: &'r [u8] = unsafe { slice::from_raw_parts(bytes, len) }; + + // TODO: Always UTF-8, so should we use `from_utf8_unchecked`? + str::from_utf8(bytes).unwrap() } - 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 { @@ -74,50 +97,108 @@ pub trait INSString: INSObject { } } -object_struct!(NSString); +object_struct!(unsafe NSString, Shared); -impl INSString for NSString {} +unsafe impl INSString for NSString {} -impl INSCopying for NSString { +unsafe impl INSCopying for NSString { type Output = NSString; } impl fmt::Display for NSString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self.as_str(), f) + autoreleasepool(|pool| fmt::Display::fmt(self.as_str(pool), f)) } } #[cfg(test)] mod tests { - use super::{INSCopying, INSString, NSString}; + use super::*; - #[cfg(not(target_vendor = "apple"))] + #[cfg(gnustep)] #[test] fn ensure_linkage() { unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() }; } + #[test] + fn test_empty() { + let s1 = NSString::from_str(""); + let s2 = NSString::new(); + + assert_eq!(s1.len(), 0); + assert_eq!(s2.len(), 0); + + assert_eq!(s1, s2); + + autoreleasepool(|pool| { + assert_eq!(s1.as_str(pool), ""); + assert_eq!(s2.as_str(pool), ""); + }); + } + #[test] fn test_utf8() { let expected = "ประเทศไทย中华Việt Nam"; let s = NSString::from_str(expected); - assert!(s.len() == expected.len()); - assert!(s.as_str() == expected); + assert_eq!(s.len(), expected.len()); + autoreleasepool(|pool| { + assert_eq!(s.as_str(pool), expected); + }); } #[test] fn test_interior_nul() { let expected = "Hello\0World"; let s = NSString::from_str(expected); - assert!(s.len() == expected.len()); - assert!(s.as_str() == expected); + assert_eq!(s.len(), expected.len()); + autoreleasepool(|pool| { + assert_eq!(s.as_str(pool), expected); + }); } #[test] fn test_copy() { let s = NSString::from_str("Hello!"); let copied = s.copy(); - assert!(copied.as_str() == s.as_str()); + autoreleasepool(|pool| { + assert_eq!(copied.as_str(pool), s.as_str(pool)); + }); + } + + #[test] + fn test_copy_nsstring_is_same() { + let string1 = NSString::from_str("Hello, world!"); + let string2 = string1.copy(); + + let s1: *const NSString = &*string1; + let s2: *const NSString = &*string2; + + assert_eq!(s1, s2, "Cloned NSString didn't have the same address"); + } + + #[test] + /// Apparently NSString does this for some reason? + fn test_strips_first_leading_zero_width_no_break_space() { + let ns_string = NSString::from_str("\u{feff}"); + let expected = ""; + autoreleasepool(|pool| { + assert_eq!(ns_string.as_str(pool), expected); + }); + assert_eq!(ns_string.len(), 0); + + let s = "\u{feff}\u{feff}a\u{feff}"; + + // Huh, this difference might be a GNUStep bug? + #[cfg(not(gnustep))] + let expected = "\u{feff}a\u{feff}"; + #[cfg(gnustep)] + let expected = "a\u{feff}"; + + let ns_string = NSString::from_str(s); + autoreleasepool(|pool| { + assert_eq!(ns_string.as_str(pool), expected); + }); + assert_eq!(ns_string.len(), expected.len()); } } diff --git a/objc2_foundation/src/value.rs b/objc2_foundation/src/value.rs index 339e0065d..03007f23c 100644 --- a/objc2_foundation/src/value.rs +++ b/objc2_foundation/src/value.rs @@ -1,5 +1,4 @@ use alloc::string::ToString; -use core::any::Any; use core::ffi::c_void; use core::marker::PhantomData; use core::mem::MaybeUninit; @@ -8,38 +7,51 @@ use core::str; use std::ffi::{CStr, CString}; use std::os::raw::c_char; -use objc2::rc::{Id, Owned}; +use objc2::rc::{Id, Shared}; use objc2::runtime::Class; use objc2::Encode; use objc2::{class, msg_send}; use super::{INSCopying, INSObject}; -pub trait INSValue: INSObject { +pub unsafe trait INSValue: INSObject { type Value: 'static + Copy + Encode; - fn value(&self) -> Self::Value { - assert!(&Self::Value::ENCODING == self.encoding()); + // Default / empty new is not provided because `-init` returns `nil` on + // Apple and GNUStep throws an exception on all other messages to this + // invalid instance. + + /// TODO. + /// + /// Note that this is broken on GNUStep for some types, see + /// [gnustep/libs-base#216]. + /// + /// [gnustep/libs-base#216]: https://github.com/gnustep/libs-base/pull/216 + fn get(&self) -> Self::Value { + if let Some(encoding) = self.encoding() { + // TODO: This can be a debug assertion (?) + assert!(&Self::Value::ENCODING == encoding, "Wrong encoding"); + unsafe { self.get_unchecked() } + } else { + panic!("Missing NSValue encoding"); + } + } + + unsafe fn get_unchecked(&self) -> Self::Value { let mut value = MaybeUninit::::uninit(); let ptr = value.as_mut_ptr() as *mut c_void; - unsafe { - let _: () = msg_send![self, getValue: ptr]; - value.assume_init() - } + let _: () = msg_send![self, getValue: ptr]; + value.assume_init() } - fn encoding(&self) -> &str { - unsafe { - let result: *const c_char = msg_send![self, objCType]; - let s = CStr::from_ptr(result); - str::from_utf8(s.to_bytes()).unwrap() - } + fn encoding(&self) -> Option<&str> { + let result: Option> = unsafe { msg_send![self, objCType] }; + result.map(|s| unsafe { CStr::from_ptr(s.as_ptr()) }.to_str().unwrap()) } - fn from_value(value: Self::Value) -> Id { + fn new(value: Self::Value) -> Id { let cls = Self::class(); - let value_ptr: *const Self::Value = &value; - let bytes = value_ptr as *const c_void; + let bytes = &value as *const Self::Value as *const c_void; let encoding = CString::new(Self::Value::ENCODING.to_string()).unwrap(); unsafe { let obj: *mut Self = msg_send![cls, alloc]; @@ -57,40 +69,44 @@ pub struct NSValue { value: PhantomData, } -object_impl!(NSValue); +object_impl!(unsafe NSValue); + +unsafe impl INSObject for NSValue { + type Ownership = Shared; -impl INSObject for NSValue -where - T: Any, -{ fn class() -> &'static Class { class!(NSValue) } } -impl INSValue for NSValue -where - T: Any + Copy + Encode, -{ +unsafe impl INSValue for NSValue { type Value = T; } -impl INSCopying for NSValue -where - T: Any, -{ +unsafe impl INSCopying for NSValue { type Output = NSValue; } #[cfg(test)] mod tests { - use crate::{INSValue, NSValue}; + use crate::{INSValue, NSRange, NSValue}; use objc2::Encode; #[test] fn test_value() { - let val = NSValue::from_value(13u32); - assert!(val.value() == 13); - assert!(&u32::ENCODING == val.encoding()); + let val = NSValue::new(13u32); + assert_eq!(val.get(), 13); + assert!(&u32::ENCODING == val.encoding().unwrap()); + } + + #[test] + fn test_value_nsrange() { + let val = NSValue::new(NSRange::from(1..2)); + assert!(&NSRange::ENCODING == val.encoding().unwrap()); + let range: NSRange = unsafe { objc2::msg_send![val, rangeValue] }; + assert_eq!(range, NSRange::from(1..2)); + // NSValue -getValue is broken on GNUStep for some types + #[cfg(not(gnustep))] + assert_eq!(val.get(), NSRange::from(1..2)); } }