Vendor things

This commit is contained in:
John Doty 2024-03-08 11:03:01 -08:00
parent 5deceec006
commit 977e3c17e5
19434 changed files with 10682014 additions and 0 deletions

View file

@ -0,0 +1,523 @@
//! Macro for making a static NSString.
//!
//! This basically does what clang does, see:
//! - Apple: <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5057-L5249>
//! - GNUStep 2.0 (not yet supported): <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCGNU.cpp#L973-L1118>
//! - Other (not yet supported): <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCGNU.cpp#L2471-L2507>
//!
//! Note that this uses the `CFString` static, while `clang` has support for
//! generating a pure `NSString`. We don't support that yet (since I don't
//! know the use-case), but we definitely could!
//! See: <https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CGObjCMac.cpp#L2007-L2068>
use core::ffi::c_void;
use core::mem::ManuallyDrop;
use core::ptr;
use core::sync::atomic::{AtomicPtr, Ordering};
use crate::foundation::NSString;
use crate::rc::Id;
use crate::runtime::Class;
// This is defined in CoreFoundation, but we don't emit a link attribute
// here because it is already linked via Foundation.
//
// Although this is a "private" (underscored) symbol, it is directly
// referenced in Objective-C binaries. So it's safe for us to reference.
extern "C" {
pub static __CFConstantStringClassReference: Class;
}
/// Structure used to describe a constant `CFString`.
///
/// This struct is the same as [`CF_CONST_STRING`], which contains
/// [`CFRuntimeBase`]. While the documentation clearly says that the ABI of
/// `CFRuntimeBase` should not be relied on, we can rely on it as long as we
/// only do it with regards to `CFString` (because `clang` does this as well).
///
/// [`CFRuntimeBase`]: <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFRuntime.h#L216-L228>
/// [`CF_CONST_STRING`]: <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFInternal.h#L332-L336>
#[repr(C)]
pub struct CFConstString {
isa: &'static Class,
// Important that we don't just use `usize` here, since that would be
// wrong on big-endian systems!
cfinfo: u32,
#[cfg(target_pointer_width = "64")]
_rc: u32,
data: *const c_void,
len: usize,
}
// Required to place in a `static`.
unsafe impl Sync for CFConstString {}
impl CFConstString {
// From `CFString.c`:
// <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFString.c#L184-L212>
// > !!! Note: Constant CFStrings use the bit patterns:
// > C8 (11001000 = default allocator, not inline, not freed contents; 8-bit; has NULL byte; doesn't have length; is immutable)
// > D0 (11010000 = default allocator, not inline, not freed contents; Unicode; is immutable)
// > The bit usages should not be modified in a way that would effect these bit patterns.
//
// Hence CoreFoundation guarantees that these two are always valid.
//
// The `CFTypeID` of `CFStringRef` is guaranteed to always be 7:
// <https://github.com/apple-oss-distributions/CF/blob/CF-1153.18/CFRuntime.c#L982>
const FLAGS_ASCII: u32 = 0x07_C8;
const FLAGS_UTF16: u32 = 0x07_D0;
pub const unsafe fn new_ascii(isa: &'static Class, data: &'static [u8]) -> Self {
Self {
isa,
cfinfo: Self::FLAGS_ASCII,
#[cfg(target_pointer_width = "64")]
_rc: 0,
data: data.as_ptr().cast(),
// The length does not include the trailing NUL.
len: data.len() - 1,
}
}
pub const unsafe fn new_utf16(isa: &'static Class, data: &'static [u16]) -> Self {
Self {
isa,
cfinfo: Self::FLAGS_UTF16,
#[cfg(target_pointer_width = "64")]
_rc: 0,
data: data.as_ptr().cast(),
// The length does not include the trailing NUL.
len: data.len() - 1,
}
}
#[inline]
pub const fn as_nsstring_const(&self) -> &NSString {
let ptr: *const Self = self;
unsafe { &*ptr.cast::<NSString>() }
}
// This is deliberately not `const` to prevent the result from being used
// in other statics, since not all platforms support that (yet).
#[inline]
pub fn as_nsstring(&self) -> &NSString {
self.as_nsstring_const()
}
}
/// Returns `true` if `bytes` is entirely ASCII with no interior NULs.
pub const fn is_ascii_no_nul(bytes: &[u8]) -> bool {
let mut i = 0;
while i < bytes.len() {
let byte = bytes[i];
if !byte.is_ascii() || byte == b'\0' {
return false;
}
i += 1;
}
true
}
pub struct Utf16Char {
pub repr: [u16; 2],
pub len: usize,
}
impl Utf16Char {
const fn encode(ch: u32) -> Self {
if ch <= 0xffff {
Self {
repr: [ch as u16, 0],
len: 1,
}
} else {
let payload = ch - 0x10000;
let hi = (payload >> 10) | 0xd800;
let lo = (payload & 0x3ff) | 0xdc00;
Self {
repr: [hi as u16, lo as u16],
len: 2,
}
}
}
#[cfg(test)]
fn as_slice(&self) -> &[u16] {
&self.repr[..self.len]
}
}
pub struct EncodeUtf16Iter {
str: &'static [u8],
index: usize,
}
impl EncodeUtf16Iter {
pub const fn new(str: &'static [u8]) -> Self {
Self { str, index: 0 }
}
pub const fn next(self) -> Option<(Self, Utf16Char)> {
if self.index >= self.str.len() {
None
} else {
let (index, ch) = decode_utf8(self.str, self.index);
Some((Self { index, ..self }, Utf16Char::encode(ch)))
}
}
}
// (&str bytes, index) -> (new index, decoded char)
const fn decode_utf8(s: &[u8], i: usize) -> (usize, u32) {
let b0 = s[i];
match b0 {
// one-byte seq
0b0000_0000..=0b0111_1111 => {
let decoded = b0 as u32;
(i + 1, decoded)
}
// two-byte seq
0b1100_0000..=0b1101_1111 => {
let decoded = ((b0 as u32 & 0x1f) << 6) | (s[i + 1] as u32 & 0x3f);
(i + 2, decoded)
}
// 3 byte seq
0b1110_0000..=0b1110_1111 => {
let decoded = ((b0 as u32 & 0x0f) << 12)
| ((s[i + 1] as u32 & 0x3f) << 6)
| (s[i + 2] as u32 & 0x3f);
(i + 3, decoded)
}
// 4 byte seq
0b1111_0000..=0b1111_0111 => {
let decoded = ((b0 as u32 & 0x07) << 18)
| ((s[i + 1] as u32 & 0x3f) << 12)
| ((s[i + 2] as u32 & 0x3f) << 6)
| (s[i + 3] as u32 & 0x3f);
(i + 4, decoded)
}
// continuation bytes, or never-valid bytes.
0b1000_0000..=0b1011_1111 | 0b1111_1000..=0b1111_1111 => {
panic!("Encountered invalid bytes")
}
}
}
/// Allows storing a [`NSString`] in a static and lazily loading it.
#[doc(hidden)]
pub struct CachedNSString {
ptr: AtomicPtr<NSString>,
}
impl CachedNSString {
/// Constructs a new [`CachedNSString`].
pub const fn new() -> Self {
Self {
ptr: AtomicPtr::new(ptr::null_mut()),
}
}
/// Returns the cached NSString. If no string is yet cached, creates one
/// with the given name and stores it.
#[inline]
pub fn get(&self, s: &str) -> &'static NSString {
// TODO: Investigate if we can use weaker orderings.
let ptr = self.ptr.load(Ordering::SeqCst);
// SAFETY: The pointer is either NULL, or has been created below.
unsafe { ptr.as_ref() }.unwrap_or_else(|| {
// "Forget" about releasing the string, effectively promoting it
// to a static.
let s = ManuallyDrop::new(NSString::from_str(s));
let ptr = Id::as_ptr(&s);
self.ptr.store(ptr as *mut NSString, Ordering::SeqCst);
// SAFETY: The pointer is valid, and will always be valid, since
// we haven't released it.
unsafe { ptr.as_ref().unwrap_unchecked() }
})
}
}
/// Creates an [`NSString`][`crate::foundation::NSString`] from a static string.
///
///
/// # Examples
///
/// This macro takes a either a `"string"` literal or `const` string slice as
/// the argument, and produces a `&'static NSString`:
///
/// ```
/// use objc2::ns_string;
/// use objc2::foundation::NSString;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let hello: &'static NSString = ns_string!("hello");
/// assert_eq!(hello.to_string(), "hello");
///
/// const WORLD: &str = "world";
/// let world = ns_string!(WORLD);
/// assert_eq!(world.to_string(), WORLD);
/// ```
///
///
/// # Unicode Strings
///
/// An NSString can contain strings with many different encodings, including
/// ASCII, UTF-8, UTF-16, and so on. This macro automatically converts your
/// string to the most efficient encoding, you don't have to do anything!
///
/// ```
/// # use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let hello_ru = ns_string!("Привет");
/// assert_eq!(hello_ru.to_string(), "Привет");
/// ```
///
/// Note that because this is implemented with `const` evaluation, massive
/// strings can increase compile time, and may even hit the `const` evaluation
/// limit.
///
///
/// # NUL handling
///
/// Strings containing ASCII NUL is allowed, the NUL is preserved as one would
/// expect:
///
/// ```
/// # use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let example = ns_string!("example\0");
/// assert_eq!(example.to_string(), "example\0");
///
/// let example = ns_string!("exa\0mple");
/// assert_eq!(example.to_string(), "exa\0mple");
/// ```
///
///
/// # Runtime Cost
///
/// None.
///
/// The result is equivalent to `@"string"` syntax in Objective-C.
///
/// Because of that, this should be preferred over [`NSString::from_str`]
/// where possible.
///
/// [`NSString::from_str`]: crate::foundation::NSString::from_str
#[cfg(feature = "foundation")] // For auto_doc_cfg
#[macro_export]
macro_rules! ns_string {
($s:expr) => {{
// Immediately place in constant for better UI
const INPUT: &str = $s;
$crate::__ns_string_inner!(INPUT)
}};
}
#[doc(hidden)]
#[cfg(feature = "apple")]
#[macro_export]
macro_rules! __ns_string_inner {
($inp:ident) => {{
const X: &[u8] = $inp.as_bytes();
$crate::__ns_string_inner!(@inner X);
// Return &'static NSString
CFSTRING.as_nsstring()
}};
(@inner $inp:ident) => {
// Note: We create both the ASCII + NUL and the UTF-16 + NUL versions
// of the string, since we can't conditionally create a static.
//
// Since we don't add the `#[used]` attribute, Rust can fairly easily
// figure out that one of the variants are never used, and simply
// exclude it.
// Convert the input slice to a C-style string with a NUL byte.
//
// The section is the same as what clang sets, see:
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5192
#[link_section = "__TEXT,__cstring,cstring_literals"]
static ASCII: [u8; $inp.len() + 1] = {
// Zero-fill with $inp.len() + 1
let mut res: [u8; $inp.len() + 1] = [0; $inp.len() + 1];
let mut i = 0;
// Fill with data from $inp
while i < $inp.len() {
res[i] = $inp[i];
i += 1;
}
// Now contains $inp + '\0'
res
};
// The full UTF-16 contents along with the written length.
const UTF16_FULL: (&[u16; $inp.len()], usize) = {
let mut out = [0u16; $inp.len()];
let mut iter = $crate::foundation::__ns_string::EncodeUtf16Iter::new($inp);
let mut written = 0;
while let Some((state, chars)) = iter.next() {
iter = state;
out[written] = chars.repr[0];
written += 1;
if chars.len > 1 {
out[written] = chars.repr[1];
written += 1;
}
}
(&{ out }, written)
};
// Convert the slice to an UTF-16 array + a final NUL byte.
//
// The section is the same as what clang sets, see:
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5193
#[link_section = "__TEXT,__ustring"]
static UTF16: [u16; UTF16_FULL.1 + 1] = {
// Zero-fill with UTF16_FULL.1 + 1
let mut res: [u16; UTF16_FULL.1 + 1] = [0; UTF16_FULL.1 + 1];
let mut i = 0;
// Fill with data from UTF16_FULL.0 up until UTF16_FULL.1
while i < UTF16_FULL.1 {
res[i] = UTF16_FULL.0[i];
i += 1;
}
// Now contains UTF16_FULL.1 + NUL
res
};
// Create the constant string structure, and store it in a static
// within a special section.
//
// The section is the same as what clang sets, see:
// https://github.com/llvm/llvm-project/blob/release/13.x/clang/lib/CodeGen/CodeGenModule.cpp#L5243
#[link_section = "__DATA,__cfstring"]
static CFSTRING: $crate::foundation::__ns_string::CFConstString = unsafe {
if $crate::foundation::__ns_string::is_ascii_no_nul($inp) {
// This is technically an optimization (UTF-16 strings are
// always valid), but it's a fairly important one!
$crate::foundation::__ns_string::CFConstString::new_ascii(
&$crate::foundation::__ns_string::__CFConstantStringClassReference,
&ASCII,
)
} else {
$crate::foundation::__ns_string::CFConstString::new_utf16(
&$crate::foundation::__ns_string::__CFConstantStringClassReference,
&UTF16,
)
}
};
};
}
#[doc(hidden)]
#[cfg(not(feature = "apple"))]
#[macro_export]
macro_rules! __ns_string_inner {
($inp:ident) => {{
use $crate::foundation::__ns_string::CachedNSString;
static CACHED_NSSTRING: CachedNSString = CachedNSString::new();
CACHED_NSSTRING.get($inp)
}};
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn test_is_ascii() {
assert!(is_ascii_no_nul(b"a"));
assert!(is_ascii_no_nul(b"abc"));
assert!(!is_ascii_no_nul(b"\xff"));
assert!(!is_ascii_no_nul(b"\0"));
assert!(!is_ascii_no_nul(b"a\0b"));
assert!(!is_ascii_no_nul(b"ab\0"));
assert!(!is_ascii_no_nul(b"a\0b\0"));
}
#[test]
fn test_decode_utf8() {
for c in '\u{0}'..=core::char::MAX {
let mut buf;
for off in 0..4 {
// Ensure we see garbage if we read outside bounds.
buf = [0xff; 8];
let len = c.encode_utf8(&mut buf[off..(off + 4)]).len();
let (end_idx, decoded) = decode_utf8(&buf, off);
assert_eq!(
(end_idx, decoded),
(off + len, c as u32),
"failed for U+{code:04X} ({ch:?}) encoded as {buf:#x?} over {range:?}",
code = c as u32,
ch = c,
buf = &buf[off..(off + len)],
range = off..(off + len),
);
}
}
}
#[test]
fn encode_utf16() {
for c in '\u{0}'..=core::char::MAX {
assert_eq!(
c.encode_utf16(&mut [0u16; 2]),
Utf16Char::encode(c as u32).as_slice(),
"failed for U+{:04X} ({:?})",
c as u32,
c
);
}
}
#[test]
fn ns_string() {
macro_rules! test {
($($s:expr,)+) => {$({
let s1 = ns_string!($s);
let s2 = NSString::from_str($s);
assert_eq!(s1, s1);
assert_eq!(s1, &*s2);
assert_eq!(s1.to_string(), $s);
assert_eq!(s2.to_string(), $s);
})+};
}
test! {
"",
"asdf",
"🦀",
"🏳️‍🌈",
"𝄞music",
"abcd【e】fg",
"abcd⒠fg",
"ääääh",
"lööps, bröther?",
"\u{fffd} \u{fffd} \u{fffd}",
"讓每個人都能打造出。",
"\0",
"\0\x01\x02\x03\x04\x05\x06\x07\x08\x09",
// "\u{feff}", // TODO
include_str!("__ns_string.rs"),
}
}
#[test]
fn ns_string_in_unsafe() {
// Test that the `unused_unsafe` lint doesn't trigger
let s = unsafe {
let s: *const NSString = ns_string!("abc");
&*s
};
assert_eq!(s.to_string(), "abc");
}
}

View file

@ -0,0 +1,506 @@
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Index, IndexMut, Range};
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{
NSCopying, NSEnumerator, NSFastEnumeration, NSFastEnumerator, NSMutableArray, NSMutableCopying,
NSObject, NSRange,
};
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
use crate::runtime::{Class, Object};
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id};
__inner_extern_class!(
/// An immutable ordered collection of objects.
///
/// This is the Objective-C equivalent of a "boxed slice" (`Box<[T]>`),
/// so effectively a `Vec<T>` where you can't change the number of
/// elements.
///
/// 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`.
///
///
/// # 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<NSMutableArray<T, Owned>, Owned>`: Allows you to mutate the
/// objects, and the array itself.
/// - `Id<NSMutableArray<T, Shared>, Owned>`: Allows you to mutate the
/// array itself, but not it's contents.
/// - `Id<NSArray<T, Owned>, Owned>`: Allows you to mutate the objects,
/// but not the array itself.
/// - `Id<NSArray<T, Shared>, Owned>`: Effectively the same as the below.
/// - `Id<Array<T, Shared>, Shared>`: Allows you to copy the array, but
/// does not allow you to modify it in any way.
/// - `Id<Array<T, Owned>, 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)]
pub struct NSArray<T: Message, O: Ownership = Shared> {
item: PhantomData<Id<T, O>>,
notunwindsafe: PhantomData<&'static mut ()>,
}
unsafe impl<T: Message, O: Ownership> ClassType for NSArray<T, O> {
type Super = NSObject;
}
);
// SAFETY: Same as Id<T, O> (which is what NSArray effectively stores).
unsafe impl<T: Message + Sync + Send> Sync for NSArray<T, Shared> {}
unsafe impl<T: Message + Sync + Send> Send for NSArray<T, Shared> {}
unsafe impl<T: Message + Sync> Sync for NSArray<T, Owned> {}
unsafe impl<T: Message + Send> Send for NSArray<T, Owned> {}
// Also same as Id<T, O>
impl<T: Message + RefUnwindSafe, O: Ownership> RefUnwindSafe for NSArray<T, O> {}
impl<T: Message + RefUnwindSafe> UnwindSafe for NSArray<T, Shared> {}
impl<T: Message + UnwindSafe> UnwindSafe for NSArray<T, Owned> {}
#[track_caller]
pub(crate) unsafe fn with_objects<T: Message + ?Sized, R: Message, O: Ownership>(
cls: &Class,
objects: &[&T],
) -> Id<R, O> {
unsafe {
msg_send_id![
msg_send_id![cls, alloc],
initWithObjects: objects.as_ptr(),
count: objects.len(),
]
}
}
extern_methods!(
/// Generic creation methods.
unsafe impl<T: Message, O: Ownership> NSArray<T, O> {
/// Get an empty array.
pub fn new() -> Id<Self, Shared> {
// SAFETY:
// - `new` may not create a new object, but instead return a shared
// instance. We remedy this by returning `Id<Self, Shared>`.
// - `O` don't actually matter here! E.g. `NSArray<T, Owned>` 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] }
}
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, O> {
// 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<NSArray<T, Owned>, Owned>` is safe to return.
//
// In essence, we can choose between always returning `Id<T, Shared>`
// or `Id<T, O>`, 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.
unsafe impl<T: Message> NSArray<T, Shared> {
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Shared> {
// SAFETY: Taking `&T` would not be sound, since the `&T` could come
// from an `Id<T, Owned>` 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.
unsafe impl<T: Message, O: Ownership> NSArray<T, O> {
#[doc(alias = "count")]
#[sel(count)]
pub fn len(&self) -> usize;
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[sel(objectAtIndex:)]
unsafe fn get_unchecked(&self, index: usize) -> &T;
#[doc(alias = "objectAtIndex:")]
pub fn get(&self, index: usize) -> Option<&T> {
// TODO: Replace this check with catching the thrown NSRangeException
if index < self.len() {
// SAFETY: The index is checked to be in bounds.
Some(unsafe { self.get_unchecked(index) })
} else {
None
}
}
#[doc(alias = "firstObject")]
#[sel(firstObject)]
pub fn first(&self) -> Option<&T>;
#[doc(alias = "lastObject")]
#[sel(lastObject)]
pub fn last(&self) -> Option<&T>;
#[doc(alias = "objectEnumerator")]
pub fn iter(&self) -> NSEnumerator<'_, T> {
unsafe {
let result: *mut Object = msg_send![self, objectEnumerator];
NSEnumerator::from_ptr(result)
}
}
#[sel(getObjects:range:)]
unsafe fn get_objects(&self, ptr: *mut &T, range: NSRange);
pub fn objects_in_range(&self, range: Range<usize>) -> Vec<&T> {
let range = NSRange::from(range);
let mut vec = Vec::with_capacity(range.length);
unsafe {
self.get_objects(vec.as_mut_ptr(), range);
vec.set_len(range.length);
}
vec
}
pub fn to_vec(&self) -> Vec<&T> {
self.objects_in_range(0..self.len())
}
// TODO: Take Id<Self, Self::ItemOwnership> ?
pub fn into_vec(array: Id<Self, Owned>) -> Vec<Id<T, O>> {
array
.to_vec()
.into_iter()
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
.collect()
}
}
/// Accessor methods that work on shared arrays.
unsafe impl<T: Message> NSArray<T, Shared> {
#[doc(alias = "objectAtIndex:")]
pub fn get_retained(&self, index: usize) -> Id<T, Shared> {
let obj = self.get(index).unwrap();
// SAFETY: The object is originally shared (see `where` bound).
unsafe { Id::retain_autoreleased(obj as *const T as *mut T).unwrap_unchecked() }
}
pub fn to_shared_vec(&self) -> Vec<Id<T, Shared>> {
self.to_vec()
.into_iter()
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
.collect()
}
}
/// Accessor methods that work on owned arrays.
unsafe impl<T: Message> NSArray<T, Owned> {
#[doc(alias = "objectAtIndex:")]
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
// 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
}
}
#[doc(alias = "firstObject")]
#[sel(firstObject)]
pub fn first_mut(&mut self) -> Option<&mut T>;
#[doc(alias = "lastObject")]
#[sel(lastObject)]
pub fn last_mut(&mut self) -> Option<&mut T>;
}
);
/// This is implemented as a shallow copy.
///
/// As such, it is only possible when the array's contents are `Shared`.
unsafe impl<T: Message> NSCopying for NSArray<T, Shared> {
type Ownership = Shared;
type Output = NSArray<T, Shared>;
}
/// This is implemented as a shallow copy.
unsafe impl<T: Message> NSMutableCopying for NSArray<T, Shared> {
type Output = NSMutableArray<T, Shared>;
}
impl<T: Message> alloc::borrow::ToOwned for NSArray<T, Shared> {
type Owned = Id<NSArray<T, Shared>, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSArray<T, O> {
type Item = T;
}
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSArray<T, O> {
type Item = &'a T;
type IntoIter = NSFastEnumerator<'a, NSArray<T, O>>;
fn into_iter(self) -> Self::IntoIter {
self.iter_fast()
}
}
impl<T: Message, O: Ownership> Index<usize> for NSArray<T, O> {
type Output = T;
fn index(&self, index: usize) -> &T {
self.get(index).unwrap()
}
}
impl<T: Message> IndexMut<usize> for NSArray<T, Owned> {
fn index_mut(&mut self, index: usize) -> &mut T {
self.get_mut(index).unwrap()
}
}
impl<T: Message, O: Ownership> DefaultId for NSArray<T, O> {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSArray<T, O> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_list().entries(self.iter_fast()).finish()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::vec::Vec;
use super::*;
use crate::foundation::{NSNumber, NSString};
use crate::rc::{RcTestObject, ThreadTestData};
fn sample_array(len: usize) -> Id<NSArray<NSObject, Owned>, Owned> {
let mut vec = Vec::with_capacity(len);
for _ in 0..len {
vec.push(NSObject::new());
}
NSArray::from_vec(vec)
}
fn sample_number_array(len: u8) -> Id<NSArray<NSNumber, Shared>, Shared> {
let mut vec = Vec::with_capacity(len as usize);
for i in 0..len {
vec.push(NSNumber::new_u8(i));
}
NSArray::from_vec(vec)
}
#[test]
fn test_two_empty() {
let _empty_array1 = NSArray::<NSObject>::new();
let _empty_array2 = NSArray::<NSObject>::new();
}
#[test]
fn test_len() {
let empty_array = NSArray::<NSObject>::new();
assert_eq!(empty_array.len(), 0);
let array = sample_array(4);
assert_eq!(array.len(), 4);
}
#[test]
fn test_equality() {
let array1 = sample_array(3);
let array2 = sample_array(3);
assert_ne!(array1, array2);
let array1 = sample_number_array(3);
let array2 = sample_number_array(3);
assert_eq!(array1, array2);
let array1 = sample_number_array(3);
let array2 = sample_number_array(4);
assert_ne!(array1, array2);
}
#[test]
fn test_debug() {
let obj = sample_number_array(0);
assert_eq!(format!("{:?}", obj), "[]");
let obj = sample_number_array(3);
assert_eq!(format!("{:?}", obj), "[0, 1, 2]");
}
#[test]
fn test_get() {
let array = sample_array(4);
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 = <NSArray<NSObject>>::new();
assert!(empty_array.first().is_none());
assert!(empty_array.last().is_none());
}
#[test]
fn test_retains_stored() {
let obj = Id::into_shared(RcTestObject::new());
let mut expected = ThreadTestData::current();
let input = [obj.clone(), obj.clone()];
expected.retain += 2;
expected.assert_current();
let array = NSArray::from_slice(&input);
expected.retain += 2;
expected.assert_current();
let _obj = array.first().unwrap();
expected.assert_current();
drop(array);
expected.release += 2;
expected.assert_current();
let array = NSArray::from_vec(Vec::from(input));
expected.retain += 2;
expected.release += 2;
expected.assert_current();
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();
drop(obj);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_nscopying_uses_retain() {
let obj = Id::into_shared(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]
#[cfg_attr(
feature = "apple",
ignore = "this works differently on different framework versions"
)]
fn test_iter_no_retain() {
let obj = Id::into_shared(RcTestObject::new());
let array = NSArray::from_slice(&[obj]);
let mut expected = ThreadTestData::current();
let iter = array.iter();
expected.retain += 0;
expected.assert_current();
assert_eq!(iter.count(), 1);
expected.autorelease += 0;
expected.assert_current();
let iter = array.iter_fast();
assert_eq!(iter.count(), 1);
expected.assert_current();
}
#[test]
fn test_iter() {
let array = sample_array(4);
assert_eq!(array.iter().count(), 4);
assert!(array
.iter()
.enumerate()
.all(|(i, obj)| Some(obj) == array.get(i)));
}
#[test]
fn test_objects_in_range() {
let array = sample_array(4);
let middle_objs = array.objects_in_range(1..3);
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_eq!(all_objs.len(), 4);
}
#[test]
fn test_into_vec() {
let array = sample_array(4);
let vec = NSArray::into_vec(array);
assert_eq!(vec.len(), 4);
}
#[test]
fn test_generic_ownership_traits() {
fn assert_partialeq<T: PartialEq>() {}
assert_partialeq::<NSArray<NSString, Shared>>();
assert_partialeq::<NSArray<NSString, Owned>>();
fn test_ownership_implies_partialeq<O: Ownership>() {
assert_partialeq::<NSArray<NSString, O>>();
}
test_ownership_implies_partialeq::<Shared>();
test_ownership_implies_partialeq::<Owned>();
}
}

View file

@ -0,0 +1,212 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{
NSCopying, NSDictionary, NSMutableAttributedString, NSMutableCopying, NSObject, NSString,
};
use crate::rc::{DefaultId, Id, Shared};
use crate::runtime::Object;
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A string that has associated attributes for portions of its text.
///
/// Examples of attributes could be: Visual style, hyperlinks, or
/// accessibility data.
///
/// Conceptually, each UTF-16 code unit in an attributed string has its
/// own collection of attributes - most often though
///
/// Only the most basic functionality is defined here, the `AppKit`
/// framework contains most of the extension methods.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsattributedstring?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSAttributedString;
unsafe impl ClassType for NSAttributedString {
type Super = NSObject;
}
);
// SAFETY: `NSAttributedString` is immutable and `NSMutableAttributedString`
// can only be mutated from `&mut` methods.
unsafe impl Sync for NSAttributedString {}
unsafe impl Send for NSAttributedString {}
// Same reasoning as `NSString`.
impl UnwindSafe for NSAttributedString {}
impl RefUnwindSafe for NSAttributedString {}
/// Attributes that you can apply to text in an attributed string.
pub type NSAttributedStringKey = NSString;
extern_methods!(
/// Creating attributed strings.
unsafe impl NSAttributedString {
/// Construct an empty attributed string.
pub fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
/// Creates a new attributed string from the given string and attributes.
///
/// The attributes are associated with every UTF-16 code unit in the
/// string.
#[doc(alias = "initWithString:")]
pub fn new_with_attributes(
string: &NSString,
// TODO: Mutability of the dictionary should be (Shared, Shared)
attributes: &NSDictionary<NSAttributedStringKey, Object>,
) -> Id<Self, Shared> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithString: string, attributes: attributes]
}
}
/// Creates a new attributed string without any attributes.
#[doc(alias = "initWithString:")]
pub fn from_nsstring(string: &NSString) -> Id<Self, Shared> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithString: string]
}
}
}
/// Querying.
unsafe impl NSAttributedString {
// TODO: Lifetimes?
pub fn string(&self) -> Id<NSString, Shared> {
unsafe { msg_send_id![self, string] }
}
/// Alias for `self.string().len_utf16()`.
#[doc(alias = "length")]
#[sel(length)]
pub fn len_utf16(&self) -> usize;
// /// TODO
// ///
// /// See [Apple's documentation on Effective and Maximal Ranges][doc].
// ///
// /// [doc]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/AttributedStrings/Tasks/AccessingAttrs.html#//apple_ref/doc/uid/20000161-SW2
// #[doc(alias = "attributesAtIndex:effectiveRange:")]
// pub fn attributes_in_effective_range(
// &self,
// index: usize,
// range: Range<usize>,
// ) -> Id<Self, Shared> {
// let range = NSRange::from(range);
// todo!()
// }
//
// attributesAtIndex:longestEffectiveRange:inRange:
// TODO: attributedSubstringFromRange:
// TODO: enumerateAttributesInRange:options:usingBlock:
}
);
impl DefaultId for NSAttributedString {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
unsafe impl NSCopying for NSAttributedString {
type Ownership = Shared;
type Output = NSAttributedString;
}
unsafe impl NSMutableCopying for NSAttributedString {
type Output = NSMutableAttributedString;
}
impl alloc::borrow::ToOwned for NSAttributedString {
type Owned = Id<NSAttributedString, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl fmt::Debug for NSAttributedString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Use -[NSAttributedString description] since it is pretty good
let obj: &NSObject = self;
fmt::Debug::fmt(obj, f)
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use alloc::{format, vec};
use super::*;
use crate::rc::{autoreleasepool, Owned};
#[test]
fn test_new() {
let s = NSAttributedString::new();
assert_eq!(&s.string().to_string(), "");
}
#[test]
fn test_string_bound_to_attributed() {
let attr_s = {
let source = NSString::from_str("Hello world!");
NSAttributedString::from_nsstring(&source)
};
let s = autoreleasepool(|_| attr_s.string());
assert_eq!(s.len(), 12);
}
#[test]
fn test_from_nsstring() {
let s = NSAttributedString::from_nsstring(&NSString::from_str("abc"));
assert_eq!(&s.string().to_string(), "abc");
}
#[test]
fn test_copy() {
let s1 = NSAttributedString::from_nsstring(&NSString::from_str("abc"));
let s2 = s1.copy();
// NSAttributedString performs this optimization in GNUStep's runtime,
// but not in Apple's; so we don't test for it!
// assert_eq!(Id::as_ptr(&s1), Id::as_ptr(&s2));
assert!(s2.is_kind_of::<NSAttributedString>());
let s3 = s1.mutable_copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3).cast());
assert!(s3.is_kind_of::<NSMutableAttributedString>());
}
#[test]
fn test_debug() {
let s = NSAttributedString::from_nsstring(&NSString::from_str("abc"));
let expected = if cfg!(feature = "gnustep-1-7") {
"abc{}"
} else {
"abc{\n}"
};
assert_eq!(format!("{:?}", s), expected);
let obj: Id<Object, Owned> = unsafe { Id::cast(NSObject::new()) };
let ptr: *const Object = &*obj;
let s = NSAttributedString::new_with_attributes(
&NSString::from_str("abc"),
&NSDictionary::from_keys_and_objects(&[&*NSString::from_str("test")], vec![obj]),
);
let expected = if cfg!(feature = "gnustep-1-7") {
format!("abc{{test = \"<NSObject: {:?}>\"; }}", ptr)
} else {
format!("abc{{\n test = \"<NSObject: {:?}>\";\n}}", ptr)
};
assert_eq!(format!("{:?}", s), expected);
}
}

