From 0895312e121a570edafb23234224f33f8922d638 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 9 Aug 2022 15:53:24 +0200 Subject: [PATCH 1/3] Add `extern_methods!` macro --- objc2/CHANGELOG.md | 1 + objc2/src/macros.rs | 1 + objc2/src/macros/declare_class.rs | 7 +- objc2/src/macros/extern_methods.rs | 546 +++++++++++++++++++++++++++++ 4 files changed, 552 insertions(+), 3 deletions(-) create mode 100644 objc2/src/macros/extern_methods.rs diff --git a/objc2/CHANGELOG.md b/objc2/CHANGELOG.md index ce04d888e..278418bfa 100644 --- a/objc2/CHANGELOG.md +++ b/objc2/CHANGELOG.md @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). means you'll have to `use objc2::ClassType` whenever you want to use e.g. `NSData::class()`. * Added `Id::into_superclass`. +* Added `extern_methods!` macro. ### Changed * **BREAKING**: Change syntax in `extern_class!` macro to be more Rust-like. diff --git a/objc2/src/macros.rs b/objc2/src/macros.rs index 7e67b1815..7a9d0c416 100644 --- a/objc2/src/macros.rs +++ b/objc2/src/macros.rs @@ -1,6 +1,7 @@ mod __rewrite_self_arg; mod declare_class; mod extern_class; +mod extern_methods; /// Gets a reference to a [`Class`] from the given name. /// diff --git a/objc2/src/macros/declare_class.rs b/objc2/src/macros/declare_class.rs index c45381134..d4de25db3 100644 --- a/objc2/src/macros/declare_class.rs +++ b/objc2/src/macros/declare_class.rs @@ -179,15 +179,16 @@ macro_rules! __inner_declare_class { /// instance method, and if you don't it will be registered as a class method. /// /// The desired selector can be specified using the `#[sel(my:selector:)]` -/// attribute. +/// attribute, similar to the [`extern_methods!`] macro. /// /// A transformation step is performed on the functions (to make them have the /// correct ABI) and hence they shouldn't really be called manually. (You -/// can't mark them as `pub` for the same reason). Instead, define a new -/// function that calls it via. [`msg_send!`]. +/// can't mark them as `pub` for the same reason). Instead, use the +/// [`extern_methods!`] macro to create a Rust interface to the methods. /// /// ["associated functions"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods /// ["methods"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods +/// [`extern_methods!`]: crate::extern_methods /// [`msg_send!`]: crate::msg_send /// /// diff --git a/objc2/src/macros/extern_methods.rs b/objc2/src/macros/extern_methods.rs new file mode 100644 index 000000000..d1a1a3508 --- /dev/null +++ b/objc2/src/macros/extern_methods.rs @@ -0,0 +1,546 @@ +/// Define methods on an external class. +/// +/// This is a convenience macro to easily generate associated functions and +/// methods that call [`msg_send!`][crate::msg_send] appropriately. +/// +/// +/// # Specification +/// +/// Within the `impl` block you can define two types of functions without +/// bodies; ["associated functions"] and ["methods"]. These are then mapped to +/// the Objective-C equivalents "class methods" and "instance methods", and an +/// appropriate body is created for you. In particular, if you use `self` your +/// method will assumbed to be an instance method, and if you don't it will be +/// assumed to be a class method. +/// +/// The desired selector can be specified using the `#[sel(my:selector:)]` +/// attribute. The name of the function doesn't matter. +/// +/// If you specify a function/method with a body, the macro will simply ignore +/// it. +/// +/// ["associated functions"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods +/// ["methods"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods +/// +/// +/// # Safety +/// +/// You must ensure that any methods you declare with the `#[sel(...)]` +/// attribute upholds the safety guarantees decribed in the +/// [`msg_send!`][crate::msg_send] macro, _or_ are marked `unsafe`. +/// +/// +/// # Examples +/// +/// Let's create a quick interface to the [`NSCalendar`] class: +/// +/// [`NSCalendar`]: https://developer.apple.com/documentation/foundation/nscalendar?language=objc +/// +/// ``` +/// use objc2::foundation::{NSObject, NSRange, NSString, NSUInteger}; +/// use objc2::rc::{Id, Shared}; +/// use objc2::runtime::Bool; +/// use objc2::{extern_class, extern_methods, msg_send_id, Encode, Encoding, ClassType}; +/// # +/// # #[cfg(feature = "gnustep-1-7")] +/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() }; +/// +/// extern_class!( +/// #[derive(PartialEq, Eq, Hash)] +/// pub struct NSCalendar; +/// +/// unsafe impl ClassType for NSCalendar { +/// type Superclass = NSObject; +/// } +/// ); +/// +/// pub type NSCalendarIdentifier = NSString; +/// +/// #[repr(usize)] // NSUInteger +/// pub enum NSCalendarUnit { +/// Hour = 32, +/// Minute = 64, +/// Second = 128, +/// // TODO: More units +/// } +/// +/// unsafe impl Encode for NSCalendarUnit { +/// const ENCODING: Encoding<'static> = usize::ENCODING; +/// } +/// +/// extern_methods!( +/// /// Creation methods. +/// // TODO: Support methods returning `Id` +/// unsafe impl NSCalendar { +/// pub fn current() -> Id { +/// unsafe { msg_send_id![Self::class(), currentCalendar].expect("unexpected NULL NSCalendar") } +/// } +/// +/// pub fn new(identifier: &NSCalendarIdentifier) -> Id { +/// unsafe { +/// msg_send_id![ +/// msg_send_id![Self::class(), alloc], +/// initWithCalendarIdentifier: identifier, +/// ] +/// .expect("failed initializing NSCalendar") +/// } +/// } +/// } +/// +/// /// Accessor methods. +/// // SAFETY: `first_weekday` is correctly defined +/// // TODO: Support methods returning `bool` +/// unsafe impl NSCalendar { +/// #[sel(firstWeekday)] +/// pub fn first_weekday(&self) -> NSUInteger; +/// +/// pub fn am_symbol(&self) -> Id { +/// unsafe { msg_send_id![self, amSymbol].expect("unexpected NULL AM symbol") } +/// } +/// +/// #[sel(date:matchesComponents:)] +/// // `unsafe` because we don't have definitions for `NSDate` and +/// // `NSDateComponents` yet, so the user must ensure that is what's +/// // passed. +/// pub unsafe fn date_matches_raw(&self, date: &NSObject, components: &NSObject) -> Bool; +/// +/// pub unsafe fn date_matches(&self, date: &NSObject, components: &NSObject) -> bool { +/// self.date_matches_raw(date, components).as_bool() +/// } +/// +/// #[sel(maximumRangeOfUnit:)] +/// pub fn max_range(&self, unit: NSCalendarUnit) -> NSRange; +/// } +/// ); +/// ``` +/// +/// The `extern_methods!` declaration then becomes: +/// +/// ``` +/// # use objc2::foundation::{NSObject, NSRange, NSString, NSUInteger}; +/// # use objc2::rc::{Id, Shared}; +/// # use objc2::runtime::Bool; +/// # use objc2::{extern_class, extern_methods, msg_send_id, Encode, Encoding, ClassType}; +/// # +/// # #[cfg(feature = "gnustep-1-7")] +/// # unsafe { objc2::__gnustep_hack::get_class_to_force_linkage() }; +/// # +/// # extern_class!( +/// # #[derive(PartialEq, Eq, Hash)] +/// # pub struct NSCalendar; +/// # +/// # unsafe impl ClassType for NSCalendar { +/// # type Superclass = NSObject; +/// # } +/// # ); +/// # +/// # pub type NSCalendarIdentifier = NSString; +/// # +/// # #[repr(usize)] // NSUInteger +/// # pub enum NSCalendarUnit { +/// # Hour = 32, +/// # Minute = 64, +/// # Second = 128, +/// # // TODO: More units +/// # } +/// # +/// # unsafe impl Encode for NSCalendarUnit { +/// # const ENCODING: Encoding<'static> = usize::ENCODING; +/// # } +/// # +/// # use objc2::msg_send; +/// /// Creation methods. +/// impl NSCalendar { +/// pub fn current() -> Id { +/// unsafe { msg_send_id![Self::class(), currentCalendar].expect("unexpected NULL NSCalendar") } +/// } +/// +/// pub fn new(identifier: &NSCalendarIdentifier) -> Id { +/// unsafe { +/// msg_send_id![ +/// msg_send_id![Self::class(), alloc], +/// initWithCalendarIdentifier: identifier, +/// ] +/// .expect("failed initializing NSCalendar") +/// } +/// } +/// } +/// +/// /// Accessor methods. +/// impl NSCalendar { +/// pub fn first_weekday(&self) -> NSUInteger { +/// unsafe { msg_send![self, firstWeekday] } +/// } +/// +/// pub fn am_symbol(&self) -> Id { +/// unsafe { msg_send_id![self, amSymbol].expect("unexpected NULL AM symbol") } +/// } +/// +/// pub unsafe fn date_matches_raw(&self, date: &NSObject, components: &NSObject) -> Bool { +/// unsafe { msg_send![self, date: date, matchesComponents: components] } +/// } +/// +/// pub unsafe fn date_matches(&self, date: &NSObject, components: &NSObject) -> bool { +/// self.date_matches_raw(date, components).as_bool() +/// } +/// +/// pub fn max_range(&self, unit: NSCalendarUnit) -> NSRange { +/// unsafe { msg_send![self, maximumRangeOfUnit: unit] } +/// } +/// } +/// ``` +#[macro_export] +macro_rules! extern_methods { + ( + $( + $(#[$impl_m:meta])* + unsafe impl<$($t:ident $(: $b:ident $(+ $rest:ident)*)?),*> $type:ty { + $($methods:tt)* + } + )+ + ) => { + $( + $(#[$impl_m])* + impl<$($t $(: $b $(+ $rest)*)?),*> $type { + $crate::__inner_extern_methods! { + @rewrite_methods + $($methods)* + } + } + )+ + }; + ( + $( + $(#[$impl_m:meta])* + unsafe impl $type:ty { + $($methods:tt)* + } + )+ + ) => { + $( + $(#[$impl_m])* + impl $type { + $crate::__inner_extern_methods! { + @rewrite_methods + $($methods)* + } + } + )+ + }; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __inner_extern_methods { + {@rewrite_methods} => {}; + { + @rewrite_methods + // Unsafe variant + $(#[$($m:tt)*])* + $v:vis unsafe fn $name:ident($($args:tt)*) $(-> $ret:ty)?; + + $($rest:tt)* + } => { + // Detect instance vs. class method. + $crate::__rewrite_self_arg! { + ($crate::__inner_extern_methods) + ($($args)*) + @method_out + @($(#[$($m)*])*) + @($v unsafe fn $name($($args)*) $(-> $ret)?) + } + + $crate::__inner_extern_methods! { + @rewrite_methods + $($rest)* + } + }; + { + @rewrite_methods + // Safe variant + $(#[$($m:tt)*])* + $v:vis fn $name:ident($($args:tt)*) $(-> $ret:ty)?; + + $($rest:tt)* + } => { + $crate::__rewrite_self_arg! { + ($crate::__inner_extern_methods) + ($($args)*) + @method_out + @($(#[$($m)*])*) + @($v fn $name($($args)*) $(-> $ret)?) + } + + $crate::__inner_extern_methods! { + @rewrite_methods + $($rest)* + } + }; + { + @rewrite_methods + // Other items that people might want to put here (e.g. functions with + // a body). + $associated_item:item + + $($rest:tt)* + } => { + $associated_item + + $crate::__inner_extern_methods! { + @rewrite_methods + $($rest)* + } + }; + { + @method_out + @($(#[$($m:tt)*])*) + @($($function_start:tt)*) + @($($kind:tt)*) + @($($args:tt)*) + } => { + $crate::__attribute_helper! { + @strip_sel + $(@[$($m)*])* + ($($function_start)* { + #[allow(unused_unsafe)] + unsafe { + $crate::__attribute_helper! { + @extract_sel + ($crate::__inner_extern_methods) + ($(#[$($m)*])*) + @unsafe_method_body + @($($kind)*) + @($($args)*) + } + } + }) + } + }; + + { + @unsafe_method_body + @(instance_method) + @( + $self:ident: $self_ty:ty, + _: $sel_ty:ty, + $($arg:ident: $arg_ty:ty),* $(,)? + ) + @($($sel:tt)*) + } => { + $crate::__collect_msg_send!( + $crate::msg_send; + $self; + ($($sel)*); + ($($arg),*); + ) + }; + { + @unsafe_method_body + @(class_method) + @( + _: $cls_ty:ty, + _: $sel_ty:ty, + $($arg:ident: $arg_ty:ty),* $(,)? + ) + @($($sel:tt)*) + } => { + $crate::__collect_msg_send!( + $crate::msg_send; + Self::class(); + ($($sel)*); + ($($arg),*); + ) + }; +} + +/// Zip selector and arguments, and forward to macro. +/// +/// TODO: Investigate if there's a better way of doing this. +#[doc(hidden)] +#[macro_export] +macro_rules! __collect_msg_send { + ( + $macro:path; + $obj:expr; + ($s:ident); + (); + ) => {{ + $macro![$obj, $s] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident:); + ($a1:expr); + ) => {{ + $macro![$obj, $s1: $a1] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident: $s2:ident:); + ($a1:expr, $a2:expr); + ) => {{ + $macro![$obj, $s1: $a1, $s2: $a2] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident: $s2:ident: $s3:ident:); + ($a1:expr, $a2:expr, $a3:expr); + ) => {{ + $macro![$obj, $s1: $a1, $s2: $a2, $s3: $a3] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident: $s2:ident: $s3:ident: $s4:ident:); + ($a1:expr, $a2:expr, $a3:expr, $a4:expr); + ) => {{ + $macro![$obj, $s1: $a1, $s2: $a2, $s3: $a3, $s4: $a4] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident: $s2:ident: $s3:ident: $s4:ident: $s5:ident:); + ($a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr); + ) => {{ + $macro![$obj, $s1: $a1, $s2: $a2, $s3: $a3, $s4: $a4, $s5: $a5] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident: $s2:ident: $s3:ident: $s4:ident: $s5:ident: $s6:ident:); + ($a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr); + ) => {{ + $macro![ + $obj, + $s1: $a1, + $s2: $a2, + $s3: $a3, + $s4: $a4, + $s5: $a5, + $s6: $a6 + ] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident: $s2:ident: $s3:ident: $s4:ident: $s5:ident: $s6:ident: $s7:ident:); + ($a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr, $a7:expr); + ) => {{ + $macro![ + $obj, + $s1: $a1, + $s2: $a2, + $s3: $a3, + $s4: $a4, + $s5: $a5, + $s6: $a6, + $s7: $a7 + ] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident: $s2:ident: $s3:ident: $s4:ident: $s5:ident: $s6:ident: $s7:ident: $s8:ident:); + ($a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr, $a7:expr, $a8:expr); + ) => {{ + $macro![ + $obj, + $s1: $a1, + $s2: $a2, + $s3: $a3, + $s4: $a4, + $s5: $a5, + $s6: $a6, + $s7: $a7, + $s8: $a8 + ] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident: $s2:ident: $s3:ident: $s4:ident: $s5:ident: $s6:ident: $s7:ident: $s8:ident: $s9:ident:); + ($a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr, $a7:expr, $a8:expr, $a9:expr); + ) => {{ + $macro![ + $obj, + $s1: $a1, + $s2: $a2, + $s3: $a3, + $s4: $a4, + $s5: $a5, + $s6: $a6, + $s7: $a7, + $s8: $a8, + $s9: $a9 + ] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident: $s2:ident: $s3:ident: $s4:ident: $s5:ident: $s6:ident: $s7:ident: $s8:ident: $s9:ident: $s10:ident:); + ($a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr, $a7:expr, $a8:expr, $a9:expr, $a10:expr); + ) => {{ + $macro![ + $obj, + $s1: $a1, + $s2: $a2, + $s3: $a3, + $s4: $a4, + $s5: $a5, + $s6: $a6, + $s7: $a7, + $s8: $a8, + $s9: $a9, + $s10: $a10 + ] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident: $s2:ident: $s3:ident: $s4:ident: $s5:ident: $s6:ident: $s7:ident: $s8:ident: $s9:ident: $s10:ident: $s11:ident:); + ($a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr, $a7:expr, $a8:expr, $a9:expr, $a10:expr, $a11:expr); + ) => {{ + $macro![ + $obj, + $s1: $a1, + $s2: $a2, + $s3: $a3, + $s4: $a4, + $s5: $a5, + $s6: $a6, + $s7: $a7, + $s8: $a8, + $s9: $a9, + $s10: $a10, + $s11: $a11 + ] + }}; + ( + $macro:path; + $obj:expr; + ($s1:ident: $s2:ident: $s3:ident: $s4:ident: $s5:ident: $s6:ident: $s7:ident: $s8:ident: $s9:ident: $s10:ident: $s11:ident: $s12:ident:); + ($a1:expr, $a2:expr, $a3:expr, $a4:expr, $a5:expr, $a6:expr, $a7:expr, $a8:expr, $a9:expr, $a10:expr, $a11:expr, $a12:expr); + ) => {{ + $macro![ + $obj, + $s1: $a1, + $s2: $a2, + $s3: $a3, + $s4: $a4, + $s5: $a5, + $s6: $a6, + $s7: $a7, + $s8: $a8, + $s9: $a9, + $s10: $a10, + $s11: $a11, + $s12: $a12 + ] + }}; + ($($_any:tt)*) => {{ + compile_error!("Number of arguments in function and selector did not match!") + }}; +} From da73029fce1031a1102cf72bbac09aa00e200080 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 9 Aug 2022 14:53:52 +0200 Subject: [PATCH 2/3] Wrap Foundation impls in `extern_methods!` macro Just a simple manual wrapping; we don't use the macro's functionality yet! --- objc2/src/foundation/array.rs | 250 ++++++++------- objc2/src/foundation/attributed_string.rs | 124 +++---- objc2/src/foundation/data.rs | 82 ++--- objc2/src/foundation/dictionary.rs | 189 +++++------ objc2/src/foundation/error.rs | 132 ++++---- objc2/src/foundation/exception.rs | 151 ++++----- objc2/src/foundation/mutable_array.rs | 226 ++++++------- .../foundation/mutable_attributed_string.rs | 66 ++-- objc2/src/foundation/mutable_data.rs | 150 ++++----- objc2/src/foundation/mutable_string.rs | 98 +++--- objc2/src/foundation/object.rs | 56 ++-- objc2/src/foundation/process_info.rs | 22 +- objc2/src/foundation/string.rs | 303 +++++++++--------- objc2/src/foundation/thread.rs | 55 ++-- objc2/src/foundation/uuid.rs | 60 ++-- objc2/src/foundation/value.rs | 262 +++++++-------- 16 files changed, 1132 insertions(+), 1094 deletions(-) diff --git a/objc2/src/foundation/array.rs b/objc2/src/foundation/array.rs index 936ded83c..0683c85ad 100644 --- a/objc2/src/foundation/array.rs +++ b/objc2/src/foundation/array.rs @@ -10,7 +10,7 @@ use super::{ }; use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId}; use crate::runtime::{Class, Object}; -use crate::{ClassType, Message, __inner_extern_class, msg_send, msg_send_id}; +use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id}; __inner_extern_class!( /// An immutable ordered collection of objects. @@ -88,154 +88,156 @@ pub(crate) unsafe fn with_objects } } -/// Generic creation methods. -impl NSArray { - /// Get an empty array. - pub fn new() -> Id { - // SAFETY: - // - `new` may not create a new object, but instead return a shared - // instance. We remedy this by returning `Id`. - // - `O` don't actually matter here! E.g. `NSArray` is - // perfectly legal, since the array doesn't have any elements, and - // hence the notion of ownership over the elements is void. - unsafe { msg_send_id![Self::class(), new].expect("unexpected NULL NSArray") } - } +extern_methods!( + /// Generic creation methods. + unsafe impl NSArray { + /// Get an empty array. + pub fn new() -> Id { + // SAFETY: + // - `new` may not create a new object, but instead return a shared + // instance. We remedy this by returning `Id`. + // - `O` don't actually matter here! E.g. `NSArray` is + // perfectly legal, since the array doesn't have any elements, and + // hence the notion of ownership over the elements is void. + unsafe { msg_send_id![Self::class(), new].expect("unexpected NULL NSArray") } + } - pub fn from_vec(vec: Vec>) -> Id { - // SAFETY: - // `initWithObjects:` may choose to deduplicate arrays (I could - // imagine it having a special case for arrays with one `NSNumber` - // object), and returning mutable references to those would be - // unsound! - // However, when we know that we have ownership over the variables, we - // also know that there cannot be another array in existence with the - // same objects, so `Id, Owned>` is safe to return. - // - // In essence, we can choose between always returning `Id` - // or `Id`, and the latter is probably the most useful, as we - // would like to know when we're the only owner of the array, to - // allow mutation of the array's items. - unsafe { with_objects(Self::class(), vec.as_slice_ref()) } + pub fn from_vec(vec: Vec>) -> Id { + // SAFETY: + // `initWithObjects:` may choose to deduplicate arrays (I could + // imagine it having a special case for arrays with one `NSNumber` + // object), and returning mutable references to those would be + // unsound! + // However, when we know that we have ownership over the variables, we + // also know that there cannot be another array in existence with the + // same objects, so `Id, Owned>` is safe to return. + // + // In essence, we can choose between always returning `Id` + // or `Id`, and the latter is probably the most useful, as we + // would like to know when we're the only owner of the array, to + // allow mutation of the array's items. + unsafe { with_objects(Self::class(), vec.as_slice_ref()) } + } } -} -/// Creation methods that produce shared arrays. -impl NSArray { - pub fn from_slice(slice: &[Id]) -> Id { - // SAFETY: Taking `&T` would not be sound, since the `&T` could come - // from an `Id` that would now no longer be owned! - // - // (Note that NSArray internally retains all the objects it is given, - // effectively making the safety requirements the same as - // `Id::retain`). - unsafe { with_objects(Self::class(), slice.as_slice_ref()) } + /// Creation methods that produce shared arrays. + unsafe impl NSArray { + pub fn from_slice(slice: &[Id]) -> Id { + // SAFETY: Taking `&T` would not be sound, since the `&T` could come + // from an `Id` that would now no longer be owned! + // + // (Note that NSArray internally retains all the objects it is given, + // effectively making the safety requirements the same as + // `Id::retain`). + unsafe { with_objects(Self::class(), slice.as_slice_ref()) } + } } -} -/// Generic accessor methods. -impl NSArray { - #[doc(alias = "count")] - pub fn len(&self) -> usize { - unsafe { msg_send![self, count] } - } + /// Generic accessor methods. + unsafe impl NSArray { + #[doc(alias = "count")] + pub fn len(&self) -> usize { + unsafe { msg_send![self, count] } + } - pub fn is_empty(&self) -> bool { - self.len() == 0 - } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } - #[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 { msg_send![self, objectAtIndex: index] }) - } else { - None + #[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 { msg_send![self, objectAtIndex: index] }) + } else { + None + } } - } - #[doc(alias = "firstObject")] - pub fn first(&self) -> Option<&T> { - unsafe { msg_send![self, firstObject] } - } + #[doc(alias = "firstObject")] + pub fn first(&self) -> Option<&T> { + unsafe { msg_send![self, firstObject] } + } - #[doc(alias = "lastObject")] - pub fn last(&self) -> Option<&T> { - unsafe { msg_send![self, lastObject] } - } + #[doc(alias = "lastObject")] + pub fn last(&self) -> Option<&T> { + unsafe { msg_send![self, lastObject] } + } - #[doc(alias = "objectEnumerator")] - pub fn iter(&self) -> NSEnumerator<'_, T> { - unsafe { - let result: *mut Object = msg_send![self, objectEnumerator]; - NSEnumerator::from_ptr(result) + #[doc(alias = "objectEnumerator")] + pub fn iter(&self) -> NSEnumerator<'_, T> { + unsafe { + let result: *mut Object = msg_send![self, objectEnumerator]; + NSEnumerator::from_ptr(result) + } } - } - pub fn objects_in_range(&self, range: Range) -> Vec<&T> { - let range = NSRange::from(range); - let mut vec = Vec::with_capacity(range.length); - unsafe { - let _: () = msg_send![self, getObjects: vec.as_ptr(), range: range]; - vec.set_len(range.length); + pub fn objects_in_range(&self, range: Range) -> Vec<&T> { + let range = NSRange::from(range); + let mut vec = Vec::with_capacity(range.length); + unsafe { + let _: () = msg_send![self, getObjects: vec.as_ptr(), range: range]; + vec.set_len(range.length); + } + vec } - vec - } - pub fn to_vec(&self) -> Vec<&T> { - self.objects_in_range(0..self.len()) - } + pub fn to_vec(&self) -> Vec<&T> { + self.objects_in_range(0..self.len()) + } - // TODO: Take Id ? - pub fn into_vec(array: Id) -> Vec> { - array - .to_vec() - .into_iter() - .map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() }) - .collect() + // TODO: Take Id ? + pub fn into_vec(array: Id) -> Vec> { + 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. -impl NSArray { - #[doc(alias = "objectAtIndex:")] - pub fn get_retained(&self, index: usize) -> Id { - 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() } - } + /// Accessor methods that work on shared arrays. + unsafe impl NSArray { + #[doc(alias = "objectAtIndex:")] + pub fn get_retained(&self, index: usize) -> Id { + 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> { - self.to_vec() - .into_iter() - .map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() }) - .collect() + pub fn to_shared_vec(&self) -> Vec> { + 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. -impl NSArray { - #[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 + /// Accessor methods that work on owned arrays. + unsafe impl NSArray { + #[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")] - pub fn first_mut(&mut self) -> Option<&mut T> { - unsafe { msg_send![self, firstObject] } - } + #[doc(alias = "firstObject")] + pub fn first_mut(&mut self) -> Option<&mut T> { + unsafe { msg_send![self, firstObject] } + } - #[doc(alias = "lastObject")] - pub fn last_mut(&mut self) -> Option<&mut T> { - unsafe { msg_send![self, lastObject] } + #[doc(alias = "lastObject")] + pub fn last_mut(&mut self) -> Option<&mut T> { + unsafe { msg_send![self, lastObject] } + } } -} +); /// This is implemented as a shallow copy. /// diff --git a/objc2/src/foundation/attributed_string.rs b/objc2/src/foundation/attributed_string.rs index 5600fe676..713d8cd76 100644 --- a/objc2/src/foundation/attributed_string.rs +++ b/objc2/src/foundation/attributed_string.rs @@ -6,7 +6,7 @@ use super::{ }; use crate::rc::{DefaultId, Id, Shared}; use crate::runtime::Object; -use crate::{extern_class, msg_send, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType}; extern_class!( /// A string that has associated attributes for portions of its text. @@ -41,74 +41,76 @@ impl RefUnwindSafe for NSAttributedString {} /// Attributes that you can apply to text in an attributed string. pub type NSAttributedStringKey = NSString; -/// Creating attributed strings. -impl NSAttributedString { - /// Construct an empty attributed string. - pub fn new() -> Id { - unsafe { msg_send_id![Self::class(), new].unwrap() } - } +extern_methods!( + /// Creating attributed strings. + unsafe impl NSAttributedString { + /// Construct an empty attributed string. + pub fn new() -> Id { + unsafe { msg_send_id![Self::class(), new].unwrap() } + } - /// 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, - ) -> Id { - unsafe { - let obj = msg_send_id![Self::class(), alloc]; - msg_send_id![obj, initWithString: string, attributes: attributes].unwrap() + /// 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, + ) -> Id { + unsafe { + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithString: string, attributes: attributes].unwrap() + } } - } - /// Creates a new attributed string without any attributes. - #[doc(alias = "initWithString:")] - pub fn from_nsstring(string: &NSString) -> Id { - unsafe { - let obj = msg_send_id![Self::class(), alloc]; - msg_send_id![obj, initWithString: string].unwrap() + /// Creates a new attributed string without any attributes. + #[doc(alias = "initWithString:")] + pub fn from_nsstring(string: &NSString) -> Id { + unsafe { + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithString: string].unwrap() + } } } -} -/// Querying. -impl NSAttributedString { - // TODO: Lifetimes? - pub fn string(&self) -> Id { - unsafe { msg_send_id![self, string].unwrap() } - } + /// Querying. + unsafe impl NSAttributedString { + // TODO: Lifetimes? + pub fn string(&self) -> Id { + unsafe { msg_send_id![self, string].unwrap() } + } - /// Alias for `self.string().len_utf16()`. - #[doc(alias = "length")] - #[allow(unused)] - // TODO: Finish this - fn len_utf16(&self) -> usize { - unsafe { msg_send![self, length] } - } + /// Alias for `self.string().len_utf16()`. + #[doc(alias = "length")] + #[allow(unused)] + // TODO: Finish this + fn len_utf16(&self) -> usize { + unsafe { msg_send![self, length] } + } - // /// 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, - // ) -> Id { - // let range = NSRange::from(range); - // todo!() - // } - // - // attributesAtIndex:longestEffectiveRange:inRange: - - // TODO: attributedSubstringFromRange: - // TODO: enumerateAttributesInRange:options:usingBlock: -} + // /// 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, + // ) -> Id { + // let range = NSRange::from(range); + // todo!() + // } + // + // attributesAtIndex:longestEffectiveRange:inRange: + + // TODO: attributedSubstringFromRange: + // TODO: enumerateAttributesInRange:options:usingBlock: + } +); impl DefaultId for NSAttributedString { type Ownership = Shared; diff --git a/objc2/src/foundation/data.rs b/objc2/src/foundation/data.rs index 151fb0d1b..6ea265216 100644 --- a/objc2/src/foundation/data.rs +++ b/objc2/src/foundation/data.rs @@ -9,7 +9,7 @@ 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, msg_send, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType}; extern_class!( /// A static byte buffer in memory. @@ -33,52 +33,54 @@ unsafe impl Send for NSData {} impl UnwindSafe for NSData {} impl RefUnwindSafe for NSData {} -impl NSData { - pub fn new() -> Id { - unsafe { msg_send_id![Self::class(), new].unwrap() } - } +extern_methods!( + unsafe impl NSData { + pub fn new() -> Id { + unsafe { msg_send_id![Self::class(), new].unwrap() } + } - #[doc(alias = "length")] - pub fn len(&self) -> usize { - unsafe { msg_send![self, length] } - } + #[doc(alias = "length")] + pub fn len(&self) -> usize { + unsafe { msg_send![self, length] } + } - pub fn is_empty(&self) -> bool { - self.len() == 0 - } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } - pub fn bytes(&self) -> &[u8] { - let ptr: *const c_void = unsafe { msg_send![self, bytes] }; - 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()) } + pub fn bytes(&self) -> &[u8] { + let ptr: *const c_void = unsafe { msg_send![self, bytes] }; + 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()) } + } } - } - pub fn with_bytes(bytes: &[u8]) -> Id { - unsafe { Id::cast(with_slice(Self::class(), bytes)) } - } + pub fn with_bytes(bytes: &[u8]) -> Id { + unsafe { Id::cast(with_slice(Self::class(), bytes)) } + } - #[cfg(feature = "block")] - pub fn from_vec(bytes: Vec) -> Id { - // 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)) } + #[cfg(feature = "block")] + pub fn from_vec(bytes: Vec) -> Id { + // 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)) } + } } -} +); unsafe impl NSCopying for NSData { type Ownership = Shared; diff --git a/objc2/src/foundation/dictionary.rs b/objc2/src/foundation/dictionary.rs index 2ec444b9c..2724e04e1 100644 --- a/objc2/src/foundation/dictionary.rs +++ b/objc2/src/foundation/dictionary.rs @@ -8,7 +8,7 @@ use core::ptr; use super::{NSArray, NSCopying, NSEnumerator, NSFastEnumeration, NSObject}; use crate::rc::{DefaultId, Id, Owned, Shared, SliceId}; -use crate::{ClassType, __inner_extern_class, msg_send, msg_send_id, Message}; +use crate::{ClassType, __inner_extern_class, extern_methods, msg_send, msg_send_id, Message}; __inner_extern_class!( #[derive(PartialEq, Eq, Hash)] @@ -30,117 +30,118 @@ unsafe impl Send for NSDictionary` impl UnwindSafe for NSDictionary {} impl RefUnwindSafe for NSDictionary {} +extern_methods!( + unsafe impl NSDictionary { + pub fn new() -> Id { + unsafe { msg_send_id![Self::class(), new].unwrap() } + } -impl NSDictionary { - pub fn new() -> Id { - unsafe { msg_send_id![Self::class(), new].unwrap() } - } - - #[doc(alias = "count")] - pub fn len(&self) -> usize { - unsafe { msg_send![self, count] } - } + #[doc(alias = "count")] + pub fn len(&self) -> usize { + unsafe { msg_send![self, count] } + } - pub fn is_empty(&self) -> bool { - self.len() == 0 - } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } - #[doc(alias = "objectForKey:")] - pub fn get(&self, key: &K) -> Option<&V> { - unsafe { msg_send![self, objectForKey: key] } - } + #[doc(alias = "objectForKey:")] + pub fn get(&self, key: &K) -> Option<&V> { + unsafe { msg_send![self, objectForKey: key] } + } - #[doc(alias = "getObjects:andKeys:")] - pub fn keys(&self) -> Vec<&K> { - let len = self.len(); - let mut keys = Vec::with_capacity(len); - unsafe { - let _: () = msg_send![ - self, - getObjects: ptr::null_mut::<&V>(), - andKeys: keys.as_mut_ptr(), - ]; - keys.set_len(len); + #[doc(alias = "getObjects:andKeys:")] + pub fn keys(&self) -> Vec<&K> { + let len = self.len(); + let mut keys = Vec::with_capacity(len); + unsafe { + let _: () = msg_send![ + self, + getObjects: ptr::null_mut::<&V>(), + andKeys: keys.as_mut_ptr(), + ]; + keys.set_len(len); + } + keys } - keys - } - #[doc(alias = "getObjects:andKeys:")] - pub fn values(&self) -> Vec<&V> { - let len = self.len(); - let mut vals = Vec::with_capacity(len); - unsafe { - let _: () = msg_send![ - self, - getObjects: vals.as_mut_ptr(), - andKeys: ptr::null_mut::<&K>(), - ]; - vals.set_len(len); + #[doc(alias = "getObjects:andKeys:")] + pub fn values(&self) -> Vec<&V> { + let len = self.len(); + let mut vals = Vec::with_capacity(len); + unsafe { + let _: () = msg_send![ + self, + getObjects: vals.as_mut_ptr(), + andKeys: ptr::null_mut::<&K>(), + ]; + vals.set_len(len); + } + vals } - 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 { - let _: () = msg_send![ - self, - getObjects: objs.as_mut_ptr(), - andKeys: keys.as_mut_ptr(), - ]; - keys.set_len(len); - objs.set_len(len); + #[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 { + let _: () = msg_send![ + self, + getObjects: objs.as_mut_ptr(), + andKeys: keys.as_mut_ptr(), + ]; + keys.set_len(len); + objs.set_len(len); + } + (keys, objs) } - (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 = "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) + #[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, Shared> { - unsafe { msg_send_id![self, allKeys] }.unwrap() - } + pub fn keys_array(&self) -> Id, Shared> { + unsafe { msg_send_id![self, allKeys] }.unwrap() + } - pub fn from_keys_and_objects(keys: &[&T], vals: Vec>) -> Id - where - T: NSCopying, - { - 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 from_keys_and_objects(keys: &[&T], vals: Vec>) -> Id + where + T: NSCopying, + { + 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, + ] + } + .unwrap() } - .unwrap() - } - pub fn into_values_array(dict: Id) -> Id, Shared> { - unsafe { msg_send_id![&dict, allValues] }.unwrap() + pub fn into_values_array(dict: Id) -> Id, Shared> { + unsafe { msg_send_id![&dict, allValues] }.unwrap() + } } -} +); impl DefaultId for NSDictionary { type Ownership = Shared; diff --git a/objc2/src/foundation/error.rs b/objc2/src/foundation/error.rs index 832bc5cd3..7321b8ae6 100644 --- a/objc2/src/foundation/error.rs +++ b/objc2/src/foundation/error.rs @@ -4,7 +4,7 @@ use core::panic::{RefUnwindSafe, UnwindSafe}; use super::{NSCopying, NSDictionary, NSObject, NSString}; use crate::ffi::NSInteger; use crate::rc::{Id, Shared}; -use crate::{extern_class, msg_send, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType}; extern_class!( /// Information about an error condition including a domain, a @@ -33,81 +33,83 @@ impl RefUnwindSafe for NSError {} pub type NSErrorUserInfoKey = NSString; pub type NSErrorDomain = NSString; -/// Creation methods. -impl NSError { - /// Construct a new [`NSError`] with the given code in the given domain. - pub fn new(code: NSInteger, domain: &NSString) -> Id { - unsafe { Self::with_user_info(code, domain, None) } - } +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 { + 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>, - ) -> Id { - // 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, - ] - .expect("unexpected NULL NSError") + // TODO: Figure out safety of `user_info` dict! + unsafe fn with_user_info( + code: NSInteger, + domain: &NSString, + user_info: Option<&NSDictionary>, + ) -> Id { + // 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, + ] + .expect("unexpected NULL NSError") + } } } -} -/// Accessor methods. -impl NSError { - pub fn domain(&self) -> Id { - unsafe { msg_send_id![self, domain].expect("unexpected NULL NSError domain") } - } + /// Accessor methods. + unsafe impl NSError { + pub fn domain(&self) -> Id { + unsafe { msg_send_id![self, domain].expect("unexpected NULL NSError domain") } + } - pub fn code(&self) -> NSInteger { - unsafe { msg_send![self, code] } - } + pub fn code(&self) -> NSInteger { + unsafe { msg_send![self, code] } + } - pub fn user_info(&self) -> Option, Shared>> { - unsafe { msg_send_id![self, userInfo] } - } + pub fn user_info(&self) -> Option, Shared>> { + unsafe { msg_send_id![self, userInfo] } + } - pub fn localized_description(&self) -> Id { - unsafe { - msg_send_id![self, localizedDescription].expect( - "unexpected NULL localized description; a default should have been generated!", - ) + pub fn localized_description(&self) -> Id { + unsafe { + msg_send_id![self, localizedDescription].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; + // 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 } } - unsafe { VALUE } - } - // TODO: Other NSErrorUserInfoKey values - // TODO: NSErrorDomain values -} + // TODO: Other NSErrorUserInfoKey values + // TODO: NSErrorDomain values + } +); impl fmt::Debug for NSError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/objc2/src/foundation/exception.rs b/objc2/src/foundation/exception.rs index a46ff6525..993893844 100644 --- a/objc2/src/foundation/exception.rs +++ b/objc2/src/foundation/exception.rs @@ -6,7 +6,7 @@ use super::{NSCopying, NSDictionary, NSObject, NSString}; use crate::exception::Exception; use crate::rc::{Id, Shared}; use crate::runtime::Object; -use crate::{extern_class, msg_send, msg_send_id, sel, ClassType}; +use crate::{extern_class, extern_methods, msg_send, msg_send_id, sel, ClassType}; extern_class!( /// A special condition that interrupts the normal flow of program @@ -35,90 +35,91 @@ 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>, + ) -> Option> { + let obj = unsafe { msg_send_id![Self::class(), alloc] }; + unsafe { msg_send_id![obj, initWithName: name, reason: reason, userInfo: user_info] } + } -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>, - ) -> Option> { - let obj = unsafe { msg_send_id![Self::class(), alloc] }; - unsafe { msg_send_id![obj, initWithName: name, reason: reason, userInfo: user_info] } - } - - /// 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. - let _: () = unsafe { msg_send![self, raise] }; - unsafe { unreachable_unchecked() } - } + /// 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. + let _: () = unsafe { msg_send![self, raise] }; + 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 { - // Nullability not documented, but a name is expected in most places. - unsafe { msg_send_id![self, name].expect("unexpected NULL NSException name") } - } + /// 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 { + // Nullability not documented, but a name is expected in most places. + unsafe { msg_send_id![self, name].expect("unexpected NULL NSException name") } + } - /// A human-readable message summarizing the reason for the exception. - pub fn reason(&self) -> Option> { - unsafe { msg_send_id![self, reason] } - } + /// A human-readable message summarizing the reason for the exception. + pub fn reason(&self) -> Option> { + unsafe { msg_send_id![self, reason] } + } - /// Application-specific data pertaining to the exception. - pub fn user_info(&self) -> Option, Shared>> { - unsafe { msg_send_id![self, userInfo] } - } + /// Application-specific data pertaining to the exception. + pub fn user_info(&self) -> Option, Shared>> { + unsafe { msg_send_id![self, userInfo] } + } - /// Convert this into an [`Exception`] object. - pub fn into_exception(this: Id) -> Id { - // SAFETY: Downcasting to "subclass" - unsafe { Id::cast(this) } - } + /// Convert this into an [`Exception`] object. + pub fn into_exception(this: Id) -> Id { + // 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::().as_ref().unwrap() }; - obj.is_kind_of::() - } else { - false + 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::().as_ref().unwrap() }; + obj.is_kind_of::() + } 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, - ) -> Result, Id> { - if Self::is_nsexception(&obj) { - // SAFETY: Just checked the object is an NSException - Ok(unsafe { Id::cast::(obj) }) - } else { - Err(obj) + /// 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, + ) -> Result, Id> { + if Self::is_nsexception(&obj) { + // SAFETY: Just checked the object is an NSException + Ok(unsafe { Id::cast::(obj) }) + } else { + Err(obj) + } } } -} +); unsafe impl NSCopying for NSException { type Ownership = Shared; diff --git a/objc2/src/foundation/mutable_array.rs b/objc2/src/foundation/mutable_array.rs index 92e3831ac..7fcff0eda 100644 --- a/objc2/src/foundation/mutable_array.rs +++ b/objc2/src/foundation/mutable_array.rs @@ -11,7 +11,7 @@ use super::{ NSObject, }; use crate::rc::{DefaultId, Id, Owned, Ownership, Shared, SliceId}; -use crate::{ClassType, Message, __inner_extern_class, msg_send, msg_send_id}; +use crate::{ClassType, Message, __inner_extern_class, extern_methods, msg_send, msg_send_id}; __inner_extern_class!( /// A growable ordered collection of objects. @@ -39,135 +39,137 @@ unsafe impl Send for NSMutableArray {} unsafe impl Sync for NSMutableArray {} unsafe impl Send for NSMutableArray {} -/// Generic creation methods. -impl NSMutableArray { - pub fn new() -> Id { - // SAFETY: Same as `NSArray::new`, except mutable arrays are always - // unique. - unsafe { msg_send_id![Self::class(), new].expect("unexpected NULL NSMutableArray") } - } - - pub fn from_vec(vec: Vec>) -> Id { - // SAFETY: Same as `NSArray::from_vec`, except mutable arrays are - // always unique. - unsafe { with_objects(Self::class(), vec.as_slice_ref()) } - } -} - -/// Creation methods that produce shared arrays. -impl NSMutableArray { - pub fn from_slice(slice: &[Id]) -> Id { - // SAFETY: Same as `NSArray::from_slice`, except mutable arrays are - // always unique. - unsafe { with_objects(Self::class(), slice.as_slice_ref()) } - } -} - -/// Generic accessor methods. -impl NSMutableArray { - #[doc(alias = "addObject:")] - pub fn push(&mut self, obj: Id) { - // SAFETY: The object is not nil - unsafe { msg_send![self, addObject: &*obj] } - } +extern_methods!( + /// Generic creation methods. + unsafe impl NSMutableArray { + pub fn new() -> Id { + // SAFETY: Same as `NSArray::new`, except mutable arrays are always + // unique. + unsafe { msg_send_id![Self::class(), new].expect("unexpected NULL NSMutableArray") } + } - #[doc(alias = "insertObject:atIndex:")] - pub fn insert(&mut self, index: usize, obj: Id) { - // TODO: Replace this check with catching the thrown NSRangeException - let len = self.len(); - if index < len { - // SAFETY: The object is not nil and the index is checked to be in - // bounds. - unsafe { msg_send![self, insertObject: &*obj, atIndex: index] } - } else { - panic!( - "insertion index (is {}) should be <= len (is {})", - index, len - ); + pub fn from_vec(vec: Vec>) -> Id { + // SAFETY: Same as `NSArray::from_vec`, except mutable arrays are + // always unique. + unsafe { with_objects(Self::class(), vec.as_slice_ref()) } } } - #[doc(alias = "replaceObjectAtIndex:withObject:")] - pub fn replace(&mut self, index: usize, obj: Id) -> Id { - 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, - ]; + /// Creation methods that produce shared arrays. + unsafe impl NSMutableArray { + pub fn from_slice(slice: &[Id]) -> Id { + // SAFETY: Same as `NSArray::from_slice`, except mutable arrays are + // always unique. + unsafe { with_objects(Self::class(), slice.as_slice_ref()) } } - old_obj } - #[doc(alias = "removeObjectAtIndex:")] - pub fn remove(&mut self, index: usize) -> Id { - 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 { - let _: () = msg_send![self, removeObjectAtIndex: index]; + /// Generic accessor methods. + unsafe impl NSMutableArray { + #[doc(alias = "addObject:")] + pub fn push(&mut self, obj: Id) { + // SAFETY: The object is not nil + unsafe { msg_send![self, addObject: &*obj] } } - obj - } - fn remove_last(&mut self) { - unsafe { msg_send![self, removeLastObject] } - } + #[doc(alias = "insertObject:atIndex:")] + pub fn insert(&mut self, index: usize, obj: Id) { + // TODO: Replace this check with catching the thrown NSRangeException + let len = self.len(); + if index < len { + // SAFETY: The object is not nil and the index is checked to be in + // bounds. + unsafe { msg_send![self, insertObject: &*obj, atIndex: index] } + } else { + panic!( + "insertion index (is {}) should be <= len (is {})", + index, len + ); + } + } - #[doc(alias = "removeLastObject")] - pub fn pop(&mut self) -> Option> { - self.last() - .map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() }) - .map(|obj| { - self.remove_last(); - obj - }) - } + #[doc(alias = "replaceObjectAtIndex:withObject:")] + pub fn replace(&mut self, index: usize, obj: Id) -> Id { + 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 + } - #[doc(alias = "removeAllObjects")] - pub fn clear(&mut self) { - unsafe { msg_send![self, removeAllObjects] } - } + #[doc(alias = "removeObjectAtIndex:")] + pub fn remove(&mut self, index: usize) -> Id { + 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 { + let _: () = msg_send![self, removeObjectAtIndex: index]; + } + obj + } - #[doc(alias = "sortUsingFunction:context:")] - pub fn sort_by Ordering>(&mut self, compare: F) { - // TODO: "C-unwind" - extern "C" fn compare_with_closure 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::().as_mut().unwrap_unchecked() }; - - NSComparisonResult::from((*closure)(obj1, obj2)) + fn remove_last(&mut self) { + unsafe { msg_send![self, removeLastObject] } } - // 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::; + #[doc(alias = "removeLastObject")] + pub fn pop(&mut self) -> Option> { + self.last() + .map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() }) + .map(|obj| { + self.remove_last(); + obj + }) + } - // 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(); + #[doc(alias = "removeAllObjects")] + pub fn clear(&mut self) { + unsafe { msg_send![self, removeAllObjects] } + } - unsafe { - let _: () = msg_send![self, sortUsingFunction: f, context: context]; + #[doc(alias = "sortUsingFunction:context:")] + pub fn sort_by Ordering>(&mut self, compare: F) { + // TODO: "C-unwind" + extern "C" fn compare_with_closure 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::().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::; + + // 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); } - // Keep the closure alive until the function has run. - drop(closure); } -} +); // Copying only possible when ItemOwnership = Shared diff --git a/objc2/src/foundation/mutable_attributed_string.rs b/objc2/src/foundation/mutable_attributed_string.rs index 1156656cb..6f85624e6 100644 --- a/objc2/src/foundation/mutable_attributed_string.rs +++ b/objc2/src/foundation/mutable_attributed_string.rs @@ -2,7 +2,7 @@ use core::fmt; use super::{NSAttributedString, NSCopying, NSMutableCopying, NSObject, NSString}; use crate::rc::{DefaultId, Id, Owned, Shared}; -use crate::{extern_class, msg_send, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType}; extern_class!( /// A mutable string that has associated attributes. @@ -17,45 +17,47 @@ extern_class!( } ); -/// Creating mutable attributed strings. -impl NSMutableAttributedString { - /// Construct an empty mutable attributed string. - pub fn new() -> Id { - unsafe { msg_send_id![Self::class(), new].unwrap() } - } +extern_methods!( + /// Creating mutable attributed strings. + unsafe impl NSMutableAttributedString { + /// Construct an empty mutable attributed string. + pub fn new() -> Id { + unsafe { msg_send_id![Self::class(), new].unwrap() } + } - // TODO: new_with_attributes + // TODO: new_with_attributes - #[doc(alias = "initWithString:")] - pub fn from_nsstring(string: &NSString) -> Id { - unsafe { - let obj = msg_send_id![Self::class(), alloc]; - msg_send_id![obj, initWithString: string].unwrap() + #[doc(alias = "initWithString:")] + pub fn from_nsstring(string: &NSString) -> Id { + unsafe { + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithString: string].unwrap() + } } - } - #[doc(alias = "initWithAttributedString:")] - pub fn from_attributed_nsstring(attributed_string: &NSAttributedString) -> Id { - unsafe { - let obj = msg_send_id![Self::class(), alloc]; - msg_send_id![obj, initWithAttributedString: attributed_string].unwrap() + #[doc(alias = "initWithAttributedString:")] + pub fn from_attributed_nsstring(attributed_string: &NSAttributedString) -> Id { + unsafe { + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithAttributedString: attributed_string].unwrap() + } } } -} -/// Modifying the attributed string. -impl NSMutableAttributedString { - // TODO - // - mutableString - // - replaceCharactersInRange:withString: - // - setAttributes:range: - - /// Replaces the entire attributed string. - #[doc(alias = "setAttributedString:")] - pub fn replace(&mut self, attributed_string: &NSAttributedString) { - unsafe { msg_send![self, setAttributedString: attributed_string] } + /// Modifying the attributed string. + unsafe impl NSMutableAttributedString { + // TODO + // - mutableString + // - replaceCharactersInRange:withString: + // - setAttributes:range: + + /// Replaces the entire attributed string. + #[doc(alias = "setAttributedString:")] + pub fn replace(&mut self, attributed_string: &NSAttributedString) { + unsafe { msg_send![self, setAttributedString: attributed_string] } + } } -} +); impl DefaultId for NSMutableAttributedString { type Ownership = Owned; diff --git a/objc2/src/foundation/mutable_data.rs b/objc2/src/foundation/mutable_data.rs index db73a56ad..38f2268e7 100644 --- a/objc2/src/foundation/mutable_data.rs +++ b/objc2/src/foundation/mutable_data.rs @@ -9,7 +9,7 @@ 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, msg_send, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType}; extern_class!( /// A dynamic byte buffer in memory. @@ -28,97 +28,99 @@ extern_class!( } ); -/// Creation methods -impl NSMutableData { - pub fn new() -> Id { - unsafe { msg_send_id![Self::class(), new].unwrap() } - } +extern_methods!( + /// Creation methods + unsafe impl NSMutableData { + pub fn new() -> Id { + unsafe { msg_send_id![Self::class(), new].unwrap() } + } - pub fn with_bytes(bytes: &[u8]) -> Id { - unsafe { Id::from_shared(Id::cast(with_slice(Self::class(), bytes))) } - } + pub fn with_bytes(bytes: &[u8]) -> Id { + unsafe { Id::from_shared(Id::cast(with_slice(Self::class(), bytes))) } + } - #[cfg(feature = "block")] - pub fn from_vec(bytes: Vec) -> Id { - unsafe { Id::from_shared(Id::cast(super::data::with_vec(Self::class(), bytes))) } - } + #[cfg(feature = "block")] + pub fn from_vec(bytes: Vec) -> Id { + unsafe { Id::from_shared(Id::cast(super::data::with_vec(Self::class(), bytes))) } + } - // TODO: Use malloc_buf/mbox and `initWithBytesNoCopy:...`? + // TODO: Use malloc_buf/mbox and `initWithBytesNoCopy:...`? - #[doc(alias = "initWithData:")] - pub fn from_data(data: &NSData) -> Id { - // 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].unwrap() + #[doc(alias = "initWithData:")] + pub fn from_data(data: &NSData) -> Id { + // 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].unwrap() + } } - } - #[doc(alias = "initWithCapacity:")] - pub fn with_capacity(capacity: usize) -> Id { - unsafe { - let obj = msg_send_id![Self::class(), alloc]; - msg_send_id![obj, initWithCapacity: capacity].unwrap() + #[doc(alias = "initWithCapacity:")] + pub fn with_capacity(capacity: usize) -> Id { + unsafe { + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithCapacity: capacity].unwrap() + } } } -} -/// Mutation methods -impl NSMutableData { - // Helps with reborrowing issue - fn raw_bytes_mut(&mut self) -> *mut c_void { - unsafe { msg_send![self, mutableBytes] } - } + /// Mutation methods + unsafe impl NSMutableData { + // Helps with reborrowing issue + fn raw_bytes_mut(&mut self) -> *mut c_void { + unsafe { msg_send![self, mutableBytes] } + } - #[doc(alias = "mutableBytes")] - pub fn bytes_mut(&mut self) -> &mut [u8] { - let ptr = self.raw_bytes_mut(); - 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()) } + #[doc(alias = "mutableBytes")] + pub fn bytes_mut(&mut self) -> &mut [u8] { + let ptr = self.raw_bytes_mut(); + 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()) } + } } - } - /// Expands with zeroes, or truncates the buffer. - #[doc(alias = "setLength:")] - pub fn set_len(&mut self, len: usize) { - unsafe { msg_send![self, setLength: len] } - } + /// Expands with zeroes, or truncates the buffer. + #[doc(alias = "setLength:")] + pub fn set_len(&mut self, len: usize) { + unsafe { msg_send![self, setLength: len] } + } - #[doc(alias = "appendBytes:length:")] - pub fn extend_from_slice(&mut self, bytes: &[u8]) { - let bytes_ptr: *const c_void = bytes.as_ptr().cast(); - unsafe { msg_send![self, appendBytes: bytes_ptr, length: bytes.len()] } - } + #[doc(alias = "appendBytes:length:")] + pub fn extend_from_slice(&mut self, bytes: &[u8]) { + let bytes_ptr: *const c_void = bytes.as_ptr().cast(); + unsafe { msg_send![self, appendBytes: bytes_ptr, length: bytes.len()] } + } - pub fn push(&mut self, byte: u8) { - self.extend_from_slice(&[byte]) - } + pub fn push(&mut self, byte: u8) { + self.extend_from_slice(&[byte]) + } - #[doc(alias = "replaceBytesInRange:withBytes:length:")] - pub fn replace_range(&mut self, range: Range, 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 { - msg_send![ - self, - replaceBytesInRange: range, - withBytes: ptr, - length: bytes.len(), - ] + #[doc(alias = "replaceBytesInRange:withBytes:length:")] + pub fn replace_range(&mut self, range: Range, 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 { + msg_send![ + self, + replaceBytesInRange: range, + withBytes: ptr, + length: bytes.len(), + ] + } } - } - pub fn set_bytes(&mut self, bytes: &[u8]) { - let len = self.len(); - self.replace_range(0..len, bytes); + 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; diff --git a/objc2/src/foundation/mutable_string.rs b/objc2/src/foundation/mutable_string.rs index 6f52983ef..9ed630aaa 100644 --- a/objc2/src/foundation/mutable_string.rs +++ b/objc2/src/foundation/mutable_string.rs @@ -5,7 +5,7 @@ use core::str; use super::{NSCopying, NSMutableCopying, NSObject, NSString}; use crate::rc::{DefaultId, Id, Owned, Shared}; -use crate::{extern_class, msg_send, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType}; extern_class!( /// A dynamic plain-text Unicode string object. @@ -20,63 +20,65 @@ extern_class!( } ); -/// Creating mutable strings. -impl NSMutableString { - /// Construct an empty [`NSMutableString`]. - pub fn new() -> Id { - unsafe { msg_send_id![Self::class(), new].unwrap() } - } +extern_methods!( + /// Creating mutable strings. + unsafe impl NSMutableString { + /// Construct an empty [`NSMutableString`]. + pub fn new() -> Id { + unsafe { msg_send_id![Self::class(), new].unwrap() } + } - /// 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 { - unsafe { - let obj = super::string::from_str(Self::class(), string); - Id::new(obj.cast()).unwrap() + /// 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 { + 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 { - unsafe { - let obj = msg_send_id![Self::class(), alloc]; - msg_send_id![obj, initWithString: string].unwrap() + /// Creates a new [`NSMutableString`] from the given [`NSString`]. + #[doc(alias = "initWithString:")] + pub fn from_nsstring(string: &NSString) -> Id { + unsafe { + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithString: string].unwrap() + } } - } - #[doc(alias = "initWithCapacity:")] - pub fn with_capacity(capacity: usize) -> Id { - unsafe { - let obj = msg_send_id![Self::class(), alloc]; - msg_send_id![obj, initWithCapacity: capacity].unwrap() + #[doc(alias = "initWithCapacity:")] + pub fn with_capacity(capacity: usize) -> Id { + unsafe { + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithCapacity: capacity].unwrap() + } } } -} -/// Mutating strings. -impl NSMutableString { - /// Appends the given [`NSString`] onto the end of this. - #[doc(alias = "appendString:")] - pub fn push_nsstring(&mut self, nsstring: &NSString) { - // SAFETY: The string is not nil - unsafe { msg_send![self, appendString: nsstring] } - } + /// Mutating strings. + unsafe impl NSMutableString { + /// Appends the given [`NSString`] onto the end of this. + #[doc(alias = "appendString:")] + pub fn push_nsstring(&mut self, nsstring: &NSString) { + // SAFETY: The string is not nil + unsafe { msg_send![self, appendString: nsstring] } + } - /// Replaces the entire string. - #[doc(alias = "setString:")] - pub fn replace(&mut self, nsstring: &NSString) { - // SAFETY: The string is not nil - unsafe { msg_send![self, setString: nsstring] } - } + /// Replaces the entire string. + #[doc(alias = "setString:")] + pub fn replace(&mut self, nsstring: &NSString) { + // SAFETY: The string is not nil + unsafe { msg_send![self, setString: nsstring] } + } - // TODO: - // - deleteCharactersInRange: - // - replaceCharactersInRange:withString: - // - insertString:atIndex: - // Figure out how these work on character boundaries -} + // TODO: + // - deleteCharactersInRange: + // - replaceCharactersInRange:withString: + // - insertString:atIndex: + // Figure out how these work on character boundaries + } +); impl DefaultId for NSMutableString { type Ownership = Owned; diff --git a/objc2/src/foundation/object.rs b/objc2/src/foundation/object.rs index b3ed54461..47d091895 100644 --- a/objc2/src/foundation/object.rs +++ b/objc2/src/foundation/object.rs @@ -4,7 +4,9 @@ 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, msg_send, msg_send_bool, msg_send_id}; +use crate::{ + ClassType, __inner_extern_class, class, extern_methods, msg_send, msg_send_bool, msg_send_id, +}; __inner_extern_class! { @__inner @@ -24,34 +26,36 @@ unsafe impl ClassType for NSObject { } } -impl NSObject { - pub fn new() -> Id { - unsafe { msg_send_id![Self::class(), new].unwrap() } - } +extern_methods!( + unsafe impl NSObject { + pub fn new() -> Id { + unsafe { msg_send_id![Self::class(), new].unwrap() } + } - fn is_kind_of_inner(&self, cls: &Class) -> bool { - unsafe { msg_send_bool![self, isKindOfClass: cls] } - } + fn is_kind_of_inner(&self, cls: &Class) -> bool { + unsafe { msg_send_bool![self, isKindOfClass: cls] } + } - /// 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(&self) -> bool { - self.is_kind_of_inner(T::class()) - } + /// 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(&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. -} + // 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). diff --git a/objc2/src/foundation/process_info.rs b/objc2/src/foundation/process_info.rs index a6a312c5b..d3d1c3713 100644 --- a/objc2/src/foundation/process_info.rs +++ b/objc2/src/foundation/process_info.rs @@ -3,7 +3,7 @@ use core::panic::{RefUnwindSafe, UnwindSafe}; use super::{NSObject, NSString}; use crate::rc::{Id, Shared}; -use crate::{extern_class, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send_id, ClassType}; extern_class!( /// A collection of information about the current process. @@ -25,17 +25,19 @@ unsafe impl Sync for NSProcessInfo {} impl UnwindSafe for NSProcessInfo {} impl RefUnwindSafe for NSProcessInfo {} -impl NSProcessInfo { - pub fn process_info() -> Id { - unsafe { msg_send_id![Self::class(), processInfo].unwrap() } - } +extern_methods!( + unsafe impl NSProcessInfo { + pub fn process_info() -> Id { + unsafe { msg_send_id![Self::class(), processInfo].unwrap() } + } - pub fn process_name(&self) -> Id { - unsafe { msg_send_id![self, processName].unwrap() } - } + pub fn process_name(&self) -> Id { + unsafe { msg_send_id![self, processName].unwrap() } + } - // TODO: This contains a lot more important functionality! -} + // TODO: This contains a lot more important functionality! + } +); impl fmt::Debug for NSProcessInfo { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/objc2/src/foundation/string.rs b/objc2/src/foundation/string.rs index 302dbcce0..71bd457ef 100644 --- a/objc2/src/foundation/string.rs +++ b/objc2/src/foundation/string.rs @@ -13,7 +13,7 @@ use super::{NSComparisonResult, NSCopying, NSMutableCopying, NSMutableString, NS use crate::ffi; use crate::rc::{autoreleasepool, AutoreleasePool, DefaultId, Id, Shared}; use crate::runtime::{Class, Object}; -use crate::{extern_class, msg_send, msg_send_bool, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send, msg_send_bool, msg_send_id, ClassType}; #[cfg(feature = "apple")] const UTF8_ENCODING: usize = 4; @@ -54,170 +54,173 @@ unsafe impl Send for NSString {} impl UnwindSafe for NSString {} impl RefUnwindSafe for NSString {} -impl NSString { - /// Construct an empty NSString. - pub fn new() -> Id { - unsafe { msg_send_id![Self::class(), new].unwrap() } - } +extern_methods!( + unsafe impl NSString { + /// Construct an empty NSString. + pub fn new() -> Id { + unsafe { msg_send_id![Self::class(), new].unwrap() } + } - /// 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-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 `self`. - /// - /// See also [`NSString::len`]. - #[doc(alias = "length")] - // TODO: Finish this - fn len_utf16(&self) -> usize { - unsafe { msg_send![self, length] } - } + /// The number of UTF-16 code units in `self`. + /// + /// See also [`NSString::len`]. + #[doc(alias = "length")] + // TODO: Finish this + fn len_utf16(&self) -> usize { + unsafe { msg_send![self, length] } + } - pub fn is_empty(&self) -> bool { - // TODO: lengthOfBytesUsingEncoding: might sometimes return 0 for - // other reasons, so this is not really correct! - self.len() == 0 - } + 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; + /// 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() + }) } - 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; + /// 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()) }) } - 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`! - } + /// 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(); - // TODO: Allow usecases where the NUL byte from `UTF8String` is kept? + // 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() - /// 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 { - unsafe { - let obj = from_str(Self::class(), string); - Id::new(obj.cast()).unwrap() + // NOTE: Please keep up to date with `objc2::exception`! } - } - // 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 + // 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 { + unsafe { + let obj = from_str(Self::class(), string); + Id::new(obj.cast()).unwrap() + } + } - /// 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:")] - pub fn has_prefix(&self, prefix: &NSString) -> bool { - unsafe { msg_send_bool![self, hasPrefix: prefix] } - } + // 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:")] + pub fn has_prefix(&self, prefix: &NSString) -> bool { + unsafe { msg_send_bool![self, hasPrefix: prefix] } + } - /// 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:")] - pub fn has_suffix(&self, suffix: &NSString) -> bool { - unsafe { msg_send_bool![self, hasSuffix: suffix] } - } + /// 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:")] + pub fn has_suffix(&self, suffix: &NSString) -> bool { + unsafe { msg_send_bool![self, hasSuffix: suffix] } + } - // pub fn from_nsrange(range: NSRange) -> Id - // https://developer.apple.com/documentation/foundation/1415155-nsstringfromrange?language=objc -} + // pub fn from_nsrange(range: NSRange) -> Id + // 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(); diff --git a/objc2/src/foundation/thread.rs b/objc2/src/foundation/thread.rs index a719dd0e0..3760f21f5 100644 --- a/objc2/src/foundation/thread.rs +++ b/objc2/src/foundation/thread.rs @@ -4,7 +4,7 @@ use core::panic::{RefUnwindSafe, UnwindSafe}; use super::{NSObject, NSString}; use crate::rc::{Id, Shared}; -use crate::{extern_class, msg_send, msg_send_bool, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send, msg_send_bool, msg_send_id, ClassType}; extern_class!( /// A thread of execution. @@ -24,37 +24,40 @@ unsafe impl Sync for NSThread {} impl UnwindSafe for NSThread {} impl RefUnwindSafe for NSThread {} -impl NSThread { - /// Returns the [`NSThread`] object representing the current thread. - pub fn current() -> Id { - unsafe { msg_send_id![Self::class(), currentThread].unwrap() } - } +extern_methods!( + unsafe impl NSThread { + /// Returns the [`NSThread`] object representing the current thread. + pub fn current() -> Id { + unsafe { msg_send_id![Self::class(), currentThread].unwrap() } + } - /// Returns the [`NSThread`] object representing the main thread. - pub fn main() -> Id { - // The main thread static may not have been initialized - // This can at least fail in GNUStep! - unsafe { msg_send_id![Self::class(), mainThread] }.expect("Could not retrieve main thread.") - } + /// Returns the [`NSThread`] object representing the main thread. + pub fn main() -> Id { + // The main thread static may not have been initialized + // This can at least fail in GNUStep! + unsafe { msg_send_id![Self::class(), mainThread] } + .expect("Could not retrieve main thread.") + } - /// Returns `true` if the thread is the main thread. - pub fn is_main(&self) -> bool { - unsafe { msg_send_bool![self, isMainThread] } - } + /// Returns `true` if the thread is the main thread. + pub fn is_main(&self) -> bool { + unsafe { msg_send_bool![self, isMainThread] } + } - /// The name of the thread. - pub fn name(&self) -> Option> { - unsafe { msg_send_id![self, name] } - } + /// The name of the thread. + pub fn name(&self) -> Option> { + unsafe { msg_send_id![self, name] } + } - unsafe fn new() -> Id { - unsafe { msg_send_id![Self::class(), new] }.unwrap() - } + unsafe fn new() -> Id { + unsafe { msg_send_id![Self::class(), new] }.unwrap() + } - unsafe fn start(&self) { - unsafe { msg_send![self, start] } + unsafe fn start(&self) { + unsafe { msg_send![self, start] } + } } -} +); impl fmt::Debug for NSThread { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/objc2/src/foundation/uuid.rs b/objc2/src/foundation/uuid.rs index 9ccc34485..9eb9492e4 100644 --- a/objc2/src/foundation/uuid.rs +++ b/objc2/src/foundation/uuid.rs @@ -3,7 +3,9 @@ use core::panic::{RefUnwindSafe, UnwindSafe}; use super::{NSCopying, NSObject, NSString}; use crate::rc::{DefaultId, Id, Shared}; -use crate::{extern_class, msg_send, msg_send_id, ClassType, Encode, Encoding, RefEncode}; +use crate::{ + extern_class, extern_methods, msg_send, msg_send_id, ClassType, Encode, Encoding, RefEncode, +}; extern_class!( /// A universally unique value. @@ -43,41 +45,43 @@ unsafe impl Send for NSUUID {} impl UnwindSafe for NSUUID {} impl RefUnwindSafe for NSUUID {} -impl NSUUID { - pub fn new_v4() -> Id { - unsafe { msg_send_id![Self::class(), new].unwrap() } - } +extern_methods!( + unsafe impl NSUUID { + pub fn new_v4() -> Id { + unsafe { msg_send_id![Self::class(), new].unwrap() } + } - /// The 'nil UUID'. - pub fn nil() -> Id { - Self::from_bytes([0; 16]) - } + /// The 'nil UUID'. + pub fn nil() -> Id { + Self::from_bytes([0; 16]) + } - pub fn from_bytes(bytes: [u8; 16]) -> Id { - let bytes = UuidBytes(bytes); - unsafe { - let obj = msg_send_id![Self::class(), alloc]; - msg_send_id![obj, initWithUUIDBytes: &bytes].unwrap() + pub fn from_bytes(bytes: [u8; 16]) -> Id { + let bytes = UuidBytes(bytes); + unsafe { + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithUUIDBytes: &bytes].unwrap() + } } - } - pub fn from_string(string: &NSString) -> Option> { - unsafe { - let obj = msg_send_id![Self::class(), alloc]; - msg_send_id![obj, initWithUUIDString: string] + pub fn from_string(string: &NSString) -> Option> { + unsafe { + let obj = msg_send_id![Self::class(), alloc]; + msg_send_id![obj, initWithUUIDString: string] + } } - } - pub fn as_bytes(&self) -> [u8; 16] { - let mut bytes = UuidBytes([0; 16]); - let _: () = unsafe { msg_send![self, getUUIDBytes: &mut bytes] }; - bytes.0 - } + pub fn as_bytes(&self) -> [u8; 16] { + let mut bytes = UuidBytes([0; 16]); + let _: () = unsafe { msg_send![self, getUUIDBytes: &mut bytes] }; + bytes.0 + } - pub fn string(&self) -> Id { - unsafe { msg_send_id![self, UUIDString].expect("expected UUID string to be non-NULL") } + pub fn string(&self) -> Id { + unsafe { msg_send_id![self, UUIDString].expect("expected UUID string to be non-NULL") } + } } -} +); impl fmt::Display for NSUUID { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/objc2/src/foundation/value.rs b/objc2/src/foundation/value.rs index 670c96bd8..aa32fc5c1 100644 --- a/objc2/src/foundation/value.rs +++ b/objc2/src/foundation/value.rs @@ -10,7 +10,9 @@ use std::os::raw::c_char; use super::{NSCopying, NSObject, NSPoint, NSRange, NSRect, NSSize}; use crate::rc::{Id, Shared}; -use crate::{extern_class, msg_send, msg_send_bool, msg_send_id, ClassType, Encode}; +use crate::{ + extern_class, extern_methods, msg_send, msg_send_bool, msg_send_id, ClassType, Encode, +}; extern_class!( /// A container wrapping any encodable type as an Obective-C object. @@ -40,154 +42,156 @@ extern_class!( // We can't implement any auto traits for NSValue, since it can contain an // arbitary object! -/// Creation methods. -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::new(1.0, 1.0)); - /// ``` - pub fn new(value: T) -> Id { - 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(), - ] - .expect("unexpected NULL NSValue") +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::new(1.0, 1.0)); + /// ``` + pub fn new(value: T) -> Id { + 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(), + ] + .expect("unexpected NULL NSValue") + } } } -} -/// Getter methods. -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` have - /// the same encoding as `*const T`, but `NonNull` 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(&self) -> T { - debug_assert!( + /// 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` have + /// the same encoding as `*const T`, but `NonNull` 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(&self) -> T { + debug_assert!( self.contains_encoding::(), "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::::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() } - } + let mut value = MaybeUninit::::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 { - if self.contains_encoding::() { - // SAFETY: We just checked that this contains an NSRange - Some(unsafe { msg_send![self, rangeValue] }) - } else { - None + pub fn get_range(&self) -> Option { + if self.contains_encoding::() { + // SAFETY: We just checked that this contains an NSRange + Some(unsafe { msg_send![self, rangeValue] }) + } else { + None + } } - } - pub fn get_point(&self) -> Option { - if self.contains_encoding::() { - // 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_point(&self) -> Option { + if self.contains_encoding::() { + // 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 { - if self.contains_encoding::() { - // SAFETY: We just checked that this contains an NSSize - let res = unsafe { msg_send![self, sizeValue] }; - Some(res) - } else { - None + pub fn get_size(&self) -> Option { + if self.contains_encoding::() { + // 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 { - if self.contains_encoding::() { - // SAFETY: We just checked that this contains an NSRect - let res = unsafe { msg_send![self, rectValue] }; - Some(res) - } else { - None + pub fn get_rect(&self) -> Option { + if self.contains_encoding::() { + // 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> = unsafe { msg_send![self, objCType] }; - result.map(|s| unsafe { CStr::from_ptr(s.as_ptr()) }.to_str().unwrap()) - } + pub fn encoding(&self) -> Option<&str> { + let result: Option> = unsafe { msg_send![self, objCType] }; + result.map(|s| unsafe { CStr::from_ptr(s.as_ptr()) }.to_str().unwrap()) + } - pub fn contains_encoding(&self) -> bool { - if let Some(encoding) = self.encoding() { - T::ENCODING.equivalent_to_str(encoding) - } else { - panic!("missing NSValue encoding"); + pub fn contains_encoding(&self) -> bool { + if let Some(encoding) = self.encoding() { + T::ENCODING.equivalent_to_str(encoding) + } else { + panic!("missing NSValue encoding"); + } } } -} +); unsafe impl NSCopying for NSValue { type Ownership = Shared; From 3f1e53f43e3dcbd61185a58c178c7aff140353c1 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Tue, 9 Aug 2022 16:14:43 +0200 Subject: [PATCH 3/3] Properly use extern_methods! macro --- objc2/src/foundation/array.rs | 35 +++++++------- objc2/src/foundation/attributed_string.rs | 7 ++- objc2/src/foundation/data.rs | 48 +++++++++++-------- objc2/src/foundation/dictionary.rs | 31 ++++-------- objc2/src/foundation/error.rs | 7 ++- objc2/src/foundation/exception.rs | 9 +++- objc2/src/foundation/mutable_array.rs | 20 ++++---- .../foundation/mutable_attributed_string.rs | 7 ++- objc2/src/foundation/mutable_data.rs | 36 +++++++------- objc2/src/foundation/mutable_string.rs | 16 +++---- objc2/src/foundation/object.rs | 10 ++-- objc2/src/foundation/string.rs | 5 +- objc2/src/foundation/thread.rs | 7 ++- objc2/src/foundation/uuid.rs | 9 ++-- 14 files changed, 119 insertions(+), 128 deletions(-) diff --git a/objc2/src/foundation/array.rs b/objc2/src/foundation/array.rs index 0683c85ad..fbd5a6f32 100644 --- a/objc2/src/foundation/array.rs +++ b/objc2/src/foundation/array.rs @@ -136,34 +136,34 @@ extern_methods!( /// Generic accessor methods. unsafe impl NSArray { #[doc(alias = "count")] - pub fn len(&self) -> usize { - unsafe { msg_send![self, 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 { msg_send![self, objectAtIndex: index] }) + Some(unsafe { self.get_unchecked(index) }) } else { None } } #[doc(alias = "firstObject")] - pub fn first(&self) -> Option<&T> { - unsafe { msg_send![self, firstObject] } - } + #[sel(firstObject)] + pub fn first(&self) -> Option<&T>; #[doc(alias = "lastObject")] - pub fn last(&self) -> Option<&T> { - unsafe { msg_send![self, lastObject] } - } + #[sel(lastObject)] + pub fn last(&self) -> Option<&T>; #[doc(alias = "objectEnumerator")] pub fn iter(&self) -> NSEnumerator<'_, T> { @@ -173,11 +173,14 @@ extern_methods!( } } + #[sel(getObjects:range:)] + unsafe fn get_objects(&self, ptr: *mut &T, range: NSRange); + pub fn objects_in_range(&self, range: Range) -> Vec<&T> { let range = NSRange::from(range); let mut vec = Vec::with_capacity(range.length); unsafe { - let _: () = msg_send![self, getObjects: vec.as_ptr(), range: range]; + self.get_objects(vec.as_mut_ptr(), range); vec.set_len(range.length); } vec @@ -228,14 +231,12 @@ extern_methods!( } #[doc(alias = "firstObject")] - pub fn first_mut(&mut self) -> Option<&mut T> { - unsafe { msg_send![self, firstObject] } - } + #[sel(firstObject)] + pub fn first_mut(&mut self) -> Option<&mut T>; #[doc(alias = "lastObject")] - pub fn last_mut(&mut self) -> Option<&mut T> { - unsafe { msg_send![self, lastObject] } - } + #[sel(lastObject)] + pub fn last_mut(&mut self) -> Option<&mut T>; } ); diff --git a/objc2/src/foundation/attributed_string.rs b/objc2/src/foundation/attributed_string.rs index 713d8cd76..dbac09f46 100644 --- a/objc2/src/foundation/attributed_string.rs +++ b/objc2/src/foundation/attributed_string.rs @@ -6,7 +6,7 @@ use super::{ }; use crate::rc::{DefaultId, Id, Shared}; use crate::runtime::Object; -use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send_id, ClassType}; extern_class!( /// A string that has associated attributes for portions of its text. @@ -84,11 +84,10 @@ extern_methods!( /// Alias for `self.string().len_utf16()`. #[doc(alias = "length")] + #[sel(length)] #[allow(unused)] // TODO: Finish this - fn len_utf16(&self) -> usize { - unsafe { msg_send![self, length] } - } + fn len_utf16(&self) -> usize; // /// TODO // /// diff --git a/objc2/src/foundation/data.rs b/objc2/src/foundation/data.rs index 6ea265216..b5f6f57c2 100644 --- a/objc2/src/foundation/data.rs +++ b/objc2/src/foundation/data.rs @@ -9,7 +9,7 @@ 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, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send_id, ClassType}; extern_class!( /// A static byte buffer in memory. @@ -34,31 +34,12 @@ impl UnwindSafe for NSData {} impl RefUnwindSafe for NSData {} extern_methods!( + /// Creation methods. unsafe impl NSData { pub fn new() -> Id { unsafe { msg_send_id![Self::class(), new].unwrap() } } - #[doc(alias = "length")] - pub fn len(&self) -> usize { - unsafe { msg_send![self, length] } - } - - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - pub fn bytes(&self) -> &[u8] { - let ptr: *const c_void = unsafe { msg_send![self, bytes] }; - 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()) } - } - } - pub fn with_bytes(bytes: &[u8]) -> Id { unsafe { Id::cast(with_slice(Self::class(), bytes)) } } @@ -80,6 +61,31 @@ extern_methods!( 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 { diff --git a/objc2/src/foundation/dictionary.rs b/objc2/src/foundation/dictionary.rs index 2724e04e1..8ea7d1c2f 100644 --- a/objc2/src/foundation/dictionary.rs +++ b/objc2/src/foundation/dictionary.rs @@ -37,29 +37,26 @@ extern_methods!( } #[doc(alias = "count")] - pub fn len(&self) -> usize { - unsafe { msg_send![self, count] } - } + #[sel(count)] + pub fn len(&self) -> usize; pub fn is_empty(&self) -> bool { self.len() == 0 } #[doc(alias = "objectForKey:")] - pub fn get(&self, key: &K) -> Option<&V> { - unsafe { msg_send![self, objectForKey: key] } - } + #[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 { - let _: () = msg_send![ - self, - getObjects: ptr::null_mut::<&V>(), - andKeys: keys.as_mut_ptr(), - ]; + self.get_objects_and_keys(ptr::null_mut(), keys.as_mut_ptr()); keys.set_len(len); } keys @@ -70,11 +67,7 @@ extern_methods!( let len = self.len(); let mut vals = Vec::with_capacity(len); unsafe { - let _: () = msg_send![ - self, - getObjects: vals.as_mut_ptr(), - andKeys: ptr::null_mut::<&K>(), - ]; + self.get_objects_and_keys(vals.as_mut_ptr(), ptr::null_mut()); vals.set_len(len); } vals @@ -86,11 +79,7 @@ extern_methods!( let mut keys = Vec::with_capacity(len); let mut objs = Vec::with_capacity(len); unsafe { - let _: () = msg_send![ - self, - getObjects: objs.as_mut_ptr(), - andKeys: keys.as_mut_ptr(), - ]; + self.get_objects_and_keys(objs.as_mut_ptr(), keys.as_mut_ptr()); keys.set_len(len); objs.set_len(len); } diff --git a/objc2/src/foundation/error.rs b/objc2/src/foundation/error.rs index 7321b8ae6..372b395d9 100644 --- a/objc2/src/foundation/error.rs +++ b/objc2/src/foundation/error.rs @@ -4,7 +4,7 @@ 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, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send_id, ClassType}; extern_class!( /// Information about an error condition including a domain, a @@ -68,9 +68,8 @@ extern_methods!( unsafe { msg_send_id![self, domain].expect("unexpected NULL NSError domain") } } - pub fn code(&self) -> NSInteger { - unsafe { msg_send![self, code] } - } + #[sel(code)] + pub fn code(&self) -> NSInteger; pub fn user_info(&self) -> Option, Shared>> { unsafe { msg_send_id![self, userInfo] } diff --git a/objc2/src/foundation/exception.rs b/objc2/src/foundation/exception.rs index 993893844..d88b544c1 100644 --- a/objc2/src/foundation/exception.rs +++ b/objc2/src/foundation/exception.rs @@ -6,7 +6,7 @@ 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, msg_send_id, sel, ClassType}; +use crate::{extern_class, extern_methods, msg_send_id, sel, ClassType}; extern_class!( /// A special condition that interrupts the normal flow of program @@ -50,6 +50,9 @@ extern_methods!( 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. /// @@ -62,7 +65,9 @@ extern_methods!( pub unsafe fn raise(&self) -> ! { // SAFETY: We only create `Shared` NSExceptions, so it is safe to give // to the place where `@catch` receives it. - let _: () = unsafe { msg_send![self, raise] }; + unsafe { self.raise_raw() }; + // SAFETY: `raise` will throw an exception, or abort if something + // unexpected happened. unsafe { unreachable_unchecked() } } diff --git a/objc2/src/foundation/mutable_array.rs b/objc2/src/foundation/mutable_array.rs index 7fcff0eda..2ef97bccc 100644 --- a/objc2/src/foundation/mutable_array.rs +++ b/objc2/src/foundation/mutable_array.rs @@ -104,6 +104,9 @@ extern_methods!( old_obj } + #[sel(removeObjectAtIndex:)] + unsafe fn remove_at(&mut self, index: usize); + #[doc(alias = "removeObjectAtIndex:")] pub fn remove(&mut self, index: usize) -> Id { let obj = if let Some(obj) = self.get(index) { @@ -111,30 +114,27 @@ extern_methods!( } else { panic!("removal index should be < len"); }; - unsafe { - let _: () = msg_send![self, removeObjectAtIndex: index]; - } + unsafe { self.remove_at(index) }; obj } - fn remove_last(&mut self) { - unsafe { msg_send![self, removeLastObject] } - } + #[sel(removeLastObject)] + unsafe fn remove_last(&mut self); #[doc(alias = "removeLastObject")] pub fn pop(&mut self) -> Option> { self.last() .map(|obj| unsafe { Id::retain(obj as *const T as *mut T).unwrap_unchecked() }) .map(|obj| { - self.remove_last(); + // SAFETY: `Self::last` just checked that there is an object + unsafe { self.remove_last() }; obj }) } #[doc(alias = "removeAllObjects")] - pub fn clear(&mut self) { - unsafe { msg_send![self, removeAllObjects] } - } + #[sel(removeAllObjects)] + pub fn clear(&mut self); #[doc(alias = "sortUsingFunction:context:")] pub fn sort_by Ordering>(&mut self, compare: F) { diff --git a/objc2/src/foundation/mutable_attributed_string.rs b/objc2/src/foundation/mutable_attributed_string.rs index 6f85624e6..a3395d2c2 100644 --- a/objc2/src/foundation/mutable_attributed_string.rs +++ b/objc2/src/foundation/mutable_attributed_string.rs @@ -2,7 +2,7 @@ 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, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send_id, ClassType}; extern_class!( /// A mutable string that has associated attributes. @@ -53,9 +53,8 @@ extern_methods!( /// Replaces the entire attributed string. #[doc(alias = "setAttributedString:")] - pub fn replace(&mut self, attributed_string: &NSAttributedString) { - unsafe { msg_send![self, setAttributedString: attributed_string] } - } + #[sel(setAttributedString:)] + pub fn replace(&mut self, attributed_string: &NSAttributedString); } ); diff --git a/objc2/src/foundation/mutable_data.rs b/objc2/src/foundation/mutable_data.rs index 38f2268e7..c1735815c 100644 --- a/objc2/src/foundation/mutable_data.rs +++ b/objc2/src/foundation/mutable_data.rs @@ -9,7 +9,7 @@ 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, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send_id, ClassType}; extern_class!( /// A dynamic byte buffer in memory. @@ -66,14 +66,17 @@ extern_methods!( /// Mutation methods unsafe impl NSMutableData { - // Helps with reborrowing issue - fn raw_bytes_mut(&mut self) -> *mut c_void { - unsafe { msg_send![self, mutableBytes] } - } + /// 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.raw_bytes_mut(); + 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() { @@ -83,36 +86,29 @@ extern_methods!( } } - /// Expands with zeroes, or truncates the buffer. - #[doc(alias = "setLength:")] - pub fn set_len(&mut self, len: usize) { - unsafe { msg_send![self, setLength: 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 { msg_send![self, appendBytes: bytes_ptr, length: bytes.len()] } + 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, 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 { - msg_send![ - self, - replaceBytesInRange: range, - withBytes: ptr, - length: bytes.len(), - ] - } + unsafe { self.replace_raw(range, ptr, bytes.len()) } } pub fn set_bytes(&mut self, bytes: &[u8]) { diff --git a/objc2/src/foundation/mutable_string.rs b/objc2/src/foundation/mutable_string.rs index 9ed630aaa..ae647cbb2 100644 --- a/objc2/src/foundation/mutable_string.rs +++ b/objc2/src/foundation/mutable_string.rs @@ -5,7 +5,7 @@ use core::str; use super::{NSCopying, NSMutableCopying, NSObject, NSString}; use crate::rc::{DefaultId, Id, Owned, Shared}; -use crate::{extern_class, extern_methods, msg_send, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send_id, ClassType}; extern_class!( /// A dynamic plain-text Unicode string object. @@ -60,17 +60,15 @@ extern_methods!( unsafe impl NSMutableString { /// Appends the given [`NSString`] onto the end of this. #[doc(alias = "appendString:")] - pub fn push_nsstring(&mut self, nsstring: &NSString) { - // SAFETY: The string is not nil - unsafe { msg_send![self, appendString: nsstring] } - } + // SAFETY: The string is not nil + #[sel(appendString:)] + pub fn push_nsstring(&mut self, nsstring: &NSString); /// Replaces the entire string. #[doc(alias = "setString:")] - pub fn replace(&mut self, nsstring: &NSString) { - // SAFETY: The string is not nil - unsafe { msg_send![self, setString: nsstring] } - } + // SAFETY: The string is not nil + #[sel(setString:)] + pub fn replace(&mut self, nsstring: &NSString); // TODO: // - deleteCharactersInRange: diff --git a/objc2/src/foundation/object.rs b/objc2/src/foundation/object.rs index 47d091895..141cc79eb 100644 --- a/objc2/src/foundation/object.rs +++ b/objc2/src/foundation/object.rs @@ -4,9 +4,7 @@ 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, msg_send_bool, msg_send_id, -}; +use crate::{ClassType, __inner_extern_class, class, extern_methods, msg_send_bool, msg_send_id}; __inner_extern_class! { @__inner @@ -36,6 +34,9 @@ extern_methods!( unsafe { msg_send_bool![self, isKindOfClass: cls] } } + #[sel(hash)] + fn hash_code(&self) -> usize; + /// Check if the object is an instance of the class, or one of it's /// subclasses. /// @@ -84,8 +85,7 @@ impl Eq for NSObject {} impl hash::Hash for NSObject { #[inline] fn hash(&self, state: &mut H) { - let hash_code: usize = unsafe { msg_send![self, hash] }; - hash_code.hash(state); + self.hash_code().hash(state); } } diff --git a/objc2/src/foundation/string.rs b/objc2/src/foundation/string.rs index 71bd457ef..fb2c29b54 100644 --- a/objc2/src/foundation/string.rs +++ b/objc2/src/foundation/string.rs @@ -73,9 +73,8 @@ extern_methods!( /// See also [`NSString::len`]. #[doc(alias = "length")] // TODO: Finish this - fn len_utf16(&self) -> usize { - unsafe { msg_send![self, length] } - } + #[sel(length)] + fn len_utf16(&self) -> usize; pub fn is_empty(&self) -> bool { // TODO: lengthOfBytesUsingEncoding: might sometimes return 0 for diff --git a/objc2/src/foundation/thread.rs b/objc2/src/foundation/thread.rs index 3760f21f5..a95071d8e 100644 --- a/objc2/src/foundation/thread.rs +++ b/objc2/src/foundation/thread.rs @@ -4,7 +4,7 @@ use core::panic::{RefUnwindSafe, UnwindSafe}; use super::{NSObject, NSString}; use crate::rc::{Id, Shared}; -use crate::{extern_class, extern_methods, msg_send, msg_send_bool, msg_send_id, ClassType}; +use crate::{extern_class, extern_methods, msg_send_bool, msg_send_id, ClassType}; extern_class!( /// A thread of execution. @@ -53,9 +53,8 @@ extern_methods!( unsafe { msg_send_id![Self::class(), new] }.unwrap() } - unsafe fn start(&self) { - unsafe { msg_send![self, start] } - } + #[sel(start)] + unsafe fn start(&self); } ); diff --git a/objc2/src/foundation/uuid.rs b/objc2/src/foundation/uuid.rs index 9eb9492e4..eaf93e5a7 100644 --- a/objc2/src/foundation/uuid.rs +++ b/objc2/src/foundation/uuid.rs @@ -3,9 +3,7 @@ use core::panic::{RefUnwindSafe, UnwindSafe}; use super::{NSCopying, NSObject, NSString}; use crate::rc::{DefaultId, Id, Shared}; -use crate::{ - extern_class, extern_methods, msg_send, msg_send_id, ClassType, Encode, Encoding, RefEncode, -}; +use crate::{extern_class, extern_methods, msg_send_id, ClassType, Encode, Encoding, RefEncode}; extern_class!( /// A universally unique value. @@ -71,9 +69,12 @@ extern_methods!( } } + #[sel(getUUIDBytes:)] + fn get_bytes_raw(&self, bytes: &mut UuidBytes); + pub fn as_bytes(&self) -> [u8; 16] { let mut bytes = UuidBytes([0; 16]); - let _: () = unsafe { msg_send![self, getUUIDBytes: &mut bytes] }; + self.get_bytes_raw(&mut bytes); bytes.0 }