View file

@ -0,0 +1,73 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSCopying, NSDictionary, NSObject, NSString};
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ns_string, ClassType};
extern_class!(
/// A representation of the code and resources stored in a bundle
/// directory on disk.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsbundle?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSBundle;
unsafe impl ClassType for NSBundle {
type Super = NSObject;
}
);
// SAFETY: Bundles are documented as thread-safe.
unsafe impl Sync for NSBundle {}
unsafe impl Send for NSBundle {}
impl UnwindSafe for NSBundle {}
impl RefUnwindSafe for NSBundle {}
extern_methods!(
unsafe impl NSBundle {
pub fn main() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), mainBundle] }
}
pub fn info(&self) -> Id<NSDictionary<NSString, NSObject>, Shared> {
unsafe { msg_send_id![self, infoDictionary] }
}
pub fn name(&self) -> Option<Id<NSString, Shared>> {
self.info().get(ns_string!("CFBundleName")).map(|name| {
let ptr: *const NSObject = name;
let ptr: *const NSString = ptr.cast();
// SAFETY: TODO
let name = unsafe { ptr.as_ref().unwrap_unchecked() };
name.copy()
})
}
}
);
impl fmt::Debug for NSBundle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Delegate to NSObject
(**self).fmt(f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
use std::println;
#[test]
#[cfg_attr(not(target_os = "macos"), ignore = "varies between platforms")]
fn try_running_functions() {
// This is mostly empty since cargo doesn't bundle the application
// before executing.
let bundle = NSBundle::main();
println!("{:?}", bundle);
assert_eq!(format!("{:?}", bundle.info()), "{}");
assert_eq!(bundle.name(), None);
}
}

View file

@ -0,0 +1,54 @@
use core::cmp::Ordering;
use crate::{Encode, Encoding, RefEncode};
/// Constants that indicate sort order.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nscomparisonresult?language=objc).
#[repr(isize)] // NSInteger
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum NSComparisonResult {
/// The left operand is smaller than the right operand.
Ascending = -1,
/// The two operands are equal.
Same = 0,
/// The left operand is greater than the right operand.
Descending = 1,
}
impl Default for NSComparisonResult {
#[inline]
fn default() -> Self {
Self::Same
}
}
unsafe impl Encode for NSComparisonResult {
const ENCODING: Encoding = isize::ENCODING;
}
unsafe impl RefEncode for NSComparisonResult {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
impl From<Ordering> for NSComparisonResult {
#[inline]
fn from(order: Ordering) -> Self {
match order {
Ordering::Less => Self::Ascending,
Ordering::Equal => Self::Same,
Ordering::Greater => Self::Descending,
}
}
}
impl From<NSComparisonResult> for Ordering {
#[inline]
fn from(comparison_result: NSComparisonResult) -> Self {
match comparison_result {
NSComparisonResult::Ascending => Self::Less,
NSComparisonResult::Same => Self::Equal,
NSComparisonResult::Descending => Self::Greater,
}
}
}

View file

@ -0,0 +1,48 @@
use crate::rc::{Id, Owned, Ownership};
use crate::{msg_send_id, Message};
pub unsafe trait NSCopying: Message {
/// Indicates whether the type is mutable or immutable.
///
/// This can be [`Owned`] if and only if `copy` creates a new instance,
/// see the following example:
///
/// ```ignore
/// let x: Id<MyObject, _> = 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<MyObject, Owned> = x.copy();
/// ```
///
/// Note that for the same reason, you should be careful when defining
/// `new` methods on your object; e.g. immutable types like [`NSString`]
/// don't return `Id<NSString, Owned>`, because that would allow this
/// trait to create an aliasing `Id<NSString, Shared>` (since sending the
/// `copy` message (and others) does not create a new instance, but
/// instead just retains the instance).
///
/// [`NSString`]: crate::foundation::NSString
type Ownership: Ownership;
/// The output type.
///
/// This is usually `Self`, but e.g. `NSMutableString` returns `NSString`.
/// TODO: Verify???
type Output: Message;
fn copy(&self) -> Id<Self::Output, Self::Ownership> {
unsafe { msg_send_id![self, copy] }
}
}
/// TODO
///
/// Note that the `mutableCopy` selector must return an owned object!
pub unsafe trait NSMutableCopying: Message {
/// TODO
type Output: Message;
fn mutable_copy(&self) -> Id<Self::Output, Owned> {
unsafe { msg_send_id![self, mutableCopy] }
}
}

View file

@ -0,0 +1,228 @@
#[cfg(feature = "block")]
use alloc::vec::Vec;
use core::ffi::c_void;
use core::fmt;
use core::ops::Index;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::slice::{self, SliceIndex};
use super::{NSCopying, NSMutableCopying, NSMutableData, NSObject};
use crate::rc::{DefaultId, Id, Shared};
use crate::runtime::{Class, Object};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A static byte buffer in memory.
///
/// This is similar to a [`slice`][`prim@slice`] of [`u8`].
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsdata?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSData;
unsafe impl ClassType for NSData {
type Super = NSObject;
}
);
// SAFETY: `NSData` is immutable and `NSMutableData` can only be mutated from
// `&mut` methods.
unsafe impl Sync for NSData {}
unsafe impl Send for NSData {}
impl UnwindSafe for NSData {}
impl RefUnwindSafe for NSData {}
extern_methods!(
/// Creation methods.
unsafe impl NSData {
pub fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
pub fn with_bytes(bytes: &[u8]) -> Id<Self, Shared> {
unsafe { Id::cast(with_slice(Self::class(), bytes)) }
}
#[cfg(feature = "block")]
pub fn from_vec(bytes: Vec<u8>) -> Id<Self, Shared> {
// 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.
// See https://github.com/gnustep/libs-base/pull/213
// So we just use NSDataWithDeallocatorBlock directly.
//
// NSMutableData does not have this problem.
#[cfg(feature = "gnustep-1-7")]
let cls = crate::class!(NSDataWithDeallocatorBlock);
#[cfg(not(feature = "gnustep-1-7"))]
let cls = Self::class();
unsafe { Id::cast(with_vec(cls, bytes)) }
}
}
/// Accessor methods.
unsafe impl NSData {
#[sel(length)]
#[doc(alias = "length")]
pub fn len(&self) -> usize;
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[sel(bytes)]
fn bytes_raw(&self) -> *const c_void;
pub fn bytes(&self) -> &[u8] {
let ptr = self.bytes_raw();
let ptr: *const u8 = ptr.cast();
// The bytes pointer may be null for length zero
if ptr.is_null() {
&[]
} else {
unsafe { slice::from_raw_parts(ptr, self.len()) }
}
}
}
);
unsafe impl NSCopying for NSData {
type Ownership = Shared;
type Output = NSData;
}
unsafe impl NSMutableCopying for NSData {
type Output = NSMutableData;
}
impl alloc::borrow::ToOwned for NSData {
type Owned = Id<NSData, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl AsRef<[u8]> for NSData {
fn as_ref(&self) -> &[u8] {
self.bytes()
}
}
// Note: We don't implement `Borrow<[u8]>` since we can't guarantee that `Eq`,
// `Ord` and `Hash` are equal for `NSData` vs. `[u8]`!
impl<I: SliceIndex<[u8]>> Index<I> for NSData {
type Output = I::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output {
// Replaces the need for getBytes:range:
Index::index(self.bytes(), index)
}
}
impl DefaultId for NSData {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl fmt::Debug for NSData {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// -[NSData description] is quite unreadable
fmt::Debug::fmt(self.bytes(), f)
}
}
impl<'a> IntoIterator for &'a NSData {
type Item = &'a u8;
type IntoIter = core::slice::Iter<'a, u8>;
fn into_iter(self) -> Self::IntoIter {
self.bytes().iter()
}
}
pub(crate) unsafe fn with_slice(cls: &Class, bytes: &[u8]) -> Id<Object, Shared> {
let bytes_ptr: *const c_void = bytes.as_ptr().cast();
unsafe {
msg_send_id![
msg_send_id![cls, alloc],
initWithBytes: bytes_ptr,
length: bytes.len(),
]
}
}
#[cfg(feature = "block")]
pub(crate) unsafe fn with_vec(cls: &Class, bytes: Vec<u8>) -> Id<Object, Shared> {
use core::mem::ManuallyDrop;
use block2::{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::<u8>::from_raw_parts(bytes.cast(), len, capacity);
});
let dealloc = dealloc.copy();
let dealloc: &Block<(*mut c_void, usize), ()> = &dealloc;
let mut bytes = ManuallyDrop::new(bytes);
let bytes_ptr: *mut c_void = bytes.as_mut_ptr().cast();
unsafe {
msg_send_id![
msg_send_id![cls, alloc],
initWithBytesNoCopy: bytes_ptr,
length: bytes.len(),
deallocator: dealloc,
]
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
#[cfg(feature = "block")]
use alloc::vec;
#[test]
fn test_bytes() {
let bytes = [3, 7, 16, 52, 112, 19];
let data = NSData::with_bytes(&bytes);
assert_eq!(data.len(), bytes.len());
assert_eq!(data.bytes(), bytes);
}
#[test]
fn test_no_bytes() {
let data = NSData::new();
assert!(Some(data.bytes()).is_some());
}
#[cfg(feature = "block")]
#[test]
fn test_from_vec() {
let bytes = vec![3, 7, 16];
let bytes_ptr = bytes.as_ptr();
let data = NSData::from_vec(bytes);
assert_eq!(data.bytes().as_ptr(), bytes_ptr);
}
#[test]
fn test_debug() {
let bytes = [3, 7, 16, 52, 112, 19];
let data = NSData::with_bytes(&bytes);
assert_eq!(format!("{:?}", data), "[3, 7, 16, 52, 112, 19]");
}
}

View file

@ -0,0 +1,264 @@
use alloc::vec::Vec;
use core::cmp::min;
use core::fmt;
use core::marker::PhantomData;
use core::ops::Index;
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr;
use super::{NSArray, NSCopying, NSEnumerator, NSFastEnumeration, NSObject};
use crate::rc::{DefaultId, Id, Owned, Shared, SliceId};
use crate::{ClassType, __inner_extern_class, extern_methods, msg_send, msg_send_id, Message};
__inner_extern_class!(
#[derive(PartialEq, Eq, Hash)]
pub struct NSDictionary<K: Message, V: Message> {
key: PhantomData<Id<K, Shared>>,
obj: PhantomData<Id<V, Owned>>,
}
unsafe impl<K: Message, V: Message> ClassType for NSDictionary<K, V> {
type Super = NSObject;
}
);
// TODO: SAFETY
// Approximately same as `NSArray<T, Shared>`
unsafe impl<K: Message + Sync + Send, V: Message + Sync> Sync for NSDictionary<K, V> {}
unsafe impl<K: Message + Sync + Send, V: Message + Send> Send for NSDictionary<K, V> {}
// Approximately same as `NSArray<T, Shared>`
impl<K: Message + UnwindSafe, V: Message + UnwindSafe> UnwindSafe for NSDictionary<K, V> {}
impl<K: Message + RefUnwindSafe, V: Message + RefUnwindSafe> RefUnwindSafe for NSDictionary<K, V> {}
extern_methods!(
unsafe impl<K: Message, V: Message> NSDictionary<K, V> {
pub fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
#[doc(alias = "count")]
#[sel(count)]
pub fn len(&self) -> usize;
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[doc(alias = "objectForKey:")]
#[sel(objectForKey:)]
pub fn get(&self, key: &K) -> Option<&V>;
#[sel(getObjects:andKeys:)]
unsafe fn get_objects_and_keys(&self, objects: *mut &V, keys: *mut &K);
#[doc(alias = "getObjects:andKeys:")]
pub fn keys(&self) -> Vec<&K> {
let len = self.len();
let mut keys = Vec::with_capacity(len);
unsafe {
self.get_objects_and_keys(ptr::null_mut(), keys.as_mut_ptr());
keys.set_len(len);
}
keys
}
#[doc(alias = "getObjects:andKeys:")]
pub fn values(&self) -> Vec<&V> {
let len = self.len();
let mut vals = Vec::with_capacity(len);
unsafe {
self.get_objects_and_keys(vals.as_mut_ptr(), ptr::null_mut());
vals.set_len(len);
}
vals
}
#[doc(alias = "getObjects:andKeys:")]
pub fn keys_and_objects(&self) -> (Vec<&K>, Vec<&V>) {
let len = self.len();
let mut keys = Vec::with_capacity(len);
let mut objs = Vec::with_capacity(len);
unsafe {
self.get_objects_and_keys(objs.as_mut_ptr(), keys.as_mut_ptr());
keys.set_len(len);
objs.set_len(len);
}
(keys, objs)
}
#[doc(alias = "keyEnumerator")]
pub fn iter_keys(&self) -> NSEnumerator<'_, K> {
unsafe {
let result = msg_send![self, keyEnumerator];
NSEnumerator::from_ptr(result)
}
}
#[doc(alias = "objectEnumerator")]
pub fn iter_values(&self) -> NSEnumerator<'_, V> {
unsafe {
let result = msg_send![self, objectEnumerator];
NSEnumerator::from_ptr(result)
}
}
pub fn keys_array(&self) -> Id<NSArray<K, Shared>, Shared> {
unsafe { msg_send_id![self, allKeys] }
}
pub fn from_keys_and_objects<T>(keys: &[&T], vals: Vec<Id<V, Owned>>) -> Id<Self, Shared>
where
T: NSCopying<Output = K>,
{
let vals = vals.as_slice_ref();
let cls = Self::class();
let count = min(keys.len(), vals.len());
let obj = unsafe { msg_send_id![cls, alloc] };
unsafe {
msg_send_id![
obj,
initWithObjects: vals.as_ptr(),
forKeys: keys.as_ptr(),
count: count,
]
}
}
pub fn into_values_array(dict: Id<Self, Owned>) -> Id<NSArray<V, Owned>, Shared> {
unsafe { msg_send_id![&dict, allValues] }
}
}
);
impl<K: Message, V: Message> DefaultId for NSDictionary<K, V> {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
unsafe impl<K: Message, V: Message> NSFastEnumeration for NSDictionary<K, V> {
type Item = K;
}
impl<'a, K: Message, V: Message> Index<&'a K> for NSDictionary<K, V> {
type Output = V;
fn index<'s>(&'s self, index: &'a K) -> &'s V {
self.get(index).unwrap()
}
}
impl<K: fmt::Debug + Message, V: fmt::Debug + Message> fmt::Debug for NSDictionary<K, V> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let iter = self.iter_keys().zip(self.iter_values());
f.debug_map().entries(iter).finish()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::vec;
use super::*;
use crate::foundation::NSString;
use crate::rc::autoreleasepool;
fn sample_dict(key: &str) -> Id<NSDictionary<NSString, NSObject>, Shared> {
let string = NSString::from_str(key);
let obj = NSObject::new();
NSDictionary::from_keys_and_objects(&[&*string], vec![obj])
}
#[test]
fn test_len() {
let dict = sample_dict("abcd");
assert_eq!(dict.len(), 1);
}
#[test]
fn test_get() {
let dict = sample_dict("abcd");
let string = NSString::from_str("abcd");
assert!(dict.get(&string).is_some());
let string = NSString::from_str("abcde");
assert!(dict.get(&string).is_none());
}
#[test]
fn test_keys() {
let dict = sample_dict("abcd");
let keys = dict.keys();
assert_eq!(keys.len(), 1);
autoreleasepool(|pool| {
assert_eq!(keys[0].as_str(pool), "abcd");
});
}
#[test]
fn test_values() {
let dict = sample_dict("abcd");
let vals = dict.values();
assert_eq!(vals.len(), 1);
}
#[test]
fn test_keys_and_objects() {
let dict = sample_dict("abcd");
let (keys, objs) = dict.keys_and_objects();
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_iter_keys() {
let dict = sample_dict("abcd");
assert_eq!(dict.iter_keys().count(), 1);
autoreleasepool(|pool| {
assert_eq!(dict.iter_keys().next().unwrap().as_str(pool), "abcd");
});
}
#[test]
fn test_iter_values() {
let dict = sample_dict("abcd");
assert_eq!(dict.iter_values().count(), 1);
}
#[test]
fn test_arrays() {
let dict = sample_dict("abcd");
let keys = dict.keys_array();
assert_eq!(keys.len(), 1);
autoreleasepool(|pool| {
assert_eq!(keys[0].as_str(pool), "abcd");
});
// let objs = NSDictionary::into_values_array(dict);
// assert_eq!(objs.len(), 1);
}
#[test]
fn test_debug() {
let key = NSString::from_str("a");
// TODO: Fix this
let val = unsafe { Id::from_shared(NSString::from_str("b")) };
let dict = NSDictionary::from_keys_and_objects(&[&*key], vec![val]);
assert_eq!(format!("{:?}", dict), r#"{"a": "b"}"#);
}
}

View file

@ -0,0 +1,196 @@
use core::marker::PhantomData;
use core::mem;
use core::ptr;
use core::slice;
use std::os::raw::c_ulong;
use crate::rc::{Id, Owned};
use crate::runtime::Object;
use crate::{msg_send, Encode, Encoding, Message, RefEncode};
// TODO: https://doc.rust-lang.org/stable/reference/trait-bounds.html#lifetime-bounds
pub struct NSEnumerator<'a, T: Message> {
id: Id<Object, Owned>,
item: PhantomData<&'a T>,
}
impl<'a, T: Message> NSEnumerator<'a, T> {
/// TODO
///
/// # Safety
///
/// The object pointer must be a valid `NSEnumerator` with `Owned`
/// ownership.
pub unsafe fn from_ptr(ptr: *mut Object) -> Self {
Self {
id: unsafe { Id::retain_autoreleased(ptr) }.unwrap(),
item: PhantomData,
}
}
}
impl<'a, T: Message> Iterator for NSEnumerator<'a, T> {
type Item = &'a T;
fn next(&mut self) -> Option<&'a T> {
unsafe { msg_send![&mut self.id, nextObject] }
}
}
pub unsafe trait NSFastEnumeration: Message {
type Item: Message;
fn iter_fast(&self) -> NSFastEnumerator<'_, Self> {
NSFastEnumerator::new(self)
}
}
#[repr(C)]
struct NSFastEnumerationState<T: Message> {
state: c_ulong, // TODO: Verify this is actually always 64 bit
items_ptr: *const *const T,
mutations_ptr: *mut c_ulong,
extra: [c_ulong; 5],
}
unsafe impl<T: Message> Encode for NSFastEnumerationState<T> {
const ENCODING: Encoding = Encoding::Struct(
"?",
&[
Encoding::C_ULONG,
Encoding::Pointer(&Encoding::Object), // <*const *const T>::ENCODING
Encoding::Pointer(&Encoding::C_ULONG),
Encoding::Array(5, &Encoding::C_ULONG),
],
);
}
unsafe impl<T: Message> RefEncode for NSFastEnumerationState<T> {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
fn enumerate<'a, 'b: 'a, C: NSFastEnumeration + ?Sized>(
object: &'b C,
state: &mut NSFastEnumerationState<C::Item>,
buf: &'a mut [*const C::Item],
) -> Option<&'a [*const C::Item]> {
let count: usize = unsafe {
// Reborrow state so that we don't move it
let state = &mut *state;
msg_send![
object,
countByEnumeratingWithState: state,
objects: buf.as_mut_ptr(),
count: buf.len(),
]
};
if count > 0 {
unsafe { Some(slice::from_raw_parts(state.items_ptr, count)) }
} else {
None
}
}
const FAST_ENUM_BUF_SIZE: usize = 16;
pub struct NSFastEnumerator<'a, C: 'a + NSFastEnumeration + ?Sized> {
object: &'a C,
ptr: *const *const C::Item,
end: *const *const C::Item,
state: NSFastEnumerationState<C::Item>,
buf: [*const C::Item; FAST_ENUM_BUF_SIZE],
}
impl<'a, C: NSFastEnumeration + ?Sized> NSFastEnumerator<'a, C> {
fn new(object: &'a C) -> Self {
Self {
object,
ptr: ptr::null(),
end: ptr::null(),
state: unsafe { mem::zeroed() },
buf: [ptr::null(); FAST_ENUM_BUF_SIZE],
}
}
fn update_buf(&mut self) -> bool {
// If this isn't our first time enumerating, record the previous value
// from the mutations pointer.
let mutations = if !self.ptr.is_null() {
Some(unsafe { *self.state.mutations_ptr })
} else {
None
};
let next_buf = enumerate(self.object, &mut self.state, &mut self.buf);
if let Some(buf) = next_buf {
// Check if the collection was mutated
if let Some(mutations) = mutations {
assert_eq!(
mutations,
unsafe { *self.state.mutations_ptr },
"Mutation detected during enumeration of object {:p}",
self.object
);
}
self.ptr = buf.as_ptr();
self.end = unsafe { self.ptr.add(buf.len()) };
true
} else {
self.ptr = ptr::null();
self.end = ptr::null();
false
}
}
}
impl<'a, C: NSFastEnumeration + ?Sized> Iterator for NSFastEnumerator<'a, C> {
type Item = &'a C::Item;
fn next(&mut self) -> Option<&'a C::Item> {
if self.ptr == self.end && !self.update_buf() {
None
} else {
unsafe {
let obj = *self.ptr;
self.ptr = self.ptr.offset(1);
Some(obj.as_ref().unwrap_unchecked())
}
}
}
}
#[cfg(test)]
mod tests {
use super::NSFastEnumeration;
use crate::foundation::{NSArray, NSNumber};
#[test]
fn test_enumerator() {
let vec = (0..4).map(NSNumber::new_usize).collect();
let array = NSArray::from_vec(vec);
let enumerator = array.iter();
assert_eq!(enumerator.count(), 4);
let enumerator = array.iter();
assert!(enumerator.enumerate().all(|(i, obj)| obj.as_usize() == i));
}
#[test]
fn test_fast_enumerator() {
let vec = (0..4).map(NSNumber::new_usize).collect();
let array = NSArray::from_vec(vec);
let enumerator = array.iter_fast();
assert_eq!(enumerator.count(), 4);
let enumerator = array.iter_fast();
assert!(enumerator.enumerate().all(|(i, obj)| obj.as_usize() == i));
}
}

View file

@ -0,0 +1,165 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSCopying, NSDictionary, NSObject, NSString};
use crate::ffi::NSInteger;
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// Information about an error condition including a domain, a
/// domain-specific error code, and application-specific information.
///
/// See also Apple's [documentation on error handling][err], and their
/// NSError [API reference][api].
///
/// [err]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorHandling/ErrorHandling.html#//apple_ref/doc/uid/TP40001806-CH201-SW1
/// [api]: https://developer.apple.com/documentation/foundation/nserror?language=objc
#[derive(PartialEq, Eq, Hash)]
pub struct NSError;
unsafe impl ClassType for NSError {
type Super = NSObject;
}
);
// SAFETY: Error objects are immutable data containers.
unsafe impl Sync for NSError {}
unsafe impl Send for NSError {}
impl UnwindSafe for NSError {}
impl RefUnwindSafe for NSError {}
pub type NSErrorUserInfoKey = NSString;
pub type NSErrorDomain = NSString;
extern_methods!(
/// Creation methods.
unsafe impl NSError {
/// Construct a new [`NSError`] with the given code in the given domain.
pub fn new(code: NSInteger, domain: &NSString) -> Id<Self, Shared> {
unsafe { Self::with_user_info(code, domain, None) }
}
// TODO: Figure out safety of `user_info` dict!
unsafe fn with_user_info(
code: NSInteger,
domain: &NSString,
user_info: Option<&NSDictionary<NSErrorUserInfoKey, NSObject>>,
) -> Id<Self, Shared> {
// SAFETY: `domain` and `user_info` are copied to the error object, so
// even if the `&NSString` came from a `&mut NSMutableString`, we're
// still good!
unsafe {
msg_send_id![
msg_send_id![Self::class(), alloc],
initWithDomain: domain,
code: code,
userInfo: user_info,
]
}
}
}
/// Accessor methods.
unsafe impl NSError {
pub fn domain(&self) -> Id<NSString, Shared> {
unsafe { msg_send_id![self, domain] }
}
#[sel(code)]
pub fn code(&self) -> NSInteger;
pub fn user_info(&self) -> Option<Id<NSDictionary<NSErrorUserInfoKey, NSObject>, Shared>> {
unsafe { msg_send_id![self, userInfo] }
}
pub fn localized_description(&self) -> Id<NSString, Shared> {
// TODO: For some reason this leaks a lot?
let obj: Option<_> = unsafe { msg_send_id![self, localizedDescription] };
obj.expect(
"unexpected NULL localized description; a default should have been generated!",
)
}
// TODO: localizedRecoveryOptions
// TODO: localizedRecoverySuggestion
// TODO: localizedFailureReason
// TODO: helpAnchor
// TODO: +setUserInfoValueProviderForDomain:provider:
// TODO: +userInfoValueProviderForDomain:
// TODO: recoveryAttempter
// TODO: attemptRecoveryFromError:...
// TODO: Figure out if this is a good design, or if we should do something
// differently (like a Rusty name for the function, or putting a bunch of
// statics in a module instead)?
#[allow(non_snake_case)]
pub fn NSLocalizedDescriptionKey() -> &'static NSErrorUserInfoKey {
extern "C" {
#[link_name = "NSLocalizedDescriptionKey"]
static VALUE: &'static NSErrorUserInfoKey;
}
unsafe { VALUE }
}
// TODO: Other NSErrorUserInfoKey values
// TODO: NSErrorDomain values
}
);
impl fmt::Debug for NSError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NSError")
.field("domain", &self.domain())
.field("code", &self.code())
.field("user_info", &self.user_info())
.finish()
}
}
impl fmt::Display for NSError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.localized_description())
}
}
impl std::error::Error for NSError {}
unsafe impl NSCopying for NSError {
type Ownership = Shared;
type Output = Self;
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
use crate::ns_string;
#[test]
fn custom_domain() {
let error = NSError::new(42, ns_string!("MyDomain"));
assert_eq!(error.code(), 42);
assert_eq!(&*error.domain(), ns_string!("MyDomain"));
let expected = if cfg!(feature = "apple") {
"The operation couldnt be completed. (MyDomain error 42.)"
} else {
"MyDomain 42"
};
assert_eq!(format!("{}", error), expected);
}
#[test]
fn basic() {
let error = NSError::new(-999, ns_string!("NSURLErrorDomain"));
let expected = if cfg!(feature = "apple") {
"The operation couldnt be completed. (NSURLErrorDomain error -999.)"
} else {
"NSURLErrorDomain -999"
};
assert_eq!(format!("{}", error), expected);
}
}

View file

@ -0,0 +1,200 @@
use core::fmt;
use core::hint::unreachable_unchecked;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSCopying, NSDictionary, NSObject, NSString};
use crate::exception::Exception;
use crate::rc::{Id, Shared};
use crate::runtime::Object;
use crate::{extern_class, extern_methods, msg_send_id, sel, ClassType};
extern_class!(
/// A special condition that interrupts the normal flow of program
/// execution.
///
/// Exceptions can be thrown and caught using the `objc2::exception`
/// module.
///
/// See also [Apple's documentation][doc].
///
/// [doc]: https://developer.apple.com/documentation/foundation/nsexception?language=objc
#[derive(PartialEq, Eq, Hash)]
pub struct NSException;
unsafe impl ClassType for NSException {
type Super = NSObject;
}
);
// SAFETY: Exception objects are immutable data containers, and documented as
// thread safe.
unsafe impl Sync for NSException {}
unsafe impl Send for NSException {}
impl UnwindSafe for NSException {}
impl RefUnwindSafe for NSException {}
type NSExceptionName = NSString;
extern_methods!(
unsafe impl NSException {
/// Create a new [`NSException`] object.
///
/// Returns `None` if the exception couldn't be created (example: If the
/// process is out of memory).
pub fn new(
name: &NSExceptionName,
reason: Option<&NSString>,
user_info: Option<&NSDictionary<Object, Object>>,
) -> Option<Id<Self, Shared>> {
let obj = unsafe { msg_send_id![Self::class(), alloc] };
unsafe { msg_send_id![obj, initWithName: name, reason: reason, userInfo: user_info] }
}
#[sel(raise)]
unsafe fn raise_raw(&self);
/// Raises the exception, causing program flow to jump to the local
/// exception handler.
///
/// This is equivalent to using `objc2::exception::throw`.
///
///
/// # Safety
///
/// Same as `objc2::exception::throw`.
pub unsafe fn raise(&self) -> ! {
// SAFETY: We only create `Shared` NSExceptions, so it is safe to give
// to the place where `@catch` receives it.
unsafe { self.raise_raw() };
// SAFETY: `raise` will throw an exception, or abort if something
// unexpected happened.
unsafe { unreachable_unchecked() }
}
/// A that uniquely identifies the type of exception.
///
/// See [Apple's documentation][doc] for some of the different values this
/// can take.
///
/// [doc]: https://developer.apple.com/documentation/foundation/nsexceptionname?language=objc
pub fn name(&self) -> Id<NSExceptionName, Shared> {
// Nullability not documented, but a name is expected in most places.
unsafe { msg_send_id![self, name] }
}
/// A human-readable message summarizing the reason for the exception.
pub fn reason(&self) -> Option<Id<NSString, Shared>> {
unsafe { msg_send_id![self, reason] }
}
/// Application-specific data pertaining to the exception.
pub fn user_info(&self) -> Option<Id<NSDictionary<Object, Object>, Shared>> {
unsafe { msg_send_id![self, userInfo] }
}
/// Convert this into an [`Exception`] object.
pub fn into_exception(this: Id<Self, Shared>) -> Id<Exception, Shared> {
// SAFETY: Downcasting to "subclass"
unsafe { Id::cast(this) }
}
pub(crate) fn is_nsexception(obj: &Exception) -> bool {
if obj.class().responds_to(sel!(isKindOfClass:)) {
// SAFETY: We only use `isKindOfClass:` on NSObject
let obj: *const Exception = obj;
let obj = unsafe { obj.cast::<NSObject>().as_ref().unwrap() };
obj.is_kind_of::<Self>()
} else {
false
}
}
/// Create this from an [`Exception`] object.
///
/// This should be considered a hint; it may return `Err` in very, very
/// few cases where the object is actually an instance of `NSException`.
pub fn from_exception(
obj: Id<Exception, Shared>,
) -> Result<Id<Self, Shared>, Id<Exception, Shared>> {
if Self::is_nsexception(&obj) {
// SAFETY: Just checked the object is an NSException
Ok(unsafe { Id::cast::<Self>(obj) })
} else {
Err(obj)
}
}
}
);
unsafe impl NSCopying for NSException {
type Ownership = Shared;
type Output = NSException;
}
impl alloc::borrow::ToOwned for NSException {
type Owned = Id<NSException, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl fmt::Debug for NSException {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let obj: &Object = self.as_ref();
write!(f, "{:?} '{}'", obj, self.name())?;
if let Some(reason) = self.reason() {
write!(f, " reason:{}", reason)?;
} else {
write!(f, " reason:(NULL)")?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::*;
#[test]
fn create_and_query() {
let exc = NSException::new(
&NSString::from_str("abc"),
Some(&NSString::from_str("def")),
None,
)
.unwrap();
assert_eq!(exc.name(), NSString::from_str("abc"));
assert_eq!(exc.reason().unwrap(), NSString::from_str("def"));
assert!(exc.user_info().is_none());
let debug = format!("<NSException: {:p}> 'abc' reason:def", exc);
assert_eq!(format!("{:?}", exc), debug);
let description = if cfg!(feature = "gnustep-1-7") {
format!("<NSException: {:p}> NAME:abc REASON:def", exc)
} else {
"def".into()
};
let exc: &NSObject = &exc;
assert_eq!(format!("{:?}", exc), description);
}
#[test]
#[should_panic = "'abc' reason:def"]
fn unwrap() {
let exc = NSException::new(
&NSString::from_str("abc"),
Some(&NSString::from_str("def")),
None,
)
.unwrap();
let _: () = Err(exc).unwrap();
}
// Further tests in `tests::exception`
}

View file

@ -0,0 +1,446 @@
use crate::{Encode, Encoding, RefEncode};
#[cfg(target_pointer_width = "64")]
type InnerFloat = f64;
#[cfg(not(target_pointer_width = "64"))]
type InnerFloat = f32;
/// The basic type for all floating-point values.
///
/// This is [`f32`] on 32-bit platforms and [`f64`] on 64-bit platforms.
///
/// This technically belongs to the `CoreGraphics` framework, but we define it
/// here for convenience.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/coregraphics/cgfloat?language=objc).
// Defined in CoreGraphics/CGBase.h
// TODO: Use a newtype here?
pub type CGFloat = InnerFloat;
// NSGeometry types are just aliases to CGGeometry types on iOS, tvOS, watchOS
// and macOS 64bit (and hence their Objective-C encodings are different).
//
// TODO: Adjust `objc2-encode` so that this is handled there, and so that we
// can effectively just forget about it and use `NS` and `CG` types equally.
#[cfg(all(
feature = "apple",
not(all(target_os = "macos", target_pointer_width = "32"))
))]
mod names {
pub(super) const POINT: &str = "CGPoint";
pub(super) const SIZE: &str = "CGSize";
pub(super) const RECT: &str = "CGRect";
}
#[cfg(any(
feature = "gnustep-1-7",
all(target_os = "macos", target_pointer_width = "32")
))]
mod names {
pub(super) const POINT: &str = "_NSPoint";
pub(super) const SIZE: &str = "_NSSize";
pub(super) const RECT: &str = "_NSRect";
}
/// A point in a two-dimensional coordinate system.
///
/// This technically belongs to the `CoreGraphics` framework, but we define it
/// here for convenience.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgpoint?language=objc).
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct CGPoint {
/// The x-coordinate of the point.
pub x: CGFloat,
/// The y-coordinate of the point.
pub y: CGFloat,
}
unsafe impl Encode for CGPoint {
const ENCODING: Encoding =
Encoding::Struct(names::POINT, &[CGFloat::ENCODING, CGFloat::ENCODING]);
}
unsafe impl RefEncode for CGPoint {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
impl CGPoint {
/// Create a new point with the given coordinates.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::CGPoint;
/// assert_eq!(CGPoint::new(10.0, -2.3), CGPoint { x: 10.0, y: -2.3 });
/// ```
#[inline]
#[doc(alias = "NSMakePoint")]
#[doc(alias = "CGPointMake")]
pub const fn new(x: CGFloat, y: CGFloat) -> Self {
Self { x, y }
}
/// A point with both coordinates set to `0.0`.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::CGPoint;
/// assert_eq!(CGPoint::ZERO, CGPoint { x: 0.0, y: 0.0 });
/// ```
#[doc(alias = "NSZeroPoint")]
#[doc(alias = "CGPointZero")]
#[doc(alias = "ORIGIN")]
pub const ZERO: Self = Self::new(0.0, 0.0);
}
/// A two-dimensional size.
///
/// As this is sometimes used to represent a distance vector, rather than a
/// physical size, the width and height are _not_ guaranteed to be
/// non-negative! Methods that expect that must use one of [`CGSize::abs`] or
/// [`CGRect::standardize`].
///
/// This technically belongs to the `CoreGraphics` framework, but we define it
/// here for convenience.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgsize?language=objc).
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct CGSize {
/// The dimensions along the x-axis.
pub width: CGFloat,
/// The dimensions along the y-axis.
pub height: CGFloat,
}
unsafe impl Encode for CGSize {
const ENCODING: Encoding =
Encoding::Struct(names::SIZE, &[CGFloat::ENCODING, CGFloat::ENCODING]);
}
unsafe impl RefEncode for CGSize {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
impl CGSize {
/// Create a new size with the given dimensions.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::CGSize;
/// let size = CGSize::new(10.0, 2.3);
/// assert_eq!(size.width, 10.0);
/// assert_eq!(size.height, 2.3);
/// ```
///
/// Negative values are allowed (though often undesired).
///
/// ```
/// use objc2::foundation::CGSize;
/// let size = CGSize::new(-1.0, 0.0);
/// assert_eq!(size.width, -1.0);
/// ```
#[inline]
#[doc(alias = "NSMakeSize")]
#[doc(alias = "CGSizeMake")]
pub const fn new(width: CGFloat, height: CGFloat) -> Self {
// The documentation for NSSize explicitly says:
// > If the value of width or height is negative, however, the
// > behavior of some methods may be undefined.
//
// But since this type can come from FFI, we'll leave it up to the
// user to ensure that it is used safely.
Self { width, height }
}
/// Convert the size to a non-negative size.
///
/// This can be used to convert the size to a safe value.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::CGSize;
/// assert_eq!(CGSize::new(-1.0, 1.0).abs(), CGSize::new(1.0, 1.0));
/// ```
#[inline]
pub fn abs(self) -> Self {
Self::new(self.width.abs(), self.height.abs())
}
/// A size that is 0.0 in both dimensions.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::CGSize;
/// assert_eq!(CGSize::ZERO, CGSize { width: 0.0, height: 0.0 });
/// ```
#[doc(alias = "NSZeroSize")]
#[doc(alias = "CGSizeZero")]
pub const ZERO: Self = Self::new(0.0, 0.0);
}
/// The location and dimensions of a rectangle.
///
/// In the default Core Graphics coordinate space (macOS), the origin is
/// located in the lower-left corner of the rectangle and the rectangle
/// extends towards the upper-right corner.
///
/// If the context has a flipped coordinate space (iOS, tvOS, watchOS) the
/// origin is in the upper-left corner and the rectangle extends towards the
/// lower-right corner.
///
/// This technically belongs to the `CoreGraphics` framework, but we define it
/// here for convenience.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/corefoundation/cgrect?language=objc).
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq, Default)]
pub struct CGRect {
/// The coordinates of the rectangles origin.
pub origin: CGPoint,
/// The dimensions of the rectangle.
pub size: CGSize,
}
unsafe impl Encode for CGRect {
const ENCODING: Encoding =
Encoding::Struct(names::RECT, &[CGPoint::ENCODING, CGSize::ENCODING]);
}
unsafe impl RefEncode for CGRect {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
impl CGRect {
/// Create a new rectangle with the given origin and dimensions.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::{CGPoint, CGRect, CGSize};
/// let origin = CGPoint::new(10.0, -2.3);
/// let size = CGSize::new(5.0, 0.0);
/// let rect = CGRect::new(origin, size);
/// ```
#[inline]
#[doc(alias = "NSMakeRect")]
#[doc(alias = "CGRectMake")]
pub const fn new(origin: CGPoint, size: CGSize) -> Self {
Self { origin, size }
}
/// A rectangle with origin (0.0, 0.0) and zero width and height.
#[doc(alias = "NSZeroRect")]
#[doc(alias = "CGRectZero")]
pub const ZERO: Self = Self::new(CGPoint::ZERO, CGSize::ZERO);
/// Returns a rectangle with a positive width and height.
///
/// This is often useful
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::{CGPoint, CGRect, CGSize};
/// let origin = CGPoint::new(1.0, 1.0);
/// let size = CGSize::new(-5.0, -2.0);
/// let rect = CGRect::new(origin, size);
/// assert_eq!(rect.standardize().size, CGSize::new(5.0, 2.0));
/// ```
#[inline]
#[doc(alias = "CGRectStandardize")]
pub fn standardize(self) -> Self {
Self::new(self.origin, self.size.abs())
}
/// The smallest coordinate of the rectangle.
#[inline]
#[doc(alias = "CGRectGetMinX")]
#[doc(alias = "CGRectGetMinY")]
#[doc(alias = "NSMinX")]
#[doc(alias = "NSMinY")]
pub fn min(self) -> CGPoint {
self.origin
}
/// The center point of the rectangle.
#[inline]
#[doc(alias = "CGRectGetMidX")]
#[doc(alias = "CGRectGetMidY")]
#[doc(alias = "NSMidX")]
#[doc(alias = "NSMidY")]
pub fn mid(self) -> CGPoint {
CGPoint::new(
self.origin.x + (self.size.width * 0.5),
self.origin.y + (self.size.height * 0.5),
)
}
/// The largest coordinate of the rectangle.
#[inline]
#[doc(alias = "CGRectGetMaxX")]
#[doc(alias = "CGRectGetMaxY")]
#[doc(alias = "NSMaxX")]
#[doc(alias = "NSMaxY")]
pub fn max(self) -> CGPoint {
CGPoint::new(
self.origin.x + self.size.width,
self.origin.y + self.size.height,
)
}
/// Returns whether a rectangle has zero width or height.
///
///
/// # Examples
///
/// ```
/// use objc2::foundation::{CGPoint, CGRect, CGSize};
/// assert!(CGRect::ZERO.is_empty());
/// let point = CGPoint::new(1.0, 2.0);
/// assert!(CGRect::new(point, CGSize::ZERO).is_empty());
/// assert!(!CGRect::new(point, CGSize::new(1.0, 1.0)).is_empty());
/// ```
#[inline]
#[doc(alias = "CGRectIsEmpty")]
pub fn is_empty(self) -> bool {
!(self.size.width > 0.0 && self.size.height > 0.0)
// TODO: NaN handling?
// self.size.width <= 0.0 || self.size.height <= 0.0
}
// TODO: NSContainsRect / CGRectContainsRect
// TODO: NSDivideRect / CGRectDivide
// TODO: NSInsetRect / CGRectInset
// TODO: NSIntegralRect / CGRectIntegral
// TODO: NSIntersectionRect / CGRectIntersection
// TODO: NSUnionRect / CGRectUnion
// TODO: NSIntersectsRect / CGRectIntersectsRect
// TODO: NSMouseInRect
// TODO: NSMouseInRect
// TODO: NSPointInRect / CGRectContainsPoint
// TODO: NSOffsetRect / CGRectOffset
// TODO: CGRectIsNull
// TODO: CGRectIsInfinite
// TODO: CGRectInfinite
// TODO: CGRectNull
// TODO: NSHeight / CGRectGetHeight (standardized)
// TODO: NSWidth / CGRectGetWidth (standardized)
}
/// A point in a Cartesian coordinate system.
///
/// This is just a convenience alias for [`CGPoint`]. For ease of use, it is
/// available on all platforms, though in practice it is only useful on macOS.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nspoint?language=objc).
pub type NSPoint = CGPoint;
/// A two-dimensional size.
///
/// This is just a convenience alias for [`CGSize`]. For ease of use, it is
/// available on all platforms, though in practice it is only useful on macOS.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nssize?language=objc).
pub type NSSize = CGSize;
/// A rectangle.
///
/// This is just a convenience alias for [`CGRect`]. For ease of use, it is
/// available on all platforms, though in practice it is only useful on macOS.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsrect?language=objc).
pub type NSRect = CGRect;
// TODO: struct NSEdgeInsets
// TODO: enum NSRectEdge
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cgsize_new() {
CGSize::new(1.0, 1.0);
CGSize::new(0.0, -0.0);
CGSize::new(-0.0, 0.0);
CGSize::new(-0.0, -0.0);
CGSize::new(-1.0, -1.0);
CGSize::new(-1.0, 1.0);
CGSize::new(1.0, -1.0);
}
// We know the Rust implementation handles NaN, infinite, negative zero
// and so on properly, so let's ensure that NSEqualXXX handles these as
// well (so that we're confident that the implementations are equivalent).
#[test]
#[cfg(any(all(feature = "apple", target_os = "macos"), feature = "gnustep-1-7"))] // or macabi
fn test_partial_eq() {
use crate::runtime::Bool;
// Note: No need to use "C-unwind"
extern "C" {
fn NSEqualPoints(a: NSPoint, b: NSPoint) -> Bool;
fn NSEqualSizes(a: NSSize, b: NSSize) -> Bool;
fn NSEqualRects(a: NSRect, b: NSRect) -> Bool;
}
// We assume that comparisons handle e.g. `x` and `y` in the same way,
// therefore we just set the coordinates / dimensions to the same.
let cases: &[(CGFloat, CGFloat)] = &[
(0.0, 0.0),
(-0.0, -0.0),
(0.0, -0.0),
(1.0, 1.0 + CGFloat::EPSILON),
(0.0, CGFloat::MIN_POSITIVE),
(0.0, CGFloat::EPSILON),
(1.0, 1.0),
(1.0, -1.0),
// Infinity
(CGFloat::INFINITY, CGFloat::INFINITY),
(CGFloat::INFINITY, CGFloat::NEG_INFINITY),
(CGFloat::NEG_INFINITY, CGFloat::NEG_INFINITY),
// NaN
(CGFloat::NAN, 0.0),
(CGFloat::NAN, 1.0),
(CGFloat::NAN, CGFloat::NAN),
(CGFloat::NAN, -CGFloat::NAN),
(-CGFloat::NAN, -CGFloat::NAN),
(CGFloat::NAN, CGFloat::INFINITY),
];
for case in cases {
let point_a = NSPoint::new(case.0, case.1);
let point_b = NSPoint::new(case.0, case.1);
let actual = unsafe { NSEqualPoints(point_a, point_b).as_bool() };
assert_eq!(point_a == point_b, actual);
if case.0 >= 0.0 && case.1 >= 0.0 {
let size_a = NSSize::new(case.0, case.1);
let size_b = NSSize::new(case.0, case.1);
let actual = unsafe { NSEqualSizes(size_a, size_b).as_bool() };
assert_eq!(size_a == size_b, actual);
let rect_a = NSRect::new(point_a, size_a);
let rect_b = NSRect::new(point_b, size_b);
let actual = unsafe { NSEqualRects(rect_a, rect_b).as_bool() };
assert_eq!(rect_a == rect_b, actual);
}
}
}
}

View file

@ -0,0 +1,215 @@
//! Bindings to the `Foundation` framework.
//!
//! This is the [`std`] equivalent for Objective-C, containing essential data
//! types, collections, and operating-system services.
//!
//! See [Apple's documentation](https://developer.apple.com/documentation/foundation?language=objc).
//!
//!
//! ## Philosophy
//!
//! The `Foundation` framework is _huge_! If we aspired to map every API it
//! exposes (a lot of it is just helper methods to make Objective-C more
//! ergonomic), this library would never be finished. Instead, our focus lies
//! on conversion methods, to allow easily using them from Rust.
//!
//! If you find some API that an object doesn't expose (but should), we gladly
//! accept [pull requests]. If it is something that is out of scope, these
//! objects implement the [`Message`] trait, so you can always just manually
//! call a method on them using the [`msg_send!`] family of macros.
//!
//! [pull requests]: https://github.com/madsmtm/objc2/pulls
//! [`Message`]: crate::Message
//! [`msg_send!`]: crate::msg_send
//!
//!
//! # Use of `Deref`
//!
//! `objc2::foundation` uses the [`Deref`] trait in a bit special way: All
//! objects deref to their superclasses. For example, `NSMutableArray` derefs
//! to `NSArray`, which in turn derefs to `NSObject`.
//!
//! Note that this is explicitly recommended against in [the
//! documentation][`Deref`] and [the Rust Design patterns
//! book][anti-pattern-deref] (see those links for details).
//!
//! Due to Objective-C objects only ever being accessible behind pointers in
//! the first place, the problems stated there are less severe, and having the
//! implementation just means that everything is much nicer when you actually
//! want to use the objects!
//!
//! All objects also implement [`AsRef`] and [`AsMut`] to their superclass,
//! and can be used in [`Id::into_super`], so if you favour explicit
//! conversion, that is a possibility too.
//!
//! [`Deref`]: std::ops::Deref
//! [`ClassType`]: crate::ClassType
//! [anti-pattern-deref]: https://rust-unofficial.github.io/patterns/anti_patterns/deref.html
//! [`Id::into_super`]: crate::rc::Id::into_super
// TODO: Remove these
#![allow(missing_docs)]
#![allow(clippy::missing_safety_doc)]
use std::os::raw::c_double;
pub use self::array::NSArray;
pub use self::attributed_string::{NSAttributedString, NSAttributedStringKey};
pub use self::bundle::NSBundle;
pub use self::comparison_result::NSComparisonResult;
pub use self::copying::{NSCopying, NSMutableCopying};
pub use self::data::NSData;
pub use self::dictionary::NSDictionary;
pub use self::enumerator::{NSEnumerator, NSFastEnumeration, NSFastEnumerator};
pub use self::error::{NSError, NSErrorDomain, NSErrorUserInfoKey};
pub use self::exception::NSException;
pub use self::geometry::{CGFloat, CGPoint, CGRect, CGSize, NSPoint, NSRect, NSSize};
pub use self::mutable_array::NSMutableArray;
pub use self::mutable_attributed_string::NSMutableAttributedString;
pub use self::mutable_data::NSMutableData;
pub use self::mutable_dictionary::NSMutableDictionary;
pub use self::mutable_set::NSMutableSet;
pub use self::mutable_string::NSMutableString;
pub use self::number::NSNumber;
pub use self::object::NSObject;
pub use self::process_info::NSProcessInfo;
pub use self::range::NSRange;
pub use self::set::NSSet;
pub use self::string::NSString;
pub use self::thread::{is_main_thread, is_multi_threaded, MainThreadMarker, NSThread};
#[cfg(not(macos_10_7))] // Temporary
pub use self::uuid::NSUUID;
pub use self::value::NSValue;
pub use self::zone::NSZone;
// Available under Foundation, so makes sense here as well:
// https://developer.apple.com/documentation/foundation/numbers_data_and_basic_values?language=objc
#[doc(no_inline)]
pub use crate::ffi::{NSInteger, NSUInteger};
/// A value indicating that a requested item couldnt be found or doesnt exist.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsnotfound?language=objc).
#[allow(non_upper_case_globals)]
pub const NSNotFound: NSInteger = crate::ffi::NSIntegerMax;
/// A number of seconds.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nstimeinterval?language=objc).
pub type NSTimeInterval = c_double;
#[cfg(feature = "apple")]
#[link(name = "Foundation", kind = "framework")]
extern "C" {}
#[cfg(feature = "gnustep-1-7")]
#[link(name = "gnustep-base", kind = "dylib")]
extern "C" {}
#[doc(hidden)]
pub mod __ns_string;
mod array;
mod attributed_string;
mod bundle;
mod comparison_result;
mod copying;
mod data;
mod dictionary;
mod enumerator;
mod error;
mod exception;
mod geometry;
mod mutable_array;
mod mutable_attributed_string;
mod mutable_data;
mod mutable_dictionary;
mod mutable_set;
mod mutable_string;
mod number;
mod object;
mod process_info;
mod range;
mod set;
mod string;
mod thread;
// Temporarily disable testing UUID on macOS 10.7 until
#[cfg(not(macos_10_7))]
mod uuid;
mod value;
mod zone;
#[cfg(test)]
mod tests {
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::*;
use crate::rc::{Id, Owned, Shared};
// We expect most Foundation types to be UnwindSafe and RefUnwindSafe,
// since they follow Rust's usual mutability rules (&T = immutable).
//
// A _lot_ of Objective-C code out there would be subtly broken if e.g.
// `NSString` wasn't exception safe!
// As an example: -[NSArray objectAtIndex:] can throw, but it is still
// perfectly valid to access the array after that!
//
// Note that e.g. `&mut NSMutableString` is still not exception safe, but
// that is the entire idea of `UnwindSafe` (that if the object could have
// been mutated, it is not exception safe).
//
// Also note that this is still just a speed bump, not actually part of
// any unsafe contract; we really protect against it if something is not
// exception safe, since `UnwindSafe` is a safe trait.
fn assert_unwindsafe<T: UnwindSafe + RefUnwindSafe>() {}
fn assert_auto_traits<T: Send + Sync + UnwindSafe + RefUnwindSafe>() {
assert_unwindsafe::<T>();
}
#[test]
fn send_sync_unwindsafe() {
assert_auto_traits::<NSArray<NSString, Shared>>();
assert_auto_traits::<NSArray<NSString, Owned>>();
assert_auto_traits::<Id<NSArray<NSString, Shared>, Shared>>();
assert_auto_traits::<Id<NSArray<NSString, Owned>, Shared>>();
assert_auto_traits::<Id<NSArray<NSString, Shared>, Owned>>();
assert_auto_traits::<Id<NSArray<NSString, Owned>, Owned>>();
assert_auto_traits::<NSAttributedString>();
assert_auto_traits::<NSComparisonResult>();
assert_auto_traits::<NSData>();
assert_auto_traits::<NSDictionary<NSString, NSString>>();
assert_auto_traits::<NSSet<NSString, Shared>>();
assert_auto_traits::<NSSet<NSString, Owned>>();
assert_auto_traits::<Id<NSSet<NSString, Shared>, Shared>>();
assert_auto_traits::<Id<NSSet<NSString, Owned>, Shared>>();
assert_auto_traits::<Id<NSSet<NSString, Shared>, Owned>>();
assert_auto_traits::<Id<NSSet<NSString, Owned>, Owned>>();
// TODO: Figure out if Send + Sync is safe?
// assert_auto_traits::<NSEnumerator<NSString>>();
// assert_auto_traits::<NSFastEnumerator<NSArray<NSString, Shared>>>();
assert_auto_traits::<NSError>();
assert_auto_traits::<NSException>();
assert_auto_traits::<CGFloat>();
assert_auto_traits::<NSPoint>();
assert_auto_traits::<NSRect>();
assert_auto_traits::<NSSize>();
assert_auto_traits::<NSMutableArray<NSString, Shared>>();
assert_auto_traits::<NSMutableAttributedString>();
assert_auto_traits::<NSMutableData>();
assert_auto_traits::<NSMutableDictionary<NSString, NSString>>();
assert_auto_traits::<NSMutableSet<NSString, Shared>>();
assert_auto_traits::<NSMutableString>();
assert_auto_traits::<NSNumber>();
// assert_auto_traits::<NSObject>(); // Intentional
assert_auto_traits::<NSProcessInfo>();
assert_auto_traits::<NSRange>();
assert_auto_traits::<NSString>();
assert_unwindsafe::<MainThreadMarker>(); // Intentional
assert_auto_traits::<NSThread>();
#[cfg(not(macos_10_7))]
assert_auto_traits::<NSUUID>();
// assert_auto_traits::<NSValue>(); // Intentional
assert_unwindsafe::<NSZone>(); // Intentional
}
}

View file

@ -0,0 +1,326 @@
use alloc::vec::Vec;
use core::cmp::Ordering;
use core::ffi::c_void;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Index, IndexMut};
use super::array::with_objects;
use super::{
NSArray, NSComparisonResult, NSCopying, NSFastEnumeration, NSFastEnumerator, NSMutableCopying,
NSObject,
};
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id};
__inner_extern_class!(
/// 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)]
pub struct NSMutableArray<T: Message, O: Ownership = Owned> {
p: PhantomData<*mut ()>,
}
unsafe impl<T: Message, O: Ownership> ClassType for NSMutableArray<T, O> {
#[inherits(NSObject)]
type Super = NSArray<T, O>;
}
);
// SAFETY: Same as NSArray<T, O>
//
// Put here because rustdoc doesn't show these otherwise
unsafe impl<T: Message + Sync + Send> Sync for NSMutableArray<T, Shared> {}
unsafe impl<T: Message + Sync + Send> Send for NSMutableArray<T, Shared> {}
unsafe impl<T: Message + Sync> Sync for NSMutableArray<T, Owned> {}
unsafe impl<T: Message + Send> Send for NSMutableArray<T, Owned> {}
extern_methods!(
/// Generic creation methods.
unsafe impl<T: Message, O: Ownership> NSMutableArray<T, O> {
pub fn new() -> Id<Self, Owned> {
// SAFETY: Same as `NSArray::new`, except mutable arrays are always
// unique.
unsafe { msg_send_id![Self::class(), new] }
}
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, Owned> {
// 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.
unsafe impl<T: Message> NSMutableArray<T, Shared> {
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Owned> {
// SAFETY: Same as `NSArray::from_slice`, except mutable arrays are
// always unique.
unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
}
}
/// Generic accessor methods.
unsafe impl<T: Message, O: Ownership> NSMutableArray<T, O> {
#[doc(alias = "addObject:")]
pub fn push(&mut self, obj: Id<T, O>) {
// SAFETY: The object is not nil
unsafe { msg_send![self, addObject: &*obj] }
}
#[doc(alias = "insertObject:atIndex:")]
pub fn insert(&mut self, index: usize, obj: Id<T, O>) {
// 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
);
}
}
#[doc(alias = "replaceObjectAtIndex:withObject:")]
pub fn replace(&mut self, index: usize, obj: Id<T, O>) -> Id<T, O> {
let old_obj = unsafe {
let obj = self.get(index).unwrap();
Id::retain_autoreleased(obj as *const T as *mut T).unwrap_unchecked()
};
unsafe {
let _: () = msg_send![
self,
replaceObjectAtIndex: index,
withObject: &*obj,
];
}
old_obj
}
#[sel(removeObjectAtIndex:)]
unsafe fn remove_at(&mut self, index: usize);
#[doc(alias = "removeObjectAtIndex:")]
pub fn remove(&mut self, index: usize) -> Id<T, O> {
let obj = if let Some(obj) = self.get(index) {
unsafe { Id::retain_autoreleased(obj as *const T as *mut T).unwrap_unchecked() }
} else {
panic!("removal index should be < len");
};
unsafe { self.remove_at(index) };
obj
}
#[sel(removeLastObject)]
unsafe fn remove_last(&mut self);
#[doc(alias = "removeLastObject")]
pub fn pop(&mut self) -> Option<Id<T, O>> {
self.last()
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
.map(|obj| {
// SAFETY: `Self::last` just checked that there is an object
unsafe { self.remove_last() };
obj
})
}
#[doc(alias = "removeAllObjects")]
#[sel(removeAllObjects)]
pub fn clear(&mut self);
#[doc(alias = "sortUsingFunction:context:")]
pub fn sort_by<F: FnMut(&T, &T) -> Ordering>(&mut self, compare: F) {
// TODO: "C-unwind"
extern "C" fn compare_with_closure<U, F: FnMut(&U, &U) -> Ordering>(
obj1: &U,
obj2: &U,
context: *mut c_void,
) -> NSComparisonResult {
// Bring back a reference to the closure.
// Guaranteed to be unique, we gave `sortUsingFunction` unique is
// ownership, and that method only runs one function at a time.
let closure: &mut F = unsafe { context.cast::<F>().as_mut().unwrap_unchecked() };
NSComparisonResult::from((*closure)(obj1, obj2))
}
// We can't name the actual lifetimes in use here, so use `_`.
// See also https://github.com/rust-lang/rust/issues/56105
let f: extern "C" fn(_, _, *mut c_void) -> NSComparisonResult =
compare_with_closure::<T, F>;
// Grab a type-erased pointer to the closure (a pointer to stack).
let mut closure = compare;
let context: *mut F = &mut closure;
let context: *mut c_void = context.cast();
unsafe {
let _: () = msg_send![self, sortUsingFunction: f, context: context];
}
// Keep the closure alive until the function has run.
drop(closure);
}
}
);
// Copying only possible when ItemOwnership = Shared
/// This is implemented as a shallow copy.
unsafe impl<T: Message> NSCopying for NSMutableArray<T, Shared> {
type Ownership = Shared;
type Output = NSArray<T, Shared>;
}
/// This is implemented as a shallow copy.
unsafe impl<T: Message> NSMutableCopying for NSMutableArray<T, Shared> {
type Output = NSMutableArray<T, Shared>;
}
impl<T: Message> alloc::borrow::ToOwned for NSMutableArray<T, Shared> {
type Owned = Id<NSMutableArray<T, Shared>, Owned>;
fn to_owned(&self) -> Self::Owned {
self.mutable_copy()
}
}
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSMutableArray<T, O> {
type Item = T;
}
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSMutableArray<T, O> {
type Item = &'a T;
type IntoIter = NSFastEnumerator<'a, NSMutableArray<T, O>>;
fn into_iter(self) -> Self::IntoIter {
self.iter_fast()
}
}
impl<T: Message, O: Ownership> Extend<Id<T, O>> for NSMutableArray<T, O> {
fn extend<I: IntoIterator<Item = Id<T, O>>>(&mut self, iter: I) {
let iterator = iter.into_iter();
iterator.for_each(move |item| self.push(item));
}
}
impl<T: Message, O: Ownership> Index<usize> for NSMutableArray<T, O> {
type Output = T;
fn index(&self, index: usize) -> &T {
self.get(index).unwrap()
}
}
impl<T: Message> IndexMut<usize> for NSMutableArray<T, Owned> {
fn index_mut(&mut self, index: usize) -> &mut T {
self.get_mut(index).unwrap()
}
}
impl<T: Message, O: Ownership> DefaultId for NSMutableArray<T, O> {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSMutableArray<T, O> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
use crate::foundation::NSString;
use crate::rc::{autoreleasepool, RcTestObject, ThreadTestData};
#[test]
fn test_adding() {
let mut array = NSMutableArray::new();
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));
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 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());
}
#[test]
fn test_remove() {
let mut array = NSMutableArray::new();
for _ in 0..4 {
array.push(RcTestObject::new());
}
let mut expected = ThreadTestData::current();
let _obj = array.remove(1);
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(array.len(), 3);
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);
}
#[test]
fn test_sort() {
let strings = vec![NSString::from_str("hello"), NSString::from_str("hi")];
let mut strings = NSMutableArray::from_vec(strings);
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");
});
}
}

View file

@ -0,0 +1,115 @@
use core::fmt;
use super::{NSAttributedString, NSCopying, NSMutableCopying, NSObject, NSString};
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A mutable string that has associated attributes.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsmutableattributedstring?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSMutableAttributedString;
unsafe impl ClassType for NSMutableAttributedString {
#[inherits(NSObject)]
type Super = NSAttributedString;
}
);
extern_methods!(
/// Creating mutable attributed strings.
unsafe impl NSMutableAttributedString {
/// Construct an empty mutable attributed string.
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
// TODO: new_with_attributes
#[doc(alias = "initWithString:")]
pub fn from_nsstring(string: &NSString) -> Id<Self, Owned> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithString: string]
}
}
#[doc(alias = "initWithAttributedString:")]
pub fn from_attributed_nsstring(attributed_string: &NSAttributedString) -> Id<Self, Owned> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithAttributedString: attributed_string]
}
}
}
/// Modifying the attributed string.
unsafe impl NSMutableAttributedString {
// TODO
// - mutableString
// - replaceCharactersInRange:withString:
// - setAttributes:range:
/// Replaces the entire attributed string.
#[doc(alias = "setAttributedString:")]
#[sel(setAttributedString:)]
pub fn replace(&mut self, attributed_string: &NSAttributedString);
}
);
impl DefaultId for NSMutableAttributedString {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
unsafe impl NSCopying for NSMutableAttributedString {
type Ownership = Shared;
type Output = NSAttributedString;
}
unsafe impl NSMutableCopying for NSMutableAttributedString {
type Output = NSMutableAttributedString;
}
impl alloc::borrow::ToOwned for NSMutableAttributedString {
type Owned = Id<NSMutableAttributedString, Owned>;
fn to_owned(&self) -> Self::Owned {
self.mutable_copy()
}
}
impl fmt::Debug for NSMutableAttributedString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use super::*;
#[test]
fn test_new() {
let s = NSAttributedString::new();
assert_eq!(&s.string().to_string(), "");
}
#[test]
fn test_copy() {
let s1 = NSMutableAttributedString::from_nsstring(&NSString::from_str("abc"));
let s2 = s1.copy();
assert_ne!(Id::as_ptr(&s1).cast(), Id::as_ptr(&s2));
assert!(s2.is_kind_of::<NSAttributedString>());
let s3 = s1.mutable_copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3));
assert!(s3.is_kind_of::<NSMutableAttributedString>());
}
}

View file

@ -0,0 +1,361 @@
#[cfg(feature = "block")]
use alloc::vec::Vec;
use core::ffi::c_void;
use core::fmt;
use core::ops::{Index, IndexMut, Range};
use core::slice::{self, SliceIndex};
use std::io;
use super::data::with_slice;
use super::{NSCopying, NSData, NSMutableCopying, NSObject, NSRange};
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A dynamic byte buffer in memory.
///
/// This is the Objective-C equivalent of a [`Vec`] containing [`u8`].
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsmutabledata?language=objc).
///
/// [`Vec`]: std::vec::Vec
#[derive(PartialEq, Eq, Hash)]
pub struct NSMutableData;
unsafe impl ClassType for NSMutableData {
#[inherits(NSObject)]
type Super = NSData;
}
);
extern_methods!(
/// Creation methods
unsafe impl NSMutableData {
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
pub fn with_bytes(bytes: &[u8]) -> Id<Self, Owned> {
unsafe { Id::from_shared(Id::cast(with_slice(Self::class(), bytes))) }
}
#[cfg(feature = "block")]
pub fn from_vec(bytes: Vec<u8>) -> Id<Self, Owned> {
unsafe { Id::from_shared(Id::cast(super::data::with_vec(Self::class(), bytes))) }
}
// TODO: Use malloc_buf/mbox and `initWithBytesNoCopy:...`?
#[doc(alias = "initWithData:")]
pub fn from_data(data: &NSData) -> Id<Self, Owned> {
// Not provided on NSData, one should just use NSData::copy or similar
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithData: data]
}
}
#[doc(alias = "initWithCapacity:")]
pub fn with_capacity(capacity: usize) -> Id<Self, Owned> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithCapacity: capacity]
}
}
}
/// Mutation methods
unsafe impl NSMutableData {
/// Expands with zeroes, or truncates the buffer.
#[doc(alias = "setLength:")]
#[sel(setLength:)]
pub fn set_len(&mut self, len: usize);
#[sel(mutableBytes)]
fn bytes_mut_raw(&mut self) -> *mut c_void;
#[doc(alias = "mutableBytes")]
pub fn bytes_mut(&mut self) -> &mut [u8] {
let ptr = self.bytes_mut_raw();
let ptr: *mut u8 = ptr.cast();
// The bytes pointer may be null for length zero
if ptr.is_null() {
&mut []
} else {
unsafe { slice::from_raw_parts_mut(ptr, self.len()) }
}
}
#[sel(appendBytes:length:)]
unsafe fn append_raw(&mut self, ptr: *const c_void, len: usize);
#[doc(alias = "appendBytes:length:")]
pub fn extend_from_slice(&mut self, bytes: &[u8]) {
let bytes_ptr: *const c_void = bytes.as_ptr().cast();
unsafe { self.append_raw(bytes_ptr, bytes.len()) }
}
pub fn push(&mut self, byte: u8) {
self.extend_from_slice(&[byte])
}
#[sel(replaceBytesInRange:withBytes:length:)]
unsafe fn replace_raw(&mut self, range: NSRange, ptr: *const c_void, len: usize);
#[doc(alias = "replaceBytesInRange:withBytes:length:")]
pub fn replace_range(&mut self, range: Range<usize>, bytes: &[u8]) {
let range = NSRange::from(range);
// No need to verify the length of the range here,
// `replaceBytesInRange:` just zero-fills if out of bounds.
let ptr: *const c_void = bytes.as_ptr().cast();
unsafe { self.replace_raw(range, ptr, bytes.len()) }
}
pub fn set_bytes(&mut self, bytes: &[u8]) {
let len = self.len();
self.replace_range(0..len, bytes);
}
}
);
unsafe impl NSCopying for NSMutableData {
type Ownership = Shared;
type Output = NSData;
}
unsafe impl NSMutableCopying for NSMutableData {
type Output = NSMutableData;
}
impl alloc::borrow::ToOwned for NSMutableData {
type Owned = Id<NSMutableData, Owned>;
fn to_owned(&self) -> Self::Owned {
self.mutable_copy()
}
}
impl AsRef<[u8]> for NSMutableData {
fn as_ref(&self) -> &[u8] {
self.bytes()
}
}
impl AsMut<[u8]> for NSMutableData {
fn as_mut(&mut self) -> &mut [u8] {
self.bytes_mut()
}
}
impl<I: SliceIndex<[u8]>> Index<I> for NSMutableData {
type Output = I::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output {
Index::index(self.bytes(), index)
}
}
impl<I: SliceIndex<[u8]>> IndexMut<I> for NSMutableData {
#[inline]
fn index_mut(&mut self, index: I) -> &mut Self::Output {
IndexMut::index_mut(self.bytes_mut(), index)
}
}
// impl FromIterator<u8> for Id<NSMutableData, Owned> {
// fn from_iter<T: IntoIterator<Item = u8>>(iter: T) -> Self {
// let iter = iter.into_iter();
// let (lower, _) = iter.size_hint();
// let data = Self::with_capacity(lower);
// for item in iter {
// data.push(item);
// }
// data
// }
// }
impl Extend<u8> for NSMutableData {
/// You should use [`extend_from_slice`] whenever possible, it is more
/// performant.
///
/// [`extend_from_slice`]: Self::extend_from_slice
fn extend<T: IntoIterator<Item = u8>>(&mut self, iter: T) {
let iterator = iter.into_iter();
iterator.for_each(move |item| self.push(item));
}
}
// Vec also has this impl
impl<'a> Extend<&'a u8> for NSMutableData {
fn extend<T: IntoIterator<Item = &'a u8>>(&mut self, iter: T) {
let iterator = iter.into_iter();
iterator.for_each(move |item| self.push(*item));
}
}
impl io::Write for NSMutableData {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.extend_from_slice(buf);
Ok(buf.len())
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
self.extend_from_slice(buf);
Ok(())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
impl DefaultId for NSMutableData {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl fmt::Debug for NSMutableData {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
impl<'a> IntoIterator for &'a NSMutableData {
type Item = &'a u8;
type IntoIter = core::slice::Iter<'a, u8>;
fn into_iter(self) -> Self::IntoIter {
self.bytes().iter()
}
}
impl<'a> IntoIterator for &'a mut NSMutableData {
type Item = &'a mut u8;
type IntoIter = core::slice::IterMut<'a, u8>;
fn into_iter(self) -> Self::IntoIter {
self.bytes_mut().iter_mut()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::Object;
#[test]
fn test_bytes_mut() {
let mut data = NSMutableData::with_bytes(&[7, 16]);
data.bytes_mut()[0] = 3;
assert_eq!(data.bytes(), [3, 16]);
}
#[test]
fn test_set_len() {
let mut data = NSMutableData::with_bytes(&[7, 16]);
data.set_len(4);
assert_eq!(data.len(), 4);
assert_eq!(data.bytes(), [7, 16, 0, 0]);
data.set_len(1);
assert_eq!(data.len(), 1);
assert_eq!(data.bytes(), [7]);
}
#[test]
fn test_append() {
let mut data = NSMutableData::with_bytes(&[7, 16]);
data.extend_from_slice(&[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_eq!(data.bytes(), [3, 7, 16]);
data.replace_range(1..2, &[52, 13]);
assert_eq!(data.bytes(), [3, 52, 13, 16]);
data.replace_range(2..4, &[6]);
assert_eq!(data.bytes(), [3, 52, 6]);
data.set_bytes(&[8, 17]);
assert_eq!(data.bytes(), [8, 17]);
}
#[test]
fn test_from_data() {
let data = NSData::with_bytes(&[1, 2]);
let mut_data = NSMutableData::from_data(&data);
assert_eq!(&*data, &**mut_data);
}
#[test]
fn test_with_capacity() {
let mut data = NSMutableData::with_capacity(5);
assert_eq!(data.bytes(), &[]);
data.extend_from_slice(&[1, 2, 3, 4, 5]);
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5]);
data.extend_from_slice(&[6, 7]);
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5, 6, 7]);
}
#[test]
fn test_extend() {
let mut data = NSMutableData::with_bytes(&[1, 2]);
data.extend(3..=5);
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5]);
data.extend(&*NSData::with_bytes(&[6, 7]));
assert_eq!(data.bytes(), &[1, 2, 3, 4, 5, 6, 7]);
}
#[test]
fn test_as_ref_borrow() {
use core::borrow::{Borrow, BorrowMut};
fn impls_borrow<T: AsRef<U> + Borrow<U> + ?Sized, U: ?Sized>(_: &T) {}
fn impls_borrow_mut<T: AsMut<U> + BorrowMut<U> + ?Sized, U: ?Sized>(_: &mut T) {}
let mut obj = NSMutableData::new();
impls_borrow::<Id<NSMutableData, Owned>, NSMutableData>(&obj);
impls_borrow_mut::<Id<NSMutableData, Owned>, NSMutableData>(&mut obj);
impls_borrow::<NSMutableData, NSMutableData>(&obj);
impls_borrow_mut::<NSMutableData, NSMutableData>(&mut obj);
impls_borrow::<NSMutableData, NSData>(&obj);
impls_borrow_mut::<NSMutableData, NSData>(&mut obj);
impls_borrow::<NSMutableData, NSObject>(&obj);
impls_borrow_mut::<NSMutableData, NSObject>(&mut obj);
impls_borrow::<NSMutableData, Object>(&obj);
impls_borrow_mut::<NSMutableData, Object>(&mut obj);
impls_borrow::<NSData, NSData>(&obj);
impls_borrow_mut::<NSData, NSData>(&mut obj);
impls_borrow::<NSData, NSObject>(&obj);
impls_borrow_mut::<NSData, NSObject>(&mut obj);
impls_borrow::<NSData, Object>(&obj);
impls_borrow_mut::<NSData, Object>(&mut obj);
fn impls_as_ref<T: AsRef<U> + ?Sized, U: ?Sized>(_: &T) {}
fn impls_as_mut<T: AsMut<U> + ?Sized, U: ?Sized>(_: &mut T) {}
impls_as_ref::<NSMutableData, [u8]>(&obj);
impls_as_mut::<NSMutableData, [u8]>(&mut obj);
impls_as_ref::<NSData, [u8]>(&obj);
let obj: &mut NSMutableData = &mut obj;
let _: &[u8] = obj.as_ref();
let _: &mut [u8] = obj.as_mut();
let obj: &mut NSData = obj;
let _: &[u8] = obj.as_ref();
}
}

View file

@ -0,0 +1,399 @@
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use core::ops::{Index, IndexMut};
use core::panic::{RefUnwindSafe, UnwindSafe};
use core::ptr;
use super::{NSArray, NSCopying, NSDictionary, NSFastEnumeration, NSObject};
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::{ClassType, __inner_extern_class, extern_methods, msg_send_id, Message};
__inner_extern_class!(
/// A mutable collection of objects associated with unique keys.
///
/// See the documentation for [`NSDictionary`] and/or [Apple's
/// documentation][apple-doc] for more information.
///
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsmutabledictionary?language=objc
#[derive(PartialEq, Eq, Hash)]
pub struct NSMutableDictionary<K: Message, V: Message> {
key: PhantomData<Id<K, Shared>>,
obj: PhantomData<Id<V, Owned>>,
}
unsafe impl<K: Message, V: Message> ClassType for NSMutableDictionary<K, V> {
#[inherits(NSObject)]
type Super = NSDictionary<K, V>;
}
);
// Same as `NSDictionary<K, V>`
unsafe impl<K: Message + Sync + Send, V: Message + Sync> Sync for NSMutableDictionary<K, V> {}
unsafe impl<K: Message + Sync + Send, V: Message + Send> Send for NSMutableDictionary<K, V> {}
// Same as `NSDictionary<K, V>`
impl<K: Message + UnwindSafe, V: Message + UnwindSafe> UnwindSafe for NSMutableDictionary<K, V> {}
impl<K: Message + RefUnwindSafe, V: Message + RefUnwindSafe> RefUnwindSafe
for NSMutableDictionary<K, V>
{
}
extern_methods!(
unsafe impl<K: Message, V: Message> NSMutableDictionary<K, V> {
/// Creates an empty [`NSMutableDictionary`].
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let dict = NSMutableDictionary::<NSString, NSObject>::new();
/// ```
pub fn new() -> Id<Self, Owned> {
// SAFETY:
// Mutable dictionaries are always unique, so it's safe to return
// `Id<Self, Owned>`
unsafe { msg_send_id![Self::class(), new] }
}
#[sel(setDictionary:)]
fn set_dictionary(&mut self, dict: &NSDictionary<K, V>);
/// Creates an [`NSMutableDictionary`] from a slice of keys and a
/// vector of values.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSNumber, NSObject};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let dict = NSMutableDictionary::from_keys_and_objects(
/// &[
/// &*NSNumber::new_i32(1),
/// &*NSNumber::new_i32(2),
/// &*NSNumber::new_i32(3),
/// ],
/// vec![NSObject::new(), NSObject::new(), NSObject::new()],
/// );
/// ```
pub fn from_keys_and_objects<T>(keys: &[&T], vals: Vec<Id<V, Owned>>) -> Id<Self, Owned>
where
T: NSCopying<Output = K>,
{
let mut dict = NSMutableDictionary::new();
dict.set_dictionary(&*NSDictionary::from_keys_and_objects(keys, vals));
dict
}
/// Returns a mutable reference to the value corresponding to the key.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// println!("{:?}", dict.get_mut(ns_string!("one")));
/// ```
#[doc(alias = "objectForKey:")]
#[sel(objectForKey:)]
pub fn get_mut(&mut self, key: &K) -> Option<&mut V>;
#[sel(getObjects:andKeys:)]
unsafe fn get_objects_and_keys(&self, objects: *mut &mut V, keys: *mut &K);
/// Returns a vector of mutable references to the values in the dictionary.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// for val in dict.values_mut() {
/// println!("{:?}", val);
/// }
/// ```
#[doc(alias = "getObjects:andKeys:")]
pub fn values_mut(&mut self) -> Vec<&mut V> {
let len = self.len();
let mut vals: Vec<&mut V> = Vec::with_capacity(len);
// SAFETY: `vals` is not null
unsafe {
self.get_objects_and_keys(vals.as_mut_ptr(), ptr::null_mut());
vals.set_len(len);
}
vals
}
#[sel(setObject:forKey:)]
fn set_object_for_key(&mut self, object: &V, key: &K);
/// Inserts a key-value pair into the dictionary.
///
/// If the dictionary did not have this key present, None is returned.
/// If the dictionary did have this key present, the value is updated,
/// and the old value is returned.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// ```
#[doc(alias = "setObject:forKey:")]
pub fn insert(&mut self, key: Id<K, Shared>, value: Id<V, Owned>) -> Option<Id<V, Owned>> {
// SAFETY:
// `obj` is a reference to a value in the dictionary so it's safe
// to cast it to a pointer and pass it to `Id::retain_autoreleased`
let obj = self.get(&*key).map(|obj| unsafe {
Id::retain_autoreleased(obj as *const V as *mut V).unwrap_unchecked()
});
self.set_object_for_key(&*value, &*key);
obj
}
#[sel(removeObjectForKey:)]
fn remove_object_for_key(&mut self, key: &K);
/// Removes a key from the dictionary, returning the value at the key
/// if the key was previously in the dictionary.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// dict.remove(ns_string!("one"));
/// assert!(dict.is_empty());
/// ```
#[doc(alias = "removeObjectForKey:")]
pub fn remove(&mut self, key: &K) -> Option<Id<V, Owned>> {
// SAFETY:
// `obj` is a reference to a value in the dictionary so it's safe
// to cast it to a pointer and pass it to `Id::retain_autoreleased`
let obj = self.get(key).map(|obj| unsafe {
Id::retain_autoreleased(obj as *const V as *mut V).unwrap_unchecked()
});
self.remove_object_for_key(key);
obj
}
/// Clears the dictionary, removing all key-value pairs.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// dict.clear();
/// assert!(dict.is_empty());
/// ```
#[doc(alias = "removeAllObjects")]
#[sel(removeAllObjects)]
pub fn clear(&mut self);
/// Returns an [`NSArray`] containing the dictionary's values,
/// consuming the dictionary.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableDictionary, NSObject, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut dict = NSMutableDictionary::new();
/// dict.insert(NSString::from_str("one"), NSObject::new());
/// let array = NSMutableDictionary::into_values_array(dict);
/// println!("{:?}", array);
/// ```
pub fn into_values_array(dict: Id<Self, Owned>) -> Id<NSArray<V, Owned>, Shared> {
unsafe { msg_send_id![&dict, allValues] }
}
}
);
unsafe impl<K: Message, V: Message> NSFastEnumeration for NSMutableDictionary<K, V> {
type Item = K;
}
impl<'a, K: Message, V: Message> Index<&'a K> for NSMutableDictionary<K, V> {
type Output = V;
fn index<'s>(&'s self, index: &'a K) -> &'s V {
self.get(index).unwrap()
}
}
impl<'a, K: Message, V: Message> IndexMut<&'a K> for NSMutableDictionary<K, V> {
fn index_mut<'s>(&'s mut self, index: &'a K) -> &'s mut V {
self.get_mut(index).unwrap()
}
}
impl<K: Message, V: Message> DefaultId for NSMutableDictionary<K, V> {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl<K: fmt::Debug + Message, V: fmt::Debug + Message> fmt::Debug for NSMutableDictionary<K, V> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
use crate::{
foundation::{NSNumber, NSString},
rc::{RcTestObject, ThreadTestData},
};
fn sample_dict() -> Id<NSMutableDictionary<NSNumber, NSObject>, Owned> {
NSMutableDictionary::from_keys_and_objects(
&[
&*NSNumber::new_i32(1),
&*NSNumber::new_i32(2),
&*NSNumber::new_i32(3),
],
vec![NSObject::new(), NSObject::new(), NSObject::new()],
)
}
#[test]
fn test_new() {
let dict = NSMutableDictionary::<NSString, NSObject>::new();
assert!(dict.is_empty());
}
#[test]
fn test_get_mut() {
let mut dict = sample_dict();
assert!(dict.get_mut(&NSNumber::new_i32(1)).is_some());
assert!(dict.get_mut(&NSNumber::new_i32(2)).is_some());
assert!(dict.get_mut(&NSNumber::new_i32(4)).is_none());
}
#[test]
fn test_values_mut() {
let mut dict = sample_dict();
let vec = dict.values_mut();
assert_eq!(vec.len(), 3);
}
#[test]
fn test_insert() {
let mut dict = NSMutableDictionary::new();
assert!(dict.insert(NSNumber::new_i32(1), NSObject::new()).is_none());
assert!(dict.insert(NSNumber::new_i32(2), NSObject::new()).is_none());
assert!(dict.insert(NSNumber::new_i32(3), NSObject::new()).is_none());
assert!(dict.insert(NSNumber::new_i32(1), NSObject::new()).is_some());
assert_eq!(dict.len(), 3);
}
#[test]
fn test_insert_retain_release() {
let mut dict = NSMutableDictionary::new();
dict.insert(NSNumber::new_i32(1), RcTestObject::new());
let mut expected = ThreadTestData::current();
let old = dict.insert(NSNumber::new_i32(1), RcTestObject::new());
expected.alloc += 1;
expected.init += 1;
expected.retain += 2;
expected.release += 2;
expected.assert_current();
drop(old);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_remove() {
let mut dict = sample_dict();
assert_eq!(dict.len(), 3);
assert!(dict.remove(&NSNumber::new_i32(1)).is_some());
assert!(dict.remove(&NSNumber::new_i32(2)).is_some());
assert!(dict.remove(&NSNumber::new_i32(1)).is_none());
assert!(dict.remove(&NSNumber::new_i32(4)).is_none());
assert_eq!(dict.len(), 1);
}
#[test]
fn test_clear() {
let mut dict = sample_dict();
assert_eq!(dict.len(), 3);
dict.clear();
assert!(dict.is_empty());
}
#[test]
fn test_remove_clear_release_dealloc() {
let mut dict = NSMutableDictionary::new();
for i in 0..4 {
dict.insert(NSNumber::new_i32(i), RcTestObject::new());
}
let mut expected = ThreadTestData::current();
let _obj = dict.remove(&NSNumber::new_i32(1));
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(dict.len(), 3);
let _obj = dict.remove(&NSNumber::new_i32(2));
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(dict.len(), 2);
dict.clear();
expected.release += 2;
expected.dealloc += 2;
expected.assert_current();
assert_eq!(dict.len(), 0);
}
#[test]
fn test_into_values_array() {
let dict = sample_dict();
let array = NSMutableDictionary::into_values_array(dict);
assert_eq!(array.len(), 3);
}
}

View file

@ -0,0 +1,368 @@
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use super::set::with_objects;
use super::{NSCopying, NSFastEnumeration, NSFastEnumerator, NSMutableCopying, NSObject, NSSet};
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send_id};
__inner_extern_class!(
/// A growable unordered collection of unique objects.
///
/// See the documentation for [`NSSet`] and/or [Apple's
/// documentation][apple-doc] for more information.
///
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsmutableset?language=objc
#[derive(PartialEq, Eq, Hash)]
pub struct NSMutableSet<T: Message, O: Ownership = Owned> {
p: PhantomData<*mut ()>,
}
unsafe impl<T: Message, O: Ownership> ClassType for NSMutableSet<T, O> {
#[inherits(NSObject)]
type Super = NSSet<T, O>;
}
);
// SAFETY: Same as NSSet<T, O>
unsafe impl<T: Message + Sync + Send> Sync for NSMutableSet<T, Shared> {}
unsafe impl<T: Message + Sync + Send> Send for NSMutableSet<T, Shared> {}
unsafe impl<T: Message + Sync> Sync for NSMutableSet<T, Owned> {}
unsafe impl<T: Message + Send> Send for NSMutableSet<T, Owned> {}
extern_methods!(
unsafe impl<T: Message, O: Ownership> NSMutableSet<T, O> {
/// Creates an empty [`NSMutableSet`].
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set = NSMutableSet::<NSString>::new();
/// ```
pub fn new() -> Id<Self, Owned> {
// SAFETY:
// Same as `NSSet::new`, except mutable sets are always unique.
unsafe { msg_send_id![Self::class(), new] }
}
/// Creates an [`NSMutableSet`] from a vector.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str).to_vec();
/// let set = NSMutableSet::from_vec(strs);
/// ```
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, Owned> {
// SAFETY:
// We always return `Id<NSMutableSet<T, O>, Owned>` because mutable
// sets are always unique.
unsafe { with_objects(Self::class(), vec.as_slice_ref()) }
}
/// Clears the set, removing all values.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut set = NSMutableSet::new();
/// set.insert(NSString::from_str("one"));
/// set.clear();
/// assert!(set.is_empty());
/// ```
#[doc(alias = "removeAllObjects")]
#[sel(removeAllObjects)]
pub fn clear(&mut self);
/// Returns a [`Vec`] containing the set's elements, consuming the set.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSMutableString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = vec![
/// NSMutableString::from_str("one"),
/// NSMutableString::from_str("two"),
/// NSMutableString::from_str("three"),
/// ];
/// let set = NSMutableSet::from_vec(strs);
/// let vec = NSMutableSet::into_vec(set);
/// assert_eq!(vec.len(), 3);
/// ```
pub fn into_vec(set: Id<Self, Owned>) -> Vec<Id<T, O>> {
set.into_iter()
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
.collect()
}
}
unsafe impl<T: Message> NSMutableSet<T, Shared> {
/// Creates an [`NSMutableSet`] from a slice.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSMutableSet::from_slice(&strs);
/// ```
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Owned> {
// SAFETY:
// Taking `&T` would not be sound, since the `&T` could come from
// an `Id<T, Owned>` that would now no longer be owned!
//
// We always return `Id<NSMutableSet<T, Shared>, Owned>` because
// the elements are shared and mutable sets are always unique.
unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
}
}
// We're explicit about `T` being `PartialEq` for these methods because the
// set compares the input value with elements in the set
// For comparison: Rust's HashSet requires similar methods to be `Hash` + `Eq`
unsafe impl<T: Message + PartialEq, O: Ownership> NSMutableSet<T, O> {
#[sel(addObject:)]
fn add_object(&mut self, value: &T);
/// Adds a value to the set. Returns whether the value was
/// newly inserted.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut set = NSMutableSet::new();
///
/// assert_eq!(set.insert(NSString::from_str("one")), true);
/// assert_eq!(set.insert(NSString::from_str("one")), false);
/// assert_eq!(set.len(), 1);
/// ```
#[doc(alias = "addObject:")]
pub fn insert(&mut self, value: Id<T, O>) -> bool {
// SAFETY:
// We take `Id<T, O>` instead of `&T` because `&T` could be a
// reference to an owned object which would cause us to have a copy
// of an owned object in our set. By taking `Id<T, O>`, we force the
// caller to transfer ownership of the value to us, making it safe
// to insert the owned object into the set.
let contains_value = self.contains(&value);
self.add_object(&*value);
!contains_value
}
#[sel(removeObject:)]
fn remove_object(&mut self, value: &T);
/// Removes a value from the set. Returns whether the value was present
/// in the set.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableSet, NSString};
/// use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let mut set = NSMutableSet::new();
///
/// set.insert(NSString::from_str("one"));
/// assert_eq!(set.remove(ns_string!("one")), true);
/// assert_eq!(set.remove(ns_string!("one")), false);
/// ```
#[doc(alias = "removeObject:")]
pub fn remove(&mut self, value: &T) -> bool {
let contains_value = self.contains(value);
self.remove_object(value);
contains_value
}
}
);
unsafe impl<T: Message> NSCopying for NSMutableSet<T, Shared> {
type Ownership = Shared;
type Output = NSSet<T, Shared>;
}
unsafe impl<T: Message> NSMutableCopying for NSMutableSet<T, Shared> {
type Output = NSMutableSet<T, Shared>;
}
impl<T: Message> alloc::borrow::ToOwned for NSMutableSet<T, Shared> {
type Owned = Id<NSMutableSet<T, Shared>, Owned>;
fn to_owned(&self) -> Self::Owned {
self.mutable_copy()
}
}
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSMutableSet<T, O> {
type Item = T;
}
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSMutableSet<T, O> {
type Item = &'a T;
type IntoIter = NSFastEnumerator<'a, NSMutableSet<T, O>>;
fn into_iter(self) -> Self::IntoIter {
self.iter_fast()
}
}
impl<T: Message + PartialEq, O: Ownership> Extend<Id<T, O>> for NSMutableSet<T, O> {
fn extend<I: IntoIterator<Item = Id<T, O>>>(&mut self, iter: I) {
for item in iter {
self.insert(item);
}
}
}
impl<T: Message, O: Ownership> DefaultId for NSMutableSet<T, O> {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSMutableSet<T, O> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use alloc::vec;
use super::*;
use crate::foundation::{NSMutableString, NSString};
use crate::ns_string;
use crate::rc::{RcTestObject, ThreadTestData};
#[test]
fn test_insert() {
let mut set = NSMutableSet::new();
assert!(set.is_empty());
assert!(set.insert(NSString::from_str("one")));
assert!(!set.insert(NSString::from_str("one")));
assert!(set.insert(NSString::from_str("two")));
}
#[test]
fn test_remove() {
let strs = ["one", "two", "three"].map(NSString::from_str);
let mut set = NSMutableSet::from_slice(&strs);
assert!(set.remove(ns_string!("one")));
assert!(!set.remove(ns_string!("one")));
}
#[test]
fn test_clear() {
let strs = ["one", "two", "three"].map(NSString::from_str);
let mut set = NSMutableSet::from_slice(&strs);
assert_eq!(set.len(), 3);
set.clear();
assert!(set.is_empty());
}
#[test]
fn test_into_vec() {
let strs = vec![
NSMutableString::from_str("one"),
NSMutableString::from_str("two"),
NSMutableString::from_str("three"),
];
let set = NSMutableSet::from_vec(strs);
let mut vec = NSMutableSet::into_vec(set);
for str in vec.iter_mut() {
str.push_nsstring(ns_string!(" times zero is zero"));
}
assert_eq!(vec.len(), 3);
let suffix = ns_string!("zero");
assert!(vec.iter().all(|str| str.has_suffix(suffix)));
}
#[test]
fn test_extend() {
let mut set = NSMutableSet::new();
assert!(set.is_empty());
set.extend(["one", "two", "three"].map(NSString::from_str));
assert_eq!(set.len(), 3);
}
#[test]
fn test_mutable_copy() {
let set1 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
let mut set2 = set1.mutable_copy();
set2.insert(NSString::from_str("four"));
assert!(set1.is_subset(&set2));
assert_ne!(set1.mutable_copy(), set2);
}
#[test]
fn test_insert_retain_release() {
let mut set = NSMutableSet::new();
let obj1 = RcTestObject::new();
let obj2 = RcTestObject::new();
let mut expected = ThreadTestData::current();
set.insert(obj1);
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(set.len(), 1);
assert_eq!(set.get_any(), set.get_any());
set.insert(obj2);
expected.retain += 1;
expected.release += 1;
expected.assert_current();
assert_eq!(set.len(), 2);
}
#[test]
fn test_clear_release_dealloc() {
let mut set = NSMutableSet::new();
for _ in 0..4 {
set.insert(RcTestObject::new());
}
let mut expected = ThreadTestData::current();
set.clear();
expected.release += 4;
expected.dealloc += 4;
expected.assert_current();
assert_eq!(set.len(), 0);
}
}

View file

@ -0,0 +1,232 @@
use core::cmp;
use core::fmt;
use core::ops::AddAssign;
use core::str;
use super::{NSCopying, NSMutableCopying, NSObject, NSString};
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A dynamic plain-text Unicode string object.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsmutablestring?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSMutableString;
unsafe impl ClassType for NSMutableString {
#[inherits(NSObject)]
type Super = NSString;
}
);
extern_methods!(
/// Creating mutable strings.
unsafe impl NSMutableString {
/// Construct an empty [`NSMutableString`].
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
/// Creates a new [`NSMutableString`] by copying the given string slice.
#[doc(alias = "initWithBytes:length:encoding:")]
#[allow(clippy::should_implement_trait)] // Not really sure of a better name
pub fn from_str(string: &str) -> Id<Self, Owned> {
unsafe {
let obj = super::string::from_str(Self::class(), string);
Id::new(obj.cast()).unwrap()
}
}
/// Creates a new [`NSMutableString`] from the given [`NSString`].
#[doc(alias = "initWithString:")]
pub fn from_nsstring(string: &NSString) -> Id<Self, Owned> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithString: string]
}
}
#[doc(alias = "initWithCapacity:")]
pub fn with_capacity(capacity: usize) -> Id<Self, Owned> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithCapacity: capacity]
}
}
}
/// Mutating strings.
unsafe impl NSMutableString {
/// Appends the given [`NSString`] onto the end of this.
#[doc(alias = "appendString:")]
// SAFETY: The string is not nil
#[sel(appendString:)]
pub fn push_nsstring(&mut self, nsstring: &NSString);
/// Replaces the entire string.
#[doc(alias = "setString:")]
// SAFETY: The string is not nil
#[sel(setString:)]
pub fn replace(&mut self, nsstring: &NSString);
// TODO:
// - deleteCharactersInRange:
// - replaceCharactersInRange:withString:
// - insertString:atIndex:
// Figure out how these work on character boundaries
}
);
impl DefaultId for NSMutableString {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
unsafe impl NSCopying for NSMutableString {
type Ownership = Shared;
type Output = NSString;
}
unsafe impl NSMutableCopying for NSMutableString {
type Output = NSMutableString;
}
impl alloc::borrow::ToOwned for NSMutableString {
type Owned = Id<NSMutableString, Owned>;
fn to_owned(&self) -> Self::Owned {
self.mutable_copy()
}
}
impl AddAssign<&NSString> for NSMutableString {
#[inline]
fn add_assign(&mut self, other: &NSString) {
self.push_nsstring(other)
}
}
impl PartialEq<NSString> for NSMutableString {
#[inline]
fn eq(&self, other: &NSString) -> bool {
PartialEq::eq(&**self, other)
}
}
impl PartialEq<NSMutableString> for NSString {
#[inline]
fn eq(&self, other: &NSMutableString) -> bool {
PartialEq::eq(self, &**other)
}
}
impl PartialOrd for NSMutableString {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
PartialOrd::partial_cmp(&**self, &**other)
}
}
impl PartialOrd<NSString> for NSMutableString {
#[inline]
fn partial_cmp(&self, other: &NSString) -> Option<cmp::Ordering> {
PartialOrd::partial_cmp(&**self, other)
}
}
impl PartialOrd<NSMutableString> for NSString {
#[inline]
fn partial_cmp(&self, other: &NSMutableString) -> Option<cmp::Ordering> {
PartialOrd::partial_cmp(self, &**other)
}
}
impl Ord for NSMutableString {
#[inline]
fn cmp(&self, other: &Self) -> cmp::Ordering {
Ord::cmp(&**self, &**other)
}
}
impl fmt::Write for NSMutableString {
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
let nsstring = NSString::from_str(s);
self.push_nsstring(&nsstring);
Ok(())
}
}
impl fmt::Display for NSMutableString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&**self, f)
}
}
impl fmt::Debug for NSMutableString {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&**self, f)
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::string::ToString;
use super::*;
#[test]
fn display_debug() {
let s = NSMutableString::from_str("test\"123");
assert_eq!(format!("{}", s), "test\"123");
assert_eq!(format!("{:?}", s), r#""test\"123""#);
}
#[test]
fn test_from_nsstring() {
let s = NSString::from_str("abc");
let s = NSMutableString::from_nsstring(&s);
assert_eq!(&s.to_string(), "abc");
}
#[test]
fn test_push_nsstring() {
let mut s = NSMutableString::from_str("abc");
s.push_nsstring(&NSString::from_str("def"));
*s += &NSString::from_str("ghi");
assert_eq!(&s.to_string(), "abcdefghi");
}
#[test]
fn test_replace() {
let mut s = NSMutableString::from_str("abc");
s.replace(&NSString::from_str("def"));
assert_eq!(&s.to_string(), "def");
}
#[test]
fn test_with_capacity() {
let mut s = NSMutableString::with_capacity(3);
*s += &NSString::from_str("abc");
*s += &NSString::from_str("def");
assert_eq!(&s.to_string(), "abcdef");
}
#[test]
fn test_copy() {
let s1 = NSMutableString::from_str("abc");
let s2 = s1.copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s2).cast());
assert!(s2.is_kind_of::<NSString>());
let s3 = s1.mutable_copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3));
assert!(s3.is_kind_of::<NSMutableString>());
}
}

View file

@ -0,0 +1,414 @@
use core::cmp::Ordering;
use core::fmt;
use core::hash;
use core::panic::{RefUnwindSafe, UnwindSafe};
use std::os::raw::{
c_char, c_double, c_float, c_int, c_longlong, c_short, c_uchar, c_uint, c_ulonglong, c_ushort,
};
use super::{
CGFloat, NSComparisonResult, NSCopying, NSInteger, NSObject, NSString, NSUInteger, NSValue,
};
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType, Encoding};
extern_class!(
/// An object wrapper for primitive scalars.
///
/// This is the Objective-C equivalant of a Rust enum containing the
/// common scalar types `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`,
/// `u64`, `f32`, `f64` and the two C types `c_long` and `c_ulong`.
///
/// All accessor methods are safe, though they may return unexpected
/// results if the number was not created from said type. Consult [Apple's
/// documentation][apple-doc] for details.
///
/// Note that due to limitations in Objective-C type encodings, it is not
/// possible to distinguish between an `NSNumber` created from [`bool`],
/// and one created from an [`i8`]/[`u8`]. You should use the getter
/// methods that fit your use-case instead!
///
/// This does not implement [`Eq`] nor [`Ord`], since it may contain a
/// floating point value. Beware that the implementation of [`PartialEq`]
/// and [`PartialOrd`] does not properly handle NaNs either. Compare
/// [`NSNumber::encoding`] with [`Encoding::Float`] or
/// [`Encoding::Double`], and use [`NSNumber::as_f32`] or
/// [`NSNumber::as_f64`] to get the desired floating point value directly.
///
/// See [Apple's documentation][apple-doc] for more information.
///
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsnumber?language=objc
pub struct NSNumber;
unsafe impl ClassType for NSNumber {
#[inherits(NSObject)]
type Super = NSValue;
}
);
// SAFETY: `NSNumber` is just a wrapper around an integer/float/bool, and it
// is immutable.
unsafe impl Sync for NSNumber {}
unsafe impl Send for NSNumber {}
impl UnwindSafe for NSNumber {}
impl RefUnwindSafe for NSNumber {}
macro_rules! def_new_fn {
{$(
$(#[$($m:meta)*])*
($fn_name:ident($fn_inp:ty); $method_name:ident: $method_inp:ty),
)*} => {$(
$(#[$($m)*])*
pub fn $fn_name(val: $fn_inp) -> Id<Self, Shared> {
let val = val as $method_inp;
unsafe {
msg_send_id![Self::class(), $method_name: val]
}
}
)*}
}
/// Creation methods.
impl NSNumber {
def_new_fn! {
(new_bool(bool); numberWithBool: bool),
(new_i8(i8); numberWithChar: c_char),
(new_u8(u8); numberWithUnsignedChar: c_uchar),
(new_i16(i16); numberWithShort: c_short),
(new_u16(u16); numberWithUnsignedShort: c_ushort),
(new_i32(i32); numberWithInt: c_int),
(new_u32(u32); numberWithUnsignedInt: c_uint),
(new_i64(i64); numberWithLongLong: c_longlong),
(new_u64(u64); numberWithUnsignedLongLong: c_ulonglong),
(new_isize(isize); numberWithInteger: NSInteger),
(new_usize(usize); numberWithUnsignedInteger: NSUInteger),
(new_f32(f32); numberWithFloat: c_float),
(new_f64(f64); numberWithDouble: c_double),
}
#[inline]
pub fn new_cgfloat(val: CGFloat) -> Id<Self, Shared> {
#[cfg(target_pointer_width = "64")]
{
Self::new_f64(val)
}
#[cfg(not(target_pointer_width = "64"))]
{
Self::new_f32(val)
}
}
}
macro_rules! def_get_fn {
{$(
$(#[$($m:meta)*])*
($fn_name:ident -> $fn_ret:ty; $method_name:ident -> $method_ret:ty),
)*} => {$(
$(#[$($m)*])*
pub fn $fn_name(&self) -> $fn_ret {
let ret: $method_ret = unsafe { msg_send![self, $method_name] };
ret as $fn_ret
}
)*}
}
/// Getter methods.
impl NSNumber {
def_get_fn! {
(as_bool -> bool; boolValue -> bool),
(as_i8 -> i8; charValue -> c_char),
(as_u8 -> u8; unsignedCharValue -> c_uchar),
(as_i16 -> i16; shortValue -> c_short),
(as_u16 -> u16; unsignedShortValue -> c_ushort),
(as_i32 -> i32; intValue -> c_int),
(as_u32 -> u32; unsignedIntValue -> c_uint),
// TODO: Getter methods for `long` and `unsigned long`
(as_i64 -> i64; longLongValue -> c_longlong),
(as_u64 -> u64; unsignedLongLongValue -> c_ulonglong),
(as_isize -> isize; integerValue -> NSInteger),
(as_usize -> usize; unsignedIntegerValue -> NSUInteger),
(as_f32 -> f32; floatValue -> c_float),
(as_f64 -> f64; doubleValue -> c_double),
}
#[inline]
pub fn as_cgfloat(&self) -> CGFloat {
#[cfg(target_pointer_width = "64")]
{
self.as_f64()
}
#[cfg(not(target_pointer_width = "64"))]
{
self.as_f32()
}
}
/// The Objective-C encoding of this `NSNumber`.
///
/// This is guaranteed to return one of:
/// - [`Encoding::Char`]
/// - [`Encoding::UChar`]
/// - [`Encoding::Short`]
/// - [`Encoding::UShort`]
/// - [`Encoding::Int`]
/// - [`Encoding::UInt`]
/// - [`Encoding::Long`]
/// - [`Encoding::ULong`]
/// - [`Encoding::LongLong`]
/// - [`Encoding::ULongLong`]
/// - [`Encoding::Float`]
/// - [`Encoding::Double`]
///
///
/// # Examples
///
/// Convert an `NSNumber` to/from an enumeration describing the different
/// number properties.
///
/// ```
/// use objc2::Encoding;
/// use objc2::foundation::NSNumber;
/// use objc2::rc::{Id, Shared};
///
/// // Note: `bool` would convert to either `Signed` or `Unsigned`,
/// // depending on platform
/// #[derive(Copy, Clone)]
/// pub enum Number {
/// Signed(i64),
/// Unsigned(u64),
/// Floating(f64),
/// }
///
/// impl Number {
/// fn into_nsnumber(self) -> Id<NSNumber, Shared> {
/// match self {
/// Self::Signed(val) => NSNumber::new_i64(val),
/// Self::Unsigned(val) => NSNumber::new_u64(val),
/// Self::Floating(val) => NSNumber::new_f64(val),
/// }
/// }
/// }
///
/// impl From<&NSNumber> for Number {
/// fn from(n: &NSNumber) -> Self {
/// match n.encoding() {
/// Encoding::Char
/// | Encoding::Short
/// | Encoding::Int
/// | Encoding::Long
/// | Encoding::LongLong => Self::Signed(n.as_i64()),
/// Encoding::UChar
/// | Encoding::UShort
/// | Encoding::UInt
/// | Encoding::ULong
/// | Encoding::ULongLong => Self::Unsigned(n.as_u64()),
/// Encoding::Float
/// | Encoding::Double => Self::Floating(n.as_f64()),
/// _ => unreachable!(),
/// }
/// }
/// }
/// ```
pub fn encoding(&self) -> Encoding {
// Use NSValue::encoding
let enc = (**self)
.encoding()
.expect("NSNumber must have an encoding!");
// Guaranteed under "Subclassing Notes"
// <https://developer.apple.com/documentation/foundation/nsnumber?language=objc#1776615>
match enc {
"c" => Encoding::Char,
"C" => Encoding::UChar,
"s" => Encoding::Short,
"S" => Encoding::UShort,
"i" => Encoding::Int,
"I" => Encoding::UInt,
"l" => Encoding::Long,
"L" => Encoding::ULong,
"q" => Encoding::LongLong,
"Q" => Encoding::ULongLong,
"f" => Encoding::Float,
"d" => Encoding::Double,
_ => unreachable!("invalid encoding for NSNumber"),
}
}
}
extern_methods!(
unsafe impl NSNumber {
#[sel(compare:)]
fn compare(&self, other: &Self) -> NSComparisonResult;
#[sel(isEqualToNumber:)]
fn is_equal_to_number(&self, other: &Self) -> bool;
fn string(&self) -> Id<NSString, Shared> {
unsafe { msg_send_id![self, stringValue] }
}
}
);
unsafe impl NSCopying for NSNumber {
type Ownership = Shared;
type Output = NSNumber;
}
impl alloc::borrow::ToOwned for NSNumber {
type Owned = Id<NSNumber, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
/// Beware: This uses the Objective-C method "isEqualToNumber:", which has
/// different floating point NaN semantics than Rust!
impl PartialEq for NSNumber {
#[doc(alias = "isEqualToNumber:")]
fn eq(&self, other: &Self) -> bool {
// Use isEqualToNumber: instaed of isEqual: since it is faster
self.is_equal_to_number(other)
}
}
impl hash::Hash for NSNumber {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// Delegate to NSObject
(***self).hash(state)
}
}
/// Beware: This uses the Objective-C method "compare:", which has different
/// floating point NaN semantics than Rust!
impl PartialOrd for NSNumber {
#[doc(alias = "compare:")]
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
// Use Objective-C semantics for comparison
Some(self.compare(other).into())
}
}
impl fmt::Display for NSNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.string(), f)
}
}
impl fmt::Debug for NSNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Delegate to -[NSObject description]
// (happens to return the same as -[NSNumber stringValue])
fmt::Debug::fmt(&***self, f)
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::*;
#[test]
fn basic() {
let val = NSNumber::new_u32(13);
assert_eq!(val.as_u32(), 13);
}
#[test]
fn roundtrip() {
assert!(NSNumber::new_bool(true).as_bool());
assert!(!NSNumber::new_bool(false).as_bool());
fn assert_roundtrip_signed(val: i64) {
assert_eq!(NSNumber::new_i8(val as i8).as_i8(), val as i8);
assert_eq!(NSNumber::new_i16(val as i16).as_i16(), val as i16);
assert_eq!(NSNumber::new_i32(val as i32).as_i32(), val as i32);
assert_eq!(NSNumber::new_i64(val).as_i64(), val);
assert_eq!(NSNumber::new_isize(val as isize).as_isize(), val as isize);
}
assert_roundtrip_signed(i64::MIN);
assert_roundtrip_signed(i32::MIN as i64);
assert_roundtrip_signed(i16::MIN as i64);
assert_roundtrip_signed(i8::MIN as i64);
assert_roundtrip_signed(-1);
assert_roundtrip_signed(0);
assert_roundtrip_signed(1);
assert_roundtrip_signed(i8::MAX as i64);
assert_roundtrip_signed(i16::MAX as i64);
assert_roundtrip_signed(i32::MAX as i64);
assert_roundtrip_signed(i64::MAX);
fn assert_roundtrip_unsigned(val: u64) {
assert_eq!(NSNumber::new_u8(val as u8).as_u8(), val as u8);
assert_eq!(NSNumber::new_u16(val as u16).as_u16(), val as u16);
assert_eq!(NSNumber::new_u32(val as u32).as_u32(), val as u32);
assert_eq!(NSNumber::new_u64(val).as_u64(), val);
assert_eq!(NSNumber::new_usize(val as usize).as_usize(), val as usize);
}
assert_roundtrip_unsigned(0);
assert_roundtrip_unsigned(1);
assert_roundtrip_unsigned(u8::MAX as u64);
assert_roundtrip_unsigned(u16::MAX as u64);
assert_roundtrip_unsigned(u32::MAX as u64);
assert_roundtrip_unsigned(u64::MAX);
fn assert_roundtrip_float(val: f64) {
assert_eq!(NSNumber::new_f32(val as f32).as_f32(), val as f32);
assert_eq!(NSNumber::new_f64(val).as_f64(), val);
}
assert_roundtrip_float(0.0);
assert_roundtrip_float(-1.0);
assert_roundtrip_float(1.0);
assert_roundtrip_float(f64::INFINITY);
assert_roundtrip_float(-f64::INFINITY);
assert_roundtrip_float(f64::MAX);
assert_roundtrip_float(f64::MIN);
assert_roundtrip_float(f64::MIN_POSITIVE);
assert!(NSNumber::new_f32(f32::NAN).as_f32().is_nan());
assert!(NSNumber::new_f64(f64::NAN).as_f64().is_nan());
assert!(NSNumber::new_f32(-f32::NAN).as_f32().is_nan());
assert!(NSNumber::new_f64(-f64::NAN).as_f64().is_nan());
}
#[test]
fn cast_between_types() {
assert_eq!(NSNumber::new_bool(true).as_i8(), 1);
assert_eq!(NSNumber::new_i32(i32::MAX).as_u32(), i32::MAX as u32);
assert_eq!(NSNumber::new_f32(1.0).as_u32(), 1);
assert_eq!(NSNumber::new_f32(1.0).as_u32(), 1);
}
#[test]
fn equality() {
let val1 = NSNumber::new_u32(123);
let val2 = NSNumber::new_u32(123);
let val3 = NSNumber::new_u8(123);
assert_eq!(val1, val1);
assert_eq!(val1, val2);
assert_eq!(val1, val3);
let val4 = NSNumber::new_u32(456);
assert_ne!(val1, val4);
}
#[test]
fn display_debug() {
fn assert_display_debug<T: fmt::Debug + fmt::Display>(val: T, expected: &str) {
// The two impls for these happen to be the same
assert_eq!(format!("{}", val), expected);
assert_eq!(format!("{:?}", val), expected);
}
assert_display_debug(NSNumber::new_u8(171), "171");
assert_display_debug(NSNumber::new_i8(-12), "-12");
assert_display_debug(NSNumber::new_u32(0xdeadbeef), "3735928559");
assert_display_debug(NSNumber::new_f32(1.1), "1.1");
assert_display_debug(NSNumber::new_f32(1.0), "1");
assert_display_debug(NSNumber::new_bool(true), "1");
assert_display_debug(NSNumber::new_bool(false), "0");
}
}

View file

@ -0,0 +1,205 @@
use core::fmt;
use core::hash;
use super::NSString;
use crate::rc::{DefaultId, Id, Owned, Shared};
use crate::runtime::{Class, Object};
use crate::{ClassType, __inner_extern_class, class, extern_methods, msg_send_id};
__inner_extern_class! {
@__inner
pub struct NSObject () {}
unsafe impl () for NSObject {
INHERITS = [Object];
}
}
unsafe impl ClassType for NSObject {
type Super = Object;
const NAME: &'static str = "NSObject";
#[inline]
fn class() -> &'static Class {
class!(NSObject)
}
fn as_super(&self) -> &Self::Super {
&self.__inner
}
fn as_super_mut(&mut self) -> &mut Self::Super {
&mut self.__inner
}
}
extern_methods!(
unsafe impl NSObject {
pub fn new() -> Id<Self, Owned> {
unsafe { msg_send_id![Self::class(), new] }
}
#[sel(isKindOfClass:)]
fn is_kind_of_inner(&self, cls: &Class) -> bool;
#[sel(isEqual:)]
fn is_equal(&self, other: &Self) -> bool;
#[sel(hash)]
fn hash_code(&self) -> usize;
/// Check if the object is an instance of the class, or one of it's
/// subclasses.
///
/// See [Apple's documentation][apple-doc] for more details on what you
/// may (and what you may not) do with this information.
///
/// [apple-doc]: https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418511-iskindofclass
#[doc(alias = "isKindOfClass:")]
pub fn is_kind_of<T: ClassType>(&self) -> bool {
self.is_kind_of_inner(T::class())
}
// Note: We don't provide a method to convert `NSObject` to `T` based on
// `is_kind_of`, since that is not possible to do in general!
//
// For example, something may have a return type of `NSString`, while
// behind the scenes they really return `NSMutableString` and expect it to
// not be modified.
}
);
/// Objective-C equality has approximately the same semantics as Rust
/// equality (although less aptly specified).
///
/// At the very least, equality is _expected_ to be symmetric and
/// transitive, and that's about the best we can do.
///
/// See also <https://nshipster.com/equality/>
impl PartialEq for NSObject {
#[inline]
#[doc(alias = "isEqual:")]
fn eq(&self, other: &Self) -> bool {
self.is_equal(other)
}
}
/// Most types' equality is reflexive.
impl Eq for NSObject {}
/// Hashing in Objective-C has the exact same requirement as in Rust:
///
/// > If two objects are equal (as determined by the isEqual: method),
/// > they must have the same hash value.
///
/// See <https://developer.apple.com/documentation/objectivec/1418956-nsobject/1418859-hash>
impl hash::Hash for NSObject {
#[inline]
fn hash<H: hash::Hasher>(&self, state: &mut H) {
self.hash_code().hash(state);
}
}
impl fmt::Debug for NSObject {
#[doc(alias = "description")]
#[doc(alias = "debugDescription")]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Get description
let description: Option<Id<NSString, Shared>> = unsafe { msg_send_id![self, description] };
match description {
// Attempt to format with description
Some(description) => fmt::Display::fmt(&description, f),
// If description was `NULL`, use `Object`'s `Debug` impl instead
None => {
let obj: &Object = self;
fmt::Debug::fmt(obj, f)
}
}
}
}
impl DefaultId for NSObject {
type Ownership = Owned;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
#[test]
fn test_deref() {
let mut obj: Id<NSObject, Owned> = NSObject::new();
let _: &NSObject = &obj;
let _: &mut NSObject = &mut obj;
let _: &Object = &obj;
let _: &mut Object = &mut obj;
}
#[test]
fn test_as_ref_borrow() {
use core::borrow::{Borrow, BorrowMut};
fn impls_as_ref<T: AsRef<U> + Borrow<U> + ?Sized, U: ?Sized>(_: &T) {}
fn impls_as_mut<T: AsMut<U> + BorrowMut<U> + ?Sized, U: ?Sized>(_: &mut T) {}
let mut obj = NSObject::new();
impls_as_ref::<Id<NSObject, Owned>, NSObject>(&obj);
impls_as_mut::<Id<NSObject, Owned>, NSObject>(&mut obj);
impls_as_ref::<NSObject, NSObject>(&obj);
impls_as_mut::<NSObject, NSObject>(&mut obj);
impls_as_ref::<NSObject, Object>(&obj);
impls_as_mut::<NSObject, Object>(&mut obj);
}
#[test]
fn test_equality() {
let obj1 = NSObject::new();
assert_eq!(obj1, obj1);
let obj2 = NSObject::new();
assert_ne!(obj1, obj2);
}
#[test]
fn test_hash() {
use core::hash::Hasher;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
let obj1 = NSObject::new();
let mut hashstate1 = DefaultHasher::new();
let mut hashstate2 = DefaultHasher::new();
obj1.hash(&mut hashstate1);
obj1.hash(&mut hashstate2);
assert_eq!(hashstate1.finish(), hashstate2.finish());
let obj2 = NSObject::new();
let mut hashstate2 = DefaultHasher::new();
obj2.hash(&mut hashstate2);
assert_ne!(hashstate1.finish(), hashstate2.finish());
}
#[test]
fn test_debug() {
let obj = NSObject::new();
let expected = format!("<NSObject: {:p}>", &*obj);
assert_eq!(format!("{:?}", obj), expected);
}
#[test]
fn test_is_kind_of() {
let obj = NSObject::new();
assert!(obj.is_kind_of::<NSObject>());
assert!(!obj.is_kind_of::<NSString>());
}
}

View file

@ -0,0 +1,65 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSObject, NSString};
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A collection of information about the current process.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsprocessinfo?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSProcessInfo;
unsafe impl ClassType for NSProcessInfo {
type Super = NSObject;
}
);
// SAFETY: The documentation explicitly states:
// > NSProcessInfo is thread-safe in macOS 10.7 and later.
unsafe impl Send for NSProcessInfo {}
unsafe impl Sync for NSProcessInfo {}
impl UnwindSafe for NSProcessInfo {}
impl RefUnwindSafe for NSProcessInfo {}
extern_methods!(
unsafe impl NSProcessInfo {
pub fn process_info() -> Id<NSProcessInfo, Shared> {
unsafe { msg_send_id![Self::class(), processInfo] }
}
pub fn process_name(&self) -> Id<NSString, Shared> {
unsafe { msg_send_id![self, processName] }
}
// TODO: This contains a lot more important functionality!
}
);
impl fmt::Debug for NSProcessInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NSProcessInfo")
.field("process_name", &self.process_name())
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::*;
#[test]
fn test_debug() {
let info = NSProcessInfo::process_info();
let expected = format!(
"NSProcessInfo {{ process_name: {:?}, .. }}",
info.process_name()
);
assert_eq!(format!("{:?}", info), expected);
}
}

View file

@ -0,0 +1,183 @@
use core::ops::Range;
use super::NSUInteger;
use crate::{Encode, Encoding, RefEncode};
/// TODO.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsrange?language=objc).
#[repr(C)]
// PartialEq is same as NSEqualRanges
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct NSRange {
/// The lower bound of the range (inclusive).
pub location: NSUInteger,
/// The number of items in the range, starting from `location`.
pub length: NSUInteger,
}
impl NSRange {
/// Create a new range with the given values.
///
/// # Examples
///
/// ```
/// use objc2::foundation::NSRange;
/// assert_eq!(NSRange::new(3, 2), NSRange::from(3..5));
/// ```
#[inline]
#[doc(alias = "NSMakeRange")]
pub const fn new(location: usize, length: usize) -> Self {
// Equivalent to NSMakeRange
Self { location, length }
}
/// Returns `true` if the range contains no items.
///
/// # Examples
///
/// ```
/// use objc2::foundation::NSRange;
///
/// assert!(!NSRange::from(3..5).is_empty());
/// assert!( NSRange::from(3..3).is_empty());
/// ```
#[inline]
pub fn is_empty(&self) -> bool {
self.length == 0
}
/// Returns `true` if the index is within the range.
///
/// # Examples
///
/// ```
/// use objc2::foundation::NSRange;
///
/// assert!(!NSRange::from(3..5).contains(2));
/// assert!( NSRange::from(3..5).contains(3));
/// assert!( NSRange::from(3..5).contains(4));
/// assert!(!NSRange::from(3..5).contains(5));
///
/// assert!(!NSRange::from(3..3).contains(3));
/// ```
#[inline]
#[doc(alias = "NSLocationInRange")]
pub fn contains(&self, index: usize) -> bool {
// Same as NSLocationInRange
if let Some(len) = index.checked_sub(self.location) {
len < self.length
} else {
// index < self.location
false
}
}
/// Returns the upper bound of the range (exclusive).
#[inline]
#[doc(alias = "NSMaxRange")]
pub fn end(&self) -> usize {
self.location
.checked_add(self.length)
.expect("NSRange too large")
}
// TODO: https://developer.apple.com/documentation/foundation/1408420-nsrangefromstring
// TODO: NSUnionRange
// TODO: NSIntersectionRange
}
// Sadly, we can't do this:
// impl RangeBounds<usize> 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<Range<usize>> for NSRange {
fn from(range: Range<usize>) -> Self {
let length = range
.end
.checked_sub(range.start)
.expect("Range end < start");
Self {
location: range.start,
length,
}
}
}
impl From<NSRange> for Range<usize> {
#[inline]
fn from(nsrange: NSRange) -> Self {
Self {
start: nsrange.location,
end: nsrange.end(),
}
}
}
unsafe impl Encode for NSRange {
const ENCODING: Encoding = Encoding::Struct("_NSRange", &[usize::ENCODING, usize::ENCODING]);
}
unsafe impl RefEncode for NSRange {
const ENCODING_REF: Encoding = Encoding::Pointer(&Self::ENCODING);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_range() {
let cases: &[(Range<usize>, NSRange)] = &[
(0..0, NSRange::new(0, 0)),
(0..10, NSRange::new(0, 10)),
(10..10, NSRange::new(10, 0)),
(10..20, NSRange::new(10, 10)),
];
for (range, expected) in cases {
assert_eq!(NSRange::from(range.clone()), *expected);
}
}
#[test]
#[should_panic = "Range end < start"]
#[allow(clippy::reversed_empty_ranges)]
fn test_from_range_inverted() {
let _ = NSRange::from(10..0);
}
#[test]
fn test_contains() {
let range = NSRange::from(10..20);
assert!(!range.contains(0));
assert!(!range.contains(9));
assert!(range.contains(10));
assert!(range.contains(11));
assert!(!range.contains(20));
assert!(!range.contains(21));
}
#[test]
fn test_end() {
let range = NSRange::from(10..20);
assert!(!range.contains(0));
assert!(!range.contains(9));
assert!(range.contains(10));
assert!(range.contains(11));
assert!(!range.contains(20));
assert!(!range.contains(21));
}
#[test]
#[should_panic = "NSRange too large"]
fn test_end_large() {
let _ = NSRange::new(usize::MAX, usize::MAX).end();
}
}

View file

@ -0,0 +1,680 @@
use alloc::vec::Vec;
use core::fmt;
use core::marker::PhantomData;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{
NSArray, NSCopying, NSEnumerator, NSFastEnumeration, NSFastEnumerator, NSMutableCopying,
NSMutableSet, NSObject,
};
use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId};
use crate::runtime::Class;
use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id};
__inner_extern_class!(
/// An immutable unordered collection of unique objects.
///
/// See [Apple's documentation][apple-doc].
///
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsset?language=objc
#[derive(PartialEq, Eq, Hash)]
pub struct NSSet<T: Message, O: Ownership = Shared> {
item: PhantomData<Id<T, O>>,
notunwindsafe: PhantomData<&'static mut ()>,
}
unsafe impl<T: Message, O: Ownership> ClassType for NSSet<T, O> {
type Super = NSObject;
}
);
// SAFETY: Same as NSArray<T, O>
unsafe impl<T: Message + Sync + Send> Sync for NSSet<T, Shared> {}
unsafe impl<T: Message + Sync + Send> Send for NSSet<T, Shared> {}
unsafe impl<T: Message + Sync> Sync for NSSet<T, Owned> {}
unsafe impl<T: Message + Send> Send for NSSet<T, Owned> {}
// SAFETY: Same as NSArray<T, O>
impl<T: Message + RefUnwindSafe, O: Ownership> RefUnwindSafe for NSSet<T, O> {}
impl<T: Message + RefUnwindSafe> UnwindSafe for NSSet<T, Shared> {}
impl<T: Message + UnwindSafe> UnwindSafe for NSSet<T, Owned> {}
#[track_caller]
pub(crate) unsafe fn with_objects<T: Message + ?Sized, R: Message, O: Ownership>(
cls: &Class,
objects: &[&T],
) -> Id<R, O> {
unsafe {
msg_send_id![
msg_send_id![cls, alloc],
initWithObjects: objects.as_ptr(),
count: objects.len()
]
}
}
extern_methods!(
unsafe impl<T: Message, O: Ownership> NSSet<T, O> {
/// Creates an empty [`NSSet`].
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set = NSSet::<NSString>::new();
/// ```
pub fn new() -> Id<Self, Shared> {
// SAFETY:
// - `new` may not create a new object, but instead return a shared
// instance. We remedy this by returning `Id<Self, Shared>`.
// - `O` don't actually matter here! E.g. `NSSet<T, Owned>` is
// perfectly legal, since the set doesn't have any elements, and
// hence the notion of ownership over the elements is void.
unsafe { msg_send_id![Self::class(), new] }
}
/// Creates an [`NSSet`] from a vector.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str).to_vec();
/// let set = NSSet::from_vec(strs);
/// ```
pub fn from_vec(vec: Vec<Id<T, O>>) -> Id<Self, O> {
// SAFETY:
// When we know that we have ownership over the variables, we also
// know that there cannot be another set in existence with the same
// objects, so `Id<NSSet<T, Owned>, Owned>` is safe to return when
// we receive `Vec<Id<T, Owned>>`.
unsafe { with_objects(Self::class(), vec.as_slice_ref()) }
}
/// Returns the number of elements in the set.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// assert_eq!(set.len(), 3);
/// ```
#[doc(alias = "count")]
#[sel(count)]
pub fn len(&self) -> usize;
/// Returns `true` if the set contains no elements.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set = NSSet::<NSString>::new();
/// assert!(set.is_empty());
/// ```
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Returns a reference to one of the objects in the set, or `None` if
/// the set is empty.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// let any = set.get_any().unwrap();
/// assert!(any == &*strs[0] || any == &*strs[1] || any == &*strs[2]);
/// ```
#[doc(alias = "anyObject")]
#[sel(anyObject)]
pub fn get_any(&self) -> Option<&T>;
/// An iterator visiting all elements in arbitrary order.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// for s in set.iter() {
/// println!("{s}");
/// }
/// ```
#[doc(alias = "objectEnumerator")]
pub fn iter(&self) -> NSEnumerator<'_, T> {
unsafe {
let result = msg_send![self, objectEnumerator];
NSEnumerator::from_ptr(result)
}
}
/// Returns a [`Vec`] containing the set's elements, consuming the set.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSMutableString, NSSet};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = vec![
/// NSMutableString::from_str("one"),
/// NSMutableString::from_str("two"),
/// NSMutableString::from_str("three"),
/// ];
/// let set = NSSet::from_vec(strs);
/// let vec = NSSet::into_vec(set);
/// assert_eq!(vec.len(), 3);
/// ```
pub fn into_vec(set: Id<Self, O>) -> Vec<Id<T, O>> {
set.into_iter()
.map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() })
.collect()
}
}
unsafe impl<T: Message> NSSet<T, Shared> {
/// Creates an [`NSSet`] from a slice.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// ```
pub fn from_slice(slice: &[Id<T, Shared>]) -> Id<Self, Shared> {
// SAFETY:
// Taking `&T` would not be sound, since the `&T` could come from
// an `Id<T, Owned>` that would now no longer be owned!
//
// We always return `Id<NSSet<T, Shared>, Shared>` because the
// elements are shared.
unsafe { with_objects(Self::class(), slice.as_slice_ref()) }
}
/// Returns an [`NSArray`] containing the set's elements, or an empty
/// array if the set is empty.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSNumber, NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let nums = [1, 2, 3];
/// let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
///
/// assert_eq!(set.to_array().len(), 3);
/// assert!(set.to_array().iter().all(|i| nums.contains(&i.as_i32())));
/// ```
#[doc(alias = "allObjects")]
pub fn to_array(&self) -> Id<NSArray<T, Shared>, Shared> {
// SAFETY:
// We only define this method for sets with shared elements
// because we can't return copies of owned elements.
unsafe { msg_send_id![self, allObjects] }
}
}
// We're explicit about `T` being `PartialEq` for these methods because the
// set compares the input value(s) with elements in the set
// For comparison: Rust's HashSet requires similar methods to be `Hash` + `Eq`
unsafe impl<T: Message + PartialEq, O: Ownership> NSSet<T, O> {
/// Returns `true` if the set contains a value.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// assert!(set.contains(ns_string!("one")));
/// ```
#[doc(alias = "containsObject:")]
#[sel(containsObject:)]
pub fn contains(&self, value: &T) -> bool;
/// Returns a reference to the value in the set, if any, that is equal
/// to the given value.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// use objc2::ns_string;
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let strs = ["one", "two", "three"].map(NSString::from_str);
/// let set = NSSet::from_slice(&strs);
/// assert_eq!(set.get(ns_string!("one")), Some(&*strs[0]));
/// assert_eq!(set.get(ns_string!("four")), None);
/// ```
#[doc(alias = "member:")]
#[sel(member:)]
pub fn get(&self, value: &T) -> Option<&T>;
/// Returns `true` if the set is a subset of another, i.e., `other`
/// contains at least all the values in `self`.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
/// let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
///
/// assert!(set1.is_subset(&set2));
/// assert!(!set2.is_subset(&set1));
/// ```
#[doc(alias = "isSubsetOfSet:")]
#[sel(isSubsetOfSet:)]
pub fn is_subset(&self, other: &NSSet<T, O>) -> bool;
/// Returns `true` if the set is a superset of another, i.e., `self`
/// contains at least all the values in `other`.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
/// let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
///
/// assert!(!set1.is_superset(&set2));
/// assert!(set2.is_superset(&set1));
/// ```
pub fn is_superset(&self, other: &NSSet<T, O>) -> bool {
other.is_subset(self)
}
#[sel(intersectsSet:)]
fn intersects_set(&self, other: &NSSet<T, O>) -> bool;
/// Returns `true` if `self` has no elements in common with `other`.
///
/// # Examples
///
/// ```
/// use objc2::foundation::{NSSet, NSString};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
///
/// let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
/// let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
/// let set3 = NSSet::from_slice(&["four", "five", "six"].map(NSString::from_str));
///
/// assert!(!set1.is_disjoint(&set2));
/// assert!(set1.is_disjoint(&set3));
/// assert!(set2.is_disjoint(&set3));
/// ```
pub fn is_disjoint(&self, other: &NSSet<T, O>) -> bool {
!self.intersects_set(other)
}
}
);
unsafe impl<T: Message> NSCopying for NSSet<T, Shared> {
type Ownership = Shared;
type Output = NSSet<T, Shared>;
}
unsafe impl<T: Message> NSMutableCopying for NSSet<T, Shared> {
type Output = NSMutableSet<T, Shared>;
}
impl<T: Message> alloc::borrow::ToOwned for NSSet<T, Shared> {
type Owned = Id<NSSet<T, Shared>, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
unsafe impl<T: Message, O: Ownership> NSFastEnumeration for NSSet<T, O> {
type Item = T;
}
impl<'a, T: Message, O: Ownership> IntoIterator for &'a NSSet<T, O> {
type Item = &'a T;
type IntoIter = NSFastEnumerator<'a, NSSet<T, O>>;
fn into_iter(self) -> Self::IntoIter {
self.iter_fast()
}
}
impl<T: Message, O: Ownership> DefaultId for NSSet<T, O> {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
impl<T: fmt::Debug + Message, O: Ownership> fmt::Debug for NSSet<T, O> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_set().entries(self.iter_fast()).finish()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use alloc::vec;
use super::*;
use crate::foundation::{NSMutableString, NSNumber, NSString};
use crate::ns_string;
use crate::rc::{RcTestObject, ThreadTestData};
#[test]
fn test_new() {
let set = NSSet::<NSString>::new();
assert!(set.is_empty());
}
#[test]
fn test_from_vec() {
let set = NSSet::<NSString>::from_vec(Vec::new());
assert!(set.is_empty());
let strs = ["one", "two", "three"].map(NSString::from_str);
let set = NSSet::from_vec(strs.to_vec());
assert!(strs.into_iter().all(|s| set.contains(&s)));
let nums = [1, 2, 3].map(NSNumber::new_i32);
let set = NSSet::from_vec(nums.to_vec());
assert!(nums.into_iter().all(|n| set.contains(&n)));
}
#[test]
fn test_from_slice() {
let set = NSSet::<NSString>::from_slice(&[]);
assert!(set.is_empty());
let strs = ["one", "two", "three"].map(NSString::from_str);
let set = NSSet::from_slice(&strs);
assert!(strs.into_iter().all(|s| set.contains(&s)));
let nums = [1, 2, 3].map(NSNumber::new_i32);
let set = NSSet::from_slice(&nums);
assert!(nums.into_iter().all(|n| set.contains(&n)));
}
#[test]
fn test_len() {
let set = NSSet::<NSString>::new();
assert!(set.is_empty());
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
assert_eq!(set.len(), 2);
let set = NSSet::from_vec(vec![NSObject::new(), NSObject::new(), NSObject::new()]);
assert_eq!(set.len(), 3);
}
#[test]
fn test_get() {
let set = NSSet::<NSString>::new();
assert!(set.get(ns_string!("one")).is_none());
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
assert!(set.get(ns_string!("two")).is_some());
assert!(set.get(ns_string!("three")).is_none());
}
#[test]
fn test_get_return_lifetime() {
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
let res = {
let value = NSString::from_str("one");
set.get(&value)
};
assert_eq!(res, Some(ns_string!("one")));
}
#[test]
fn test_get_any() {
let set = NSSet::<NSString>::new();
assert!(set.get_any().is_none());
let strs = ["one", "two", "three"].map(NSString::from_str);
let set = NSSet::from_slice(&strs);
let any = set.get_any().unwrap();
assert!(any == &*strs[0] || any == &*strs[1] || any == &*strs[2]);
}
#[test]
fn test_contains() {
let set = NSSet::<NSString>::new();
assert!(!set.contains(ns_string!("one")));
let set = NSSet::from_slice(&["one", "two", "two"].map(NSString::from_str));
assert!(set.contains(ns_string!("one")));
assert!(!set.contains(ns_string!("three")));
}
#[test]
fn test_is_subset() {
let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
assert!(set1.is_subset(&set2));
assert!(!set2.is_subset(&set1));
}
#[test]
fn test_is_superset() {
let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
assert!(!set1.is_superset(&set2));
assert!(set2.is_superset(&set1));
}
#[test]
fn test_is_disjoint() {
let set1 = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
let set2 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
let set3 = NSSet::from_slice(&["four", "five", "six"].map(NSString::from_str));
assert!(!set1.is_disjoint(&set2));
assert!(set1.is_disjoint(&set3));
assert!(set2.is_disjoint(&set3));
}
#[test]
fn test_to_array() {
let nums = [1, 2, 3];
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
assert_eq!(set.to_array().len(), 3);
assert!(set.to_array().iter().all(|i| nums.contains(&i.as_i32())));
}
#[test]
fn test_iter() {
let nums = [1, 2, 3];
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
assert_eq!(set.iter().count(), 3);
assert!(set.iter().all(|i| nums.contains(&i.as_i32())));
}
#[test]
fn test_iter_fast() {
let nums = [1, 2, 3];
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
assert_eq!(set.iter_fast().count(), 3);
assert!(set.iter_fast().all(|i| nums.contains(&i.as_i32())));
}
#[test]
fn test_into_iter() {
let nums = [1, 2, 3];
let set = NSSet::from_slice(&nums.map(NSNumber::new_i32));
assert!(set.into_iter().all(|i| nums.contains(&i.as_i32())));
}
#[test]
fn test_into_vec() {
let strs = vec![
NSMutableString::from_str("one"),
NSMutableString::from_str("two"),
NSMutableString::from_str("three"),
];
let set = NSSet::from_vec(strs);
let mut vec = NSSet::into_vec(set);
for str in vec.iter_mut() {
str.push_nsstring(ns_string!(" times zero is zero"));
}
assert_eq!(vec.len(), 3);
let suffix = ns_string!("zero");
assert!(vec.iter().all(|str| str.has_suffix(suffix)));
}
#[test]
fn test_equality() {
let set1 = NSSet::<NSString>::new();
let set2 = NSSet::<NSString>::new();
assert_eq!(set1, set2);
}
#[test]
fn test_copy() {
let set1 = NSSet::from_slice(&["one", "two", "three"].map(NSString::from_str));
let set2 = set1.copy();
assert_eq!(set1, set2);
}
#[test]
fn test_debug() {
let set = NSSet::<NSString>::new();
assert_eq!(format!("{:?}", set), "{}");
let set = NSSet::from_slice(&["one", "two"].map(NSString::from_str));
assert!(matches!(
format!("{:?}", set).as_str(),
"{\"one\", \"two\"}" | "{\"two\", \"one\"}"
));
}
#[test]
fn test_retains_stored() {
let obj = Id::into_shared(RcTestObject::new());
let mut expected = ThreadTestData::current();
let input = [obj.clone(), obj.clone()];
expected.retain += 2;
expected.assert_current();
let set = NSSet::from_slice(&input);
expected.retain += 1;
expected.assert_current();
let _obj = set.get_any().unwrap();
expected.assert_current();
drop(set);
expected.release += 1;
expected.assert_current();
let set = NSSet::from_vec(Vec::from(input));
expected.retain += 1;
expected.release += 2;
expected.assert_current();
drop(set);
expected.release += 1;
expected.assert_current();
drop(obj);
expected.release += 1;
expected.dealloc += 1;
expected.assert_current();
}
#[test]
fn test_nscopying_uses_retain() {
let obj = Id::into_shared(RcTestObject::new());
let set = NSSet::from_slice(&[obj]);
let mut expected = ThreadTestData::current();
let _copy = set.copy();
expected.assert_current();
let _copy = set.mutable_copy();
expected.retain += 1;
expected.assert_current();
}
#[test]
#[cfg_attr(
feature = "apple",
ignore = "this works differently on different framework versions"
)]
fn test_iter_no_retain() {
let obj = Id::into_shared(RcTestObject::new());
let set = NSSet::from_slice(&[obj]);
let mut expected = ThreadTestData::current();
let iter = set.iter();
expected.retain += 0;
expected.assert_current();
assert_eq!(iter.count(), 1);
expected.autorelease += 0;
expected.assert_current();
let iter = set.iter_fast();
assert_eq!(iter.count(), 1);
expected.assert_current();
}
}

View file

@ -0,0 +1,527 @@
use alloc::borrow::ToOwned;
use core::cmp;
use core::ffi::c_void;
use core::fmt;
use core::panic::RefUnwindSafe;
use core::panic::UnwindSafe;
use core::ptr::NonNull;
use core::slice;
use core::str;
use std::os::raw::c_char;
use super::{NSComparisonResult, NSCopying, NSMutableCopying, NSMutableString, NSObject};
use crate::rc::{autoreleasepool, AutoreleasePool, DefaultId, Id, Shared};
use crate::runtime::{Class, Object};
use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType};
#[cfg(feature = "apple")]
const UTF8_ENCODING: usize = 4;
#[cfg(feature = "gnustep-1-7")]
const UTF8_ENCODING: i32 = 4;
extern_class!(
/// An immutable, plain-text Unicode string object.
///
/// Can be created statically using the [`ns_string!`] macro.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring?language=objc).
///
/// [`ns_string!`]: crate::ns_string
#[derive(PartialEq, Eq, Hash)]
pub struct NSString;
// TODO: Use isEqualToString: for comparison (instead of just isEqual:)
// The former is more performant
// TODO: Check if performance of NSSelectorFromString is worthwhile
unsafe impl ClassType for NSString {
type Super = NSObject;
}
);
// SAFETY: `NSString` is immutable and `NSMutableString` can only be mutated
// from `&mut` methods.
unsafe impl Sync for NSString {}
unsafe impl Send for NSString {}
// Even if an exception occurs inside a string method, the state of the string
// (should) still be perfectly safe to access.
impl UnwindSafe for NSString {}
impl RefUnwindSafe for NSString {}
extern_methods!(
unsafe impl NSString {
/// Construct an empty NSString.
pub fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
/// Create a new string by appending the given string to self.
///
///
/// # Example
///
/// ```
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// use objc2::ns_string;
/// let error_tag = ns_string!("Error: ");
/// let error_string = ns_string!("premature end of file.");
/// let error_message = error_tag.concat(error_string);
/// assert_eq!(&*error_message, ns_string!("Error: premature end of file."));
/// ```
#[doc(alias = "stringByAppendingString")]
#[doc(alias = "stringByAppendingString:")]
pub fn concat(&self, other: &Self) -> Id<Self, Shared> {
// SAFETY: The other string is non-null, and won't be retained
// by the function.
unsafe { msg_send_id![self, stringByAppendingString: other] }
}
/// Create a new string by appending the given string, separated by
/// a path separator.
///
/// This is similar to [`Path::join`][std::path::Path::join].
///
/// Note that this method only works with file paths (not, for
/// example, string representations of URLs).
///
///
/// # Examples
///
/// ```
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// use objc2::ns_string;
///
/// let extension = ns_string!("scratch.tiff");
/// assert_eq!(&*ns_string!("/tmp").join_path(extension), ns_string!("/tmp/scratch.tiff"));
/// assert_eq!(&*ns_string!("/tmp/").join_path(extension), ns_string!("/tmp/scratch.tiff"));
/// assert_eq!(&*ns_string!("/").join_path(extension), ns_string!("/scratch.tiff"));
/// assert_eq!(&*ns_string!("").join_path(extension), ns_string!("scratch.tiff"));
/// ```
#[doc(alias = "stringByAppendingPathComponent")]
#[doc(alias = "stringByAppendingPathComponent:")]
pub fn join_path(&self, other: &Self) -> Id<Self, Shared> {
// SAFETY: Same as `Self::concat`.
unsafe { msg_send_id![self, stringByAppendingPathComponent: other] }
}
/// The number of UTF-8 code units in `self`.
#[doc(alias = "lengthOfBytesUsingEncoding")]
#[doc(alias = "lengthOfBytesUsingEncoding:")]
pub fn len(&self) -> usize {
unsafe { msg_send![self, lengthOfBytesUsingEncoding: UTF8_ENCODING] }
}
/// The number of UTF-16 code units in the string.
///
/// See also [`NSString::len`].
#[doc(alias = "length")]
#[sel(length)]
pub fn len_utf16(&self) -> usize;
pub fn is_empty(&self) -> bool {
// TODO: lengthOfBytesUsingEncoding: might sometimes return 0 for
// other reasons, so this is not really correct!
self.len() == 0
}
/// Get the [`str`](`prim@str`) representation of this string if it can be
/// done efficiently.
///
/// Returns [`None`] if the internal storage does not allow this to be
/// done efficiently. Use [`NSString::as_str`] or `NSString::to_string`
/// if performance is not an issue.
#[doc(alias = "CFStringGetCStringPtr")]
#[allow(unused)]
// TODO: Finish this
fn as_str_wip(&self) -> Option<&str> {
type CFStringEncoding = u32;
#[allow(non_upper_case_globals)]
// https://developer.apple.com/documentation/corefoundation/cfstringbuiltinencodings/kcfstringencodingutf8?language=objc
const kCFStringEncodingUTF8: CFStringEncoding = 0x08000100;
extern "C" {
// https://developer.apple.com/documentation/corefoundation/1542133-cfstringgetcstringptr?language=objc
fn CFStringGetCStringPtr(s: &NSString, encoding: CFStringEncoding)
-> *const c_char;
}
let bytes = unsafe { CFStringGetCStringPtr(self, kCFStringEncodingUTF8) };
NonNull::new(bytes as *mut u8).map(|bytes| {
let len = self.len();
let bytes: &[u8] = unsafe { slice::from_raw_parts(bytes.as_ptr(), len) };
str::from_utf8(bytes).unwrap()
})
}
/// Get an [UTF-16] string slice if it can be done efficiently.
///
/// Returns [`None`] if the internal storage of `self` does not allow this
/// to be returned efficiently.
///
/// See [`as_str`](Self::as_str) for the UTF-8 equivalent.
///
/// [UTF-16]: https://en.wikipedia.org/wiki/UTF-16
#[allow(unused)]
// TODO: Finish this
fn as_utf16(&self) -> Option<&[u16]> {
extern "C" {
// https://developer.apple.com/documentation/corefoundation/1542939-cfstringgetcharactersptr?language=objc
fn CFStringGetCharactersPtr(s: &NSString) -> *const u16;
}
let ptr = unsafe { CFStringGetCharactersPtr(self) };
NonNull::new(ptr as *mut u16)
.map(|ptr| unsafe { slice::from_raw_parts(ptr.as_ptr(), self.len_utf16()) })
}
/// Get the [`str`](`prim@str`) representation of this.
///
/// TODO: Further explain this.
#[doc(alias = "UTF8String")]
pub fn as_str<'r, 's: 'r, 'p: 'r>(&'s self, pool: &'p AutoreleasePool) -> &'r str {
// NOTE: Please keep up to date with `objc2::exception`!
// 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.
//
// https://developer.apple.com/documentation/foundation/nsstring/1411189-utf8string?language=objc
let bytes: *const c_char = unsafe { msg_send![self, UTF8String] };
let bytes: *const u8 = bytes.cast();
let len = self.len();
// 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()
// NOTE: Please keep up to date with `objc2::exception`!
}
// TODO: Allow usecases where the NUL byte from `UTF8String` is kept?
/// Creates an immutable `NSString` by copying the given string slice.
///
/// Prefer using the [`ns_string!`] macro when possible.
///
/// [`ns_string!`]: crate::ns_string
#[doc(alias = "initWithBytes")]
#[doc(alias = "initWithBytes:length:encoding:")]
#[allow(clippy::should_implement_trait)] // Not really sure of a better name
pub fn from_str(string: &str) -> Id<Self, Shared> {
unsafe {
let obj = from_str(Self::class(), string);
Id::new(obj.cast()).unwrap()
}
}
// TODO: initWithBytesNoCopy:, maybe add lifetime parameter to NSString?
// See https://github.com/nvzqz/fruity/blob/320efcf715c2c5fbd2f3084f671f2be2e03a6f2b/src/foundation/ns_string/mod.rs#L350-L381
// Might be quite difficult, as Objective-C code might assume the NSString
// is always alive?
// See https://github.com/drewcrawford/foundationr/blob/b27683417a35510e8e5d78a821f081905b803de6/src/nsstring.rs
/// Whether the given string matches the beginning characters of this
/// string.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring/1410309-hasprefix?language=objc).
#[doc(alias = "hasPrefix")]
#[doc(alias = "hasPrefix:")]
#[sel(hasPrefix:)]
pub fn has_prefix(&self, prefix: &NSString) -> bool;
/// Whether the given string matches the ending characters of this string.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsstring/1416529-hassuffix?language=objc).
#[doc(alias = "hasSuffix")]
#[doc(alias = "hasSuffix:")]
#[sel(hasSuffix:)]
pub fn has_suffix(&self, suffix: &NSString) -> bool;
// TODO: Other comparison methods:
// - compare:options:
// - compare:options:range:
// - compare:options:range:locale:
// - localizedCompare:
// - caseInsensitiveCompare:
// - localizedCaseInsensitiveCompare:
// - localizedStandardCompare:
#[sel(compare:)]
fn compare(&self, other: &Self) -> NSComparisonResult;
// pub fn from_nsrange(range: NSRange) -> Id<Self, Shared>
// https://developer.apple.com/documentation/foundation/1415155-nsstringfromrange?language=objc
}
);
pub(crate) fn from_str(cls: &Class, string: &str) -> *mut Object {
let bytes: *const c_void = string.as_ptr().cast();
unsafe {
let obj: *mut Object = msg_send![cls, alloc];
msg_send![
obj,
initWithBytes: bytes,
length: string.len(),
encoding: UTF8_ENCODING,
]
}
}
impl PartialOrd for NSString {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for NSString {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.compare(other).into()
}
}
// TODO: PartialEq and PartialOrd against &str
// See `fruity`'s implementation:
// https://github.com/nvzqz/fruity/blob/320efcf715c2c5fbd2f3084f671f2be2e03a6f2b/src/foundation/ns_string/mod.rs#L69-L163
impl DefaultId for NSString {
type Ownership = Shared;
#[inline]
fn default_id() -> Id<Self, Self::Ownership> {
Self::new()
}
}
unsafe impl NSCopying for NSString {
type Ownership = Shared;
type Output = NSString;
}
unsafe impl NSMutableCopying for NSString {
type Output = NSMutableString;
}
impl ToOwned for NSString {
type Owned = Id<NSString, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl fmt::Display for NSString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// The call to `to_owned` is unfortunate, but is required to work
// around `f` not being AutoreleaseSafe.
// TODO: Fix this!
let s = autoreleasepool(|pool| self.as_str(pool).to_owned());
fmt::Display::fmt(&s, f)
}
}
impl fmt::Debug for NSString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// The call to `to_owned` is unfortunate, but is required to work
// around `f` not being AutoreleaseSafe.
// TODO: Fix this!
let s = autoreleasepool(|pool| self.as_str(pool).to_owned());
fmt::Debug::fmt(&s, f)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::format;
use core::ptr;
#[test]
fn test_equality() {
let s1 = NSString::from_str("abc");
let s2 = NSString::from_str("abc");
assert_eq!(s1, s1);
assert_eq!(s1, s2);
let s3 = NSString::from_str("def");
assert_ne!(s1, s3);
}
#[test]
fn display_debug() {
let s = NSString::from_str("xyz\"123");
assert_eq!(format!("{}", s), "xyz\"123");
assert_eq!(format!("{:?}", s), r#""xyz\"123""#);
}
#[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_eq!(s.len(), expected.len());
autoreleasepool(|pool| {
assert_eq!(s.as_str(pool), expected);
});
}
#[test]
fn test_nul() {
let expected = "\0";
let s = NSString::from_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_eq!(s.len(), expected.len());
autoreleasepool(|pool| {
assert_eq!(s.as_str(pool), expected);
});
}
#[test]
fn test_copy() {
let s1 = NSString::from_str("abc");
let s2 = s1.copy();
// An optimization that NSString makes, since it is immutable
assert_eq!(Id::as_ptr(&s1), Id::as_ptr(&s2));
assert!(s2.is_kind_of::<NSString>());
let s3 = s1.mutable_copy();
assert_ne!(Id::as_ptr(&s1), Id::as_ptr(&s3).cast());
assert!(s3.is_kind_of::<NSMutableString>());
}
#[test]
fn test_copy_nsstring_is_same() {
let string1 = NSString::from_str("Hello, world!");
let string2 = string1.copy();
assert!(
ptr::eq(&*string1, &*string2),
"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(feature = "apple")]
let expected = "\u{feff}a\u{feff}";
#[cfg(feature = "gnustep-1-7")]
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());
}
#[test]
fn test_hash() {
use core::hash::Hasher;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;
let s1 = NSString::from_str("example string goes here");
let s2 = NSString::from_str("example string goes here");
let mut hashstate = DefaultHasher::new();
let mut hashstate2 = DefaultHasher::new();
s1.hash(&mut hashstate);
s2.hash(&mut hashstate2);
assert_eq!(hashstate.finish(), hashstate2.finish());
}
#[test]
fn test_prefix_suffix() {
let s = NSString::from_str("abcdef");
let prefix = NSString::from_str("abc");
let suffix = NSString::from_str("def");
assert!(s.has_prefix(&prefix));
assert!(s.has_suffix(&suffix));
assert!(!s.has_prefix(&suffix));
assert!(!s.has_suffix(&prefix));
}
#[test]
#[allow(clippy::nonminimal_bool)]
fn test_cmp() {
let s1 = NSString::from_str("aa");
assert!(s1 <= s1);
assert!(s1 >= s1);
let s2 = NSString::from_str("ab");
assert!(s1 < s2);
assert!(!(s1 > s2));
assert!(s1 <= s2);
assert!(!(s1 >= s2));
let s3 = NSString::from_str("ba");
assert!(s1 < s3);
assert!(!(s1 > s3));
assert!(s1 <= s3);
assert!(!(s1 >= s3));
assert!(s2 < s3);
assert!(!(s2 > s3));
assert!(s2 <= s3);
assert!(!(s2 >= s3));
let s = NSString::from_str("abc");
let shorter = NSString::from_str("a");
let longer = NSString::from_str("abcdef");
assert!(s > shorter);
assert!(s < longer);
}
}

View file

@ -0,0 +1,237 @@
use core::fmt;
use core::marker::PhantomData;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSObject, NSString};
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType};
extern_class!(
/// A thread of execution.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsthread?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSThread;
unsafe impl ClassType for NSThread {
type Super = NSObject;
}
);
unsafe impl Send for NSThread {}
unsafe impl Sync for NSThread {}
impl UnwindSafe for NSThread {}
impl RefUnwindSafe for NSThread {}
extern_methods!(
unsafe impl NSThread {
/// Returns the [`NSThread`] object representing the current thread.
pub fn current() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), currentThread] }
}
/// Returns the [`NSThread`] object representing the main thread.
pub fn main() -> Id<NSThread, Shared> {
// The main thread static may not have been initialized
// This can at least fail in GNUStep!
let obj: Option<_> = unsafe { msg_send_id![Self::class(), mainThread] };
obj.expect("Could not retrieve main thread.")
}
/// Returns `true` if the thread is the main thread.
#[sel(isMainThread)]
pub fn is_main(&self) -> bool;
/// The name of the thread.
pub fn name(&self) -> Option<Id<NSString, Shared>> {
unsafe { msg_send_id![self, name] }
}
unsafe fn new() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
#[sel(start)]
unsafe fn start(&self);
#[sel(isMainThread)]
fn is_current_main() -> bool;
#[sel(isMultiThreaded)]
fn is_global_multi() -> bool;
}
);
impl fmt::Debug for NSThread {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Use -[NSThread description] since that includes the thread number
let obj: &NSObject = self;
fmt::Debug::fmt(obj, f)
}
}
/// Whether the application is multithreaded according to Cocoa.
pub fn is_multi_threaded() -> bool {
NSThread::is_global_multi()
}
/// Whether the current thread is the main thread.
pub fn is_main_thread() -> bool {
NSThread::is_current_main()
}
#[allow(unused)]
fn make_multithreaded() {
let thread = unsafe { NSThread::new() };
unsafe { thread.start() };
// Don't bother waiting for it to complete!
}
/// A marker type taken by functions that can only be executed on the main
/// thread.
///
/// By design, this is neither [`Send`] nor [`Sync`], and can only be created
/// on the main thread, meaning that if you're holding this, you know you're
/// running on the main thread.
///
/// See the following links for more information on main-thread-only APIs:
/// - [Main Thread Only APIs on OS X](https://www.dribin.org/dave/blog/archives/2009/02/01/main_thread_apis/)
/// - [Thread Safety Summary](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html#//apple_ref/doc/uid/10000057i-CH12-SW1)
/// - [Are the Cocoa Frameworks Thread Safe?](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/AddingBehaviortoaCocoaProgram/AddingBehaviorCocoa.html#//apple_ref/doc/uid/TP40002974-CH5-SW47)
/// - [Technical Note TN2028 - Threading Architectures](https://developer.apple.com/library/archive/technotes/tn/tn2028.html#//apple_ref/doc/uid/DTS10003065)
/// - [Thread Management](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html)
/// - [Mike Ash' article on thread safety](https://www.mikeash.com/pyblog/friday-qa-2009-01-09.html)
///
///
/// # Examples
///
/// Use when designing APIs that are only safe to use on the main thread:
///
/// ```no_run
/// use objc2::foundation::MainThreadMarker;
/// use objc2::runtime::Object;
/// use objc2::msg_send;
/// # let obj = 0 as *const Object;
///
/// // This action requires the main thread, so we take a marker as parameter.
/// // It signals clearly to users "this requires the main thread".
/// unsafe fn do_thing(obj: *const Object, _mtm: MainThreadMarker) {
/// msg_send![obj, someActionThatRequiresTheMainThread]
/// }
///
/// // Usage
/// let mtm = MainThreadMarker::new().unwrap();
/// unsafe { do_thing(obj, mtm) }
/// ```
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
// This is valid to Copy because it's still `!Send` and `!Sync`.
pub struct MainThreadMarker {
// No lifetime information needed; the main thread is static and available
// throughout the entire program!
_priv: PhantomData<*mut ()>,
}
impl MainThreadMarker {
/// Construct a new [`MainThreadMarker`].
///
/// Returns [`None`] if the current thread was not the main thread.
pub fn new() -> Option<Self> {
if is_main_thread() {
// SAFETY: We just checked that we are running on the main thread.
Some(unsafe { Self::new_unchecked() })
} else {
None
}
}
/// Construct a new [`MainThreadMarker`] without first checking whether
/// the current thread is the main one.
///
/// # Safety
///
/// The current thread must be the main thread.
pub unsafe fn new_unchecked() -> Self {
// SAFETY: Upheld by caller
Self { _priv: PhantomData }
}
}
impl fmt::Debug for MainThreadMarker {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("MainThreadMarker").finish()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::*;
#[test]
#[cfg_attr(
feature = "gnustep-1-7",
ignore = "Retrieving main thread is weirdly broken, only works with --test-threads=1"
)]
fn test_main_thread() {
let current = NSThread::current();
let main = NSThread::main();
assert!(main.is_main());
if main == current {
assert!(current.is_main());
assert!(is_main_thread());
} else {
assert!(!current.is_main());
assert!(!is_main_thread());
}
}
#[test]
fn test_not_main_thread() {
let res = std::thread::spawn(|| (is_main_thread(), NSThread::current().is_main()))
.join()
.unwrap();
assert_eq!(res, (false, false));
}
#[test]
fn test_main_thread_auto_traits() {
fn assert_traits<T: Unpin + UnwindSafe + RefUnwindSafe + Sized>() {}
assert_traits::<MainThreadMarker>()
}
#[test]
#[cfg_attr(
feature = "gnustep-1-7",
ignore = "Retrieving main thread is weirdly broken, only works with --test-threads=1"
)]
fn test_debug() {
let thread = NSThread::main();
let actual = format!("{:?}", thread);
let expected = [
// macOS 11
format!("<NSThread: {:p}>{{number = 1, name = (null)}}", thread),
format!("<NSThread: {:p}>{{number = 1, name = main}}", thread),
// macOS 12
format!("<_NSMainThread: {:p}>{{number = 1, name = (null)}}", thread),
format!("<_NSMainThread: {:p}>{{number = 1, name = main}}", thread),
];
assert!(
expected.contains(&actual),
"Expected one of {:?}, got {:?}",
expected,
actual,
);
// SAFETY: We don't use the marker for anything other than its Debug
// impl, so this test doesn't actually need to run on the main thread!
let marker = unsafe { MainThreadMarker::new_unchecked() };
assert_eq!(format!("{:?}", marker), "MainThreadMarker");
}
}

View file

@ -0,0 +1,189 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use super::{NSCopying, NSObject, NSString};
use crate::rc::{DefaultId, Id, Shared};
use crate::{extern_class, extern_methods, msg_send_id, ClassType, Encode, Encoding, RefEncode};
extern_class!(
/// A universally unique value.
///
/// Can be used to identify types, interfaces, and other items.
///
/// Conversion methods to/from UUIDs from the `uuid` crate can be
/// enabled with the `uuid` crate feature.
///
/// macOS: This is only available on 10.8 and above.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nsuuid?language=objc).
#[derive(PartialEq, Eq, Hash)]
pub struct NSUUID;
unsafe impl ClassType for NSUUID {
type Super = NSObject;
}
);
/// The headers describe `initWithUUIDBytes:` and `getUUIDBytes:` as
/// taking `uuid_t`, but something fishy is going on, in reality they
/// expect a reference to these!
///
/// Hence we create this newtype to change the encoding.
#[repr(transparent)]
struct UuidBytes([u8; 16]);
unsafe impl RefEncode for UuidBytes {
const ENCODING_REF: Encoding = Encoding::Array(16, &u8::ENCODING);
}
// SAFETY: `NSUUID` is immutable.
unsafe impl Sync for NSUUID {}
unsafe impl Send for NSUUID {}
impl UnwindSafe for NSUUID {}
impl RefUnwindSafe for NSUUID {}
extern_methods!(
unsafe impl NSUUID {
pub fn new_v4() -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), new] }
}
/// The 'nil UUID'.
pub fn nil() -> Id<Self, Shared> {
Self::from_bytes([0; 16])
}
pub fn from_bytes(bytes: [u8; 16]) -> Id<Self, Shared> {
let bytes = UuidBytes(bytes);
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithUUIDBytes: &bytes]
}
}
pub fn from_string(string: &NSString) -> Option<Id<Self, Shared>> {
unsafe {
let obj = msg_send_id![Self::class(), alloc];
msg_send_id![obj, initWithUUIDString: string]
}
}
#[sel(getUUIDBytes:)]
fn get_bytes_raw(&self, bytes: &mut UuidBytes);
pub fn as_bytes(&self) -> [u8; 16] {
let mut bytes = UuidBytes([0; 16]);
self.get_bytes_raw(&mut bytes);
bytes.0
}
pub fn string(&self) -> Id<NSString, Shared> {
unsafe { msg_send_id![self, UUIDString] }
}
}
);
impl fmt::Display for NSUUID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.string(), f)
}
}
impl fmt::Debug for NSUUID {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// The `uuid` crate does `Debug` and `Display` equally, and so do we
fmt::Display::fmt(&self.string(), f)
}
}
// UUID `compare:` is broken for some reason?
// impl PartialOrd for NSUUID {
// #[inline]
// fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
// Some(self.cmp(other))
// }
// }
// impl Ord for NSUUID {
// fn cmp(&self, other: &Self) -> cmp::Ordering {
// let res: NSComparisonResult = unsafe { msg_send![self, compare: other] };
// res.into()
// }
// }
/// Conversion methods to/from `uuid` crate.
#[cfg(feature = "uuid")]
impl NSUUID {
pub fn from_uuid(uuid: uuid::Uuid) -> Id<Self, Shared> {
Self::from_bytes(uuid.into_bytes())
}
pub fn as_uuid(&self) -> uuid::Uuid {
uuid::Uuid::from_bytes(self.as_bytes())
}
}
impl DefaultId for NSUUID {
type Ownership = Shared;
fn default_id() -> Id<Self, Self::Ownership> {
Self::nil()
}
}
unsafe impl NSCopying for NSUUID {
type Ownership = Shared;
type Output = NSUUID;
}
impl alloc::borrow::ToOwned for NSUUID {
type Owned = Id<NSUUID, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use super::*;
#[test]
fn test_new() {
let uuid1 = NSUUID::new_v4();
let uuid2 = NSUUID::new_v4();
assert_ne!(uuid1, uuid2, "Statistically very unlikely");
}
#[test]
fn test_bytes() {
let uuid = NSUUID::from_bytes([10; 16]);
assert_eq!(uuid.as_bytes(), [10; 16]);
}
#[test]
fn display_debug() {
let uuid = NSUUID::from_bytes([10; 16]);
let expected = "0A0A0A0A-0A0A-0A0A-0A0A-0A0A0A0A0A0A";
assert_eq!(format!("{}", uuid), expected);
assert_eq!(format!("{:?}", uuid), expected);
}
// #[test]
// fn test_compare() {
// let uuid1 = NSUUID::from_bytes([10; 16]);
// let uuid2 = NSUUID::from_bytes([9; 16]);
// assert!(uuid1 > uuid2);
// }
#[cfg(feature = "uuid")]
#[test]
fn test_convert_roundtrip() {
let nsuuid1 = NSUUID::new_v4();
let uuid = nsuuid1.as_uuid();
let nsuuid2 = NSUUID::from_uuid(uuid);
assert_eq!(nsuuid1, nsuuid2);
}
}

View file

@ -0,0 +1,363 @@
use alloc::string::ToString;
use core::ffi::c_void;
use core::fmt;
use core::hash;
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use core::str;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use super::{NSCopying, NSObject, NSPoint, NSRange, NSRect, NSSize};
use crate::rc::{Id, Shared};
use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType, Encode};
extern_class!(
/// A container wrapping any encodable type as an Obective-C object.
///
/// Since Objective-C collections like [`NSArray`] can only contain
/// objects, it is common to wrap pointers or structures like [`NSRange`].
///
/// Note that creating `NSValue`s is not `unsafe`, but almost all usage of
/// it is, since we cannot guarantee that the type that was used to
/// construct it is the same as the expected output type.
///
/// See also the [`NSNumber`] subclass for when you want to wrap numbers.
///
/// See [Apple's documentation][apple-doc] for more information.
///
/// [`NSArray`]: super::NSArray
/// [`NSRange`]: super::NSRange
/// [`NSNumber`]: super::NSNumber
/// [apple-doc]: https://developer.apple.com/documentation/foundation/nsnumber?language=objc
pub struct NSValue;
unsafe impl ClassType for NSValue {
type Super = NSObject;
}
);
// We can't implement any auto traits for NSValue, since it can contain an
// arbitary object!
extern_methods!(
/// Creation methods.
unsafe impl NSValue {
// 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.
/// Create a new `NSValue` containing the given type.
///
/// Be careful when using this since you may accidentally pass a reference
/// when you wanted to pass a concrete type instead.
///
///
/// # Examples
///
/// Create an `NSValue` containing an [`NSPoint`][super::NSPoint].
///
/// ```
/// use objc2::foundation::{NSPoint, NSValue};
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let val = NSValue::new::<NSPoint>(NSPoint::new(1.0, 1.0));
/// ```
pub fn new<T: 'static + Copy + Encode>(value: T) -> Id<Self, Shared> {
let bytes: *const T = &value;
let bytes: *const c_void = bytes.cast();
let encoding = CString::new(T::ENCODING.to_string()).unwrap();
unsafe {
msg_send_id![
msg_send_id![Self::class(), alloc],
initWithBytes: bytes,
objCType: encoding.as_ptr(),
]
}
}
}
/// Getter methods.
unsafe impl NSValue {
/// Retrieve the data contained in the `NSValue`.
///
/// 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
///
///
/// # Safety
///
/// The type of `T` must be what the NSValue actually stores, and any
/// safety invariants that the value has must be upheld.
///
/// Note that it may be, but is not always, enough to simply check whether
/// [`contains_encoding`] returns `true`. For example, `NonNull<T>` have
/// the same encoding as `*const T`, but `NonNull<T>` is clearly not
/// safe to return from this function even if you've checked the encoding
/// beforehand.
///
/// [`contains_encoding`]: Self::contains_encoding
///
///
/// # Examples
///
/// Store a pointer in `NSValue`, and retrieve it again afterwards.
///
/// ```
/// use std::ffi::c_void;
/// use std::ptr;
/// use objc2::foundation::NSValue;
///
/// # #[cfg(feature = "gnustep-1-7")]
/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() };
/// let val = NSValue::new::<*const c_void>(ptr::null());
/// // SAFETY: The value was just created with a pointer
/// let res = unsafe { val.get::<*const c_void>() };
/// assert!(res.is_null());
/// ```
pub unsafe fn get<T: 'static + Copy + Encode>(&self) -> T {
debug_assert!(
self.contains_encoding::<T>(),
"wrong encoding. NSValue tried to return something with encoding {}, but the encoding of the given type was {}",
self.encoding().unwrap_or("(NULL)"),
T::ENCODING,
);
let mut value = MaybeUninit::<T>::uninit();
let ptr: *mut c_void = value.as_mut_ptr().cast();
let _: () = unsafe { msg_send![self, getValue: ptr] };
// SAFETY: We know that `getValue:` initialized the value, and user
// ensures that it is safe to access.
unsafe { value.assume_init() }
}
pub fn get_range(&self) -> Option<NSRange> {
if self.contains_encoding::<NSRange>() {
// SAFETY: We just checked that this contains an NSRange
Some(unsafe { msg_send![self, rangeValue] })
} else {
None
}
}
pub fn get_point(&self) -> Option<NSPoint> {
if self.contains_encoding::<NSPoint>() {
// SAFETY: We just checked that this contains an NSPoint
//
// Note: The documentation says that `pointValue`, `sizeValue` and
// `rectValue` is only available on macOS, but turns out that they
// are actually available everywhere!
let res = unsafe { msg_send![self, pointValue] };
Some(res)
} else {
None
}
}
pub fn get_size(&self) -> Option<NSSize> {
if self.contains_encoding::<NSSize>() {
// SAFETY: We just checked that this contains an NSSize
let res = unsafe { msg_send![self, sizeValue] };
Some(res)
} else {
None
}
}
pub fn get_rect(&self) -> Option<NSRect> {
if self.contains_encoding::<NSRect>() {
// SAFETY: We just checked that this contains an NSRect
let res = unsafe { msg_send![self, rectValue] };
Some(res)
} else {
None
}
}
pub fn encoding(&self) -> Option<&str> {
let result: Option<NonNull<c_char>> = unsafe { msg_send![self, objCType] };
result.map(|s| unsafe { CStr::from_ptr(s.as_ptr()) }.to_str().unwrap())
}
pub fn contains_encoding<T: 'static + Copy + Encode>(&self) -> bool {
if let Some(encoding) = self.encoding() {
T::ENCODING.equivalent_to_str(encoding)
} else {
panic!("missing NSValue encoding");
}
}
#[sel(isEqualToValue:)]
fn is_equal_to_value(&self, other: &Self) -> bool;
}
);
unsafe impl NSCopying for NSValue {
type Ownership = Shared;
type Output = NSValue;
}
impl alloc::borrow::ToOwned for NSValue {
type Owned = Id<NSValue, Shared>;
fn to_owned(&self) -> Self::Owned {
self.copy()
}
}
impl PartialEq for NSValue {
#[doc(alias = "isEqualToValue:")]
fn eq(&self, other: &Self) -> bool {
// Use isEqualToValue: instaed of isEqual: since it is faster
self.is_equal_to_value(other)
}
}
impl hash::Hash for NSValue {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// Delegate to NSObject
(**self).hash(state)
}
}
impl fmt::Debug for NSValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let enc = self.encoding().unwrap_or("(NULL)");
let bytes = &**self; // Delegate to -[NSObject description]
f.debug_struct("NSValue")
.field("encoding", &enc)
.field("bytes", bytes)
.finish()
}
}
#[cfg(test)]
mod tests {
use alloc::format;
use core::{ptr, slice};
use super::*;
use crate::rc::{RcTestObject, ThreadTestData};
#[test]
fn basic() {
let val = NSValue::new(13u32);
assert_eq!(unsafe { val.get::<u32>() }, 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);
let val2 = NSValue::new(123u32);
assert_eq!(val1, val1);
assert_eq!(val1, val2);
let val3 = NSValue::new(456u32);
assert_ne!(val1, val3);
}
#[test]
fn test_equality_across_types() {
let val1 = NSValue::new(123i32);
let val2 = NSValue::new(123u32);
// Test that `objCType` is checked when comparing equality
assert_ne!(val1, val2);
}
#[test]
#[ignore = "the debug output changes depending on OS version"]
fn test_debug() {
let expected = if cfg!(feature = "gnustep-1-7") {
r#"NSValue { encoding: "C", bytes: (C) <ab> }"#
} else if cfg!(newer_apple) {
r#"NSValue { encoding: "C", bytes: {length = 1, bytes = 0xab} }"#
} else {
r#"NSValue { encoding: "C", bytes: <ab> }"#
};
assert_eq!(format!("{:?}", NSValue::new(171u8)), expected);
}
#[test]
fn nsrange() {
let range = NSRange::from(1..2);
let val = NSValue::new(range);
assert_eq!(val.get_range(), Some(range));
assert_eq!(val.get_point(), None);
assert_eq!(val.get_size(), None);
assert_eq!(val.get_rect(), None);
// NSValue -getValue is broken on GNUStep for some types
#[cfg(not(feature = "gnustep-1-7"))]
assert_eq!(unsafe { val.get::<NSRange>() }, range);
}
#[test]
fn nspoint() {
let point = NSPoint::new(1.0, 2.0);
let val = NSValue::new(point);
assert_eq!(val.get_point(), Some(point));
#[cfg(not(feature = "gnustep-1-7"))]
assert_eq!(unsafe { val.get::<NSPoint>() }, point);
}
#[test]
fn nssize() {
let point = NSSize::new(1.0, 2.0);
let val = NSValue::new(point);
assert_eq!(val.get_size(), Some(point));
#[cfg(not(feature = "gnustep-1-7"))]
assert_eq!(unsafe { val.get::<NSSize>() }, point);
}
#[test]
fn nsrect() {
let rect = NSRect::new(NSPoint::new(1.0, 2.0), NSSize::new(3.0, 4.0));
let val = NSValue::new(rect);
assert_eq!(val.get_rect(), Some(rect));
#[cfg(not(feature = "gnustep-1-7"))]
assert_eq!(unsafe { val.get::<NSRect>() }, rect);
}
#[test]
fn store_str() {
let s = "abc";
let val = NSValue::new(s.as_ptr());
assert!(val.contains_encoding::<*const u8>());
let slice = unsafe { slice::from_raw_parts(val.get(), s.len()) };
let s2 = str::from_utf8(slice).unwrap();
assert_eq!(s2, s);
}
#[test]
fn store_cstr() {
// The following Apple article says that NSValue can't easily store
// C-strings, but apparently that doesn't apply to us!
// <https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/NumbersandValues/Articles/Values.html#//apple_ref/doc/uid/20000174-BAJJHDEG>
let s = CStr::from_bytes_with_nul(b"test123\0").unwrap();
let val = NSValue::new(s.as_ptr());
assert!(val.contains_encoding::<*const c_char>());
let s2 = unsafe { CStr::from_ptr(val.get()) };
assert_eq!(s2, s);
}
}

View file

@ -0,0 +1,95 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};
use crate::ffi;
#[cfg(feature = "gnustep-1-7")]
use crate::Encode;
use crate::{Encoding, RefEncode};
/// A type used to identify and manage memory zones.
///
/// Zones are ignored on all newer platforms, you should very rarely need to
/// use this, but may be useful if you need to implement `copyWithZone:` or
/// `allocWithZone:`.
///
/// See [Apple's documentation](https://developer.apple.com/documentation/foundation/nszone?language=objc).
pub struct NSZone {
// Use `objc_object` to mark the types as !Send, !Sync and UnsafeCell.
//
// This works since `objc_object` is a ZST
_inner: ffi::objc_object,
}
impl fmt::Debug for NSZone {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<NSZone {:p}>", self)
}
}
// Note: We don't know anything about the internals of `NSZone`, so best not
// to make it `Send` and `Sync` for now.
impl UnwindSafe for NSZone {}
impl RefUnwindSafe for NSZone {}
unsafe impl RefEncode for NSZone {
#[cfg(feature = "apple")]
const ENCODING_REF: Encoding = Encoding::Pointer(&Encoding::Struct("_NSZone", &[]));
#[cfg(feature = "gnustep-1-7")]
const ENCODING_REF: Encoding = Encoding::Pointer(&Encoding::Struct(
"_NSZone",
&[
// Functions
Encoding::Pointer(&Encoding::Unknown),
Encoding::Pointer(&Encoding::Unknown),
Encoding::Pointer(&Encoding::Unknown),
Encoding::Pointer(&Encoding::Unknown),
Encoding::Pointer(&Encoding::Unknown),
Encoding::Pointer(&Encoding::Unknown),
// Stats
Encoding::Pointer(&Encoding::Unknown),
// Zone granularity
usize::ENCODING,
// Name of zone
Encoding::Object,
// Next zone - note that the contents of this doesn't matter,
// since this is nested far enough that the encoding string just
// ends up ignoring it.
Encoding::Pointer(&Encoding::Struct("_NSZone", &[])),
],
));
}
#[cfg(test)]
mod tests {
use alloc::string::ToString;
use core::ptr;
use super::*;
use crate::foundation::NSObject;
use crate::msg_send_id;
use crate::rc::{Allocated, Id, Owned};
use crate::ClassType;
#[test]
fn alloc_with_zone() {
let zone: *const NSZone = ptr::null();
let _obj: Id<Allocated<NSObject>, Owned> =
unsafe { msg_send_id![NSObject::class(), allocWithZone: zone] };
}
#[test]
fn verify_encoding() {
let expected = if cfg!(all(feature = "gnustep-1-7", target_pointer_width = "64")) {
"^{_NSZone=^?^?^?^?^?^?^?Q@^{_NSZone}}"
} else if cfg!(all(
feature = "gnustep-1-7",
not(target_pointer_width = "64")
)) {
"^{_NSZone=^?^?^?^?^?^?^?I@^{_NSZone}}"
} else {
"^{_NSZone=}"
};
assert_eq!(NSZone::ENCODING_REF.to_string(), expected);
}
}