From f4ca9770e158158383f596d05e687ebd9a83f144 Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Tue, 31 Mar 2020 15:22:00 -0700 Subject: [PATCH] Fix NSData wrapper so storing and retrieving bytes in UserDefaults works as expected, with hopefully little to no cloning issues. Wrap the NSNumber logic into a proper type. More documentation/cleanup, still ongoing. --- README.md | 2 +- examples/defaults.rs | 40 ++++++++-- src/defaults/mod.rs | 75 +++++++++---------- src/defaults/value.rs | 95 +++++++++++++++--------- src/foundation/data.rs | 129 +++++++++++++++++++++++++++++++++ src/foundation/dictionary.rs | 2 +- src/foundation/mod.rs | 35 +++++++-- src/foundation/number.rs | 88 ++++++++++++++++++++++ src/foundation/string.rs | 12 ++- src/lib.rs | 6 +- src/notification_center/mod.rs | 3 - 11 files changed, 388 insertions(+), 99 deletions(-) create mode 100644 src/foundation/data.rs create mode 100644 src/foundation/number.rs diff --git a/README.md b/README.md index b721c97..8048c7b 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ struct BasicApp { } impl AppDelegate for BasicApp { - fn did_finish_launching(&mut self) { + fn did_finish_launching(&self) { self.window.set_minimum_content_size(400., 400.); self.window.set_title("Hello World!"); self.window.show(); diff --git a/examples/defaults.rs b/examples/defaults.rs index d3896ee..f7db861 100644 --- a/examples/defaults.rs +++ b/examples/defaults.rs @@ -3,7 +3,8 @@ use std::collections::HashMap; use cacao::macos::app::{App, AppDelegate}; -use cacao::defaults::{UserDefaults, DefaultValue}; +use cacao::defaults::{UserDefaults, Value}; +use cacao::foundation::NSData; #[derive(Default)] struct DefaultsTest; @@ -14,16 +15,39 @@ impl AppDelegate for DefaultsTest { defaults.register({ let mut map = HashMap::new(); - map.insert("LOL", DefaultValue::string("laugh")); - map.insert("X", DefaultValue::Integer(1)); - map.insert("X2", DefaultValue::Float(1.0)); - map.insert("BOOL", DefaultValue::bool(true)); + //map.insert("LOL", Value::string("laugh")); + //map.insert("X", Value::Integer(1)); + //map.insert("X2", Value::Float(1.0)); + map.insert("BOOL", Value::bool(true)); + + println!("Test equivalency:"); + let s = "BYTES TEST".to_string().into_bytes(); + println!(" {:?}", s); + let x = NSData::new(s); + println!(" {:?}", x.bytes()); + + let s2 = "BYTES TEST".to_string().into_bytes(); + map.insert("BYTES", Value::Data(s2)); + map }); - println!("Retrieved LOL: {:?}", defaults.get("LOL")); - println!("Retrieved LOL: {:?}", defaults.get("X")); - println!("Retrieved LOL: {:?}", defaults.get("X2")); + //println!("Retrieved LOL: {:?}", defaults.get("LOL")); + //println!("Retrieved LOL: {:?}", defaults.get("X")); + //println!("Retrieved LOL: {:?}", defaults.get("X2")); + + let bytes = defaults.get("BYTES").unwrap(); + println!("Bytes: {:?}", bytes); + let data = match std::str::from_utf8(bytes.as_data().unwrap()) { + Ok(s) => s, + Err(e) => { + eprintln!("Error converting bytes {}", e); + "Error converting bytes" + } + }; + + println!("Retrieved Bytes: {}", data); + App::terminate(); } diff --git a/src/defaults/mod.rs b/src/defaults/mod.rs index c0b3147..34b1d6c 100644 --- a/src/defaults/mod.rs +++ b/src/defaults/mod.rs @@ -1,7 +1,7 @@ //! Wraps `NSUserDefaults`, providing an interface to fetch and store small amounts of data. //! //! In general, this tries to take an approach popularized by `serde_json`'s `Value` struct. In -//! this case, `DefaultValue` handles wrapping types for insertion/retrieval, shepherding between +//! this case, `Value` handles wrapping types for insertion/retrieval, shepherding between //! the Objective-C runtime and your Rust code. //! //! It currently supports a number of primitive types, as well as a generic `Data` type for custom @@ -17,13 +17,13 @@ //! ## Example //! ```rust //! use std::collections::HashMap; -//! use cacao::defaults::{UserDefaults, DefaultValue}; +//! use cacao::defaults::{UserDefaults, Value}; //! //! let mut defaults = UserDefaults::standard(); //! //! defaults.register({ //! let map = HashMap::new(); -//! map.insert("test", DefaultValue::string("value")); +//! map.insert("test", Value::string("value")); //! map //! }); //! @@ -33,18 +33,15 @@ //! ``` use std::collections::HashMap; -use std::ffi::CStr; -use std::os::raw::c_char; -use std::unreachable; use objc::{class, msg_send, sel, sel_impl}; use objc::runtime::Object; use objc_id::Id; -use crate::foundation::{id, nil, YES, BOOL, NSInteger, NSString, NSDictionary}; +use crate::foundation::{id, nil, YES, BOOL, NSData, NSInteger, NSString, NSDictionary, NSNumber}; mod value; -pub use value::DefaultValue; +pub use value::Value; /// Wraps and provides methods for interacting with `NSUserDefaults`, which can be used for storing /// pieces of information (preferences, or _defaults_) to persist across application launches. @@ -106,17 +103,17 @@ impl UserDefaults { /// ```rust /// use std::collections::HashMap; /// - /// use cacao::defaults::{UserDefaults, DefaultValue}; + /// use cacao::defaults::{UserDefaults, Value}; /// /// let mut defaults = UserDefaults::standard(); /// /// defaults.register({ /// let mut map = HashMap::new(); - /// map.insert("test", DefaultValue::Bool(true)); + /// map.insert("test", Value::Bool(true)); /// map /// }); /// ``` - pub fn register>(&mut self, values: HashMap) { + pub fn register>(&mut self, values: HashMap) { let dictionary = NSDictionary::from(values); unsafe { @@ -128,14 +125,14 @@ impl UserDefaults { /// `NSUserDefaults` store, and asynchronously persists to the disk. /// /// ```rust - /// use cacao::defaults::{UserDefaults, DefaultValue}; + /// use cacao::defaults::{UserDefaults, Value}; /// /// let mut defaults = UserDefaults::standard(); - /// defaults.insert("test", DefaultValue::Bool(true)); + /// defaults.insert("test", Value::Bool(true)); /// ``` - pub fn insert>(&mut self, key: K, value: DefaultValue) { + pub fn insert>(&mut self, key: K, value: Value) { let key = NSString::new(key.as_ref()); - let value: id = (&value).into(); + let value: id = value.into(); unsafe { let _: () = msg_send![&*self.0, setObject:value forKey:key]; @@ -145,7 +142,7 @@ impl UserDefaults { /// Remove the default associated with the key. If the key doesn't exist, this is a noop. /// /// ```rust - /// use cacao::defaults::{UserDefaults, DefaultValue}; + /// use cacao::defaults::{UserDefaults, Value}; /// /// let mut defaults = UserDefaults::standard(); /// defaults.remove("test"); @@ -158,7 +155,7 @@ impl UserDefaults { } } - /// Returns a `DefaultValue` for the given key, from which you can further extract the data you + /// Returns a `Value` for the given key, from which you can further extract the data you /// need. Note that this does a `nil` check and will return `None` in such cases, with the /// exception of `bool` values, where it will always return either `true` or `false`. This is /// due to the underlying storage engine used for `NSUserDefaults`. @@ -167,15 +164,15 @@ impl UserDefaults { /// returns new immutable copies each time, so we're free to vend them as such. /// /// ```rust - /// use cacao::defaults::{UserDefaults, DefaultValue}; + /// use cacao::defaults::{UserDefaults, Value}; /// /// let mut defaults = UserDefaults::standard(); - /// defaults.insert("test", DefaultValue::string("value")); + /// defaults.insert("test", Value::string("value")); /// /// let value = defaults.get("test").unwrap().as_str().unwrap(); /// assert_eq!(value, "value"); /// ``` - pub fn get>(&self, key: K) -> Option { + pub fn get>(&self, key: K) -> Option { let key = NSString::new(key.as_ref()); let result: id = unsafe { @@ -186,36 +183,32 @@ impl UserDefaults { return None; } - let is_string: BOOL = unsafe { msg_send![result, isKindOfClass:class!(NSString)] }; - if is_string == YES { + if NSData::is(result) { + let data = NSData::wrap(result); + return Some(Value::Data(data.into_vec())); + } + + if NSString::is(result) { let s = NSString::wrap(result).to_str().to_string(); - return Some(DefaultValue::String(s)); + return Some(Value::String(s)); } // This works, but might not be the best approach. We basically need to inspect the // `NSNumber` returned and see what the wrapped encoding type is. `q` and `d` represent // `NSInteger` (platform specific) and `double` (f64) respectively, but conceivably we // might need others. - let is_number: BOOL = unsafe { msg_send![result, isKindOfClass:class!(NSNumber)] }; - if is_number == YES { - unsafe { - let t: *const c_char = msg_send![result, objCType]; - let slice = CStr::from_ptr(t); + if NSNumber::is(result) { + let number = NSNumber::wrap(result); + + return match number.objc_type() { + "q" => Some(Value::Integer(number.as_i64())), + "d" => Some(Value::Float(number.as_f64())), - if let Ok(code) = slice.to_str() { - println!("Code: {}", code); - - if code == "q" { - let v: NSInteger = msg_send![result, integerValue]; - return Some(DefaultValue::Integer(v as i64)); - } - - if code == "d" { - let v: f64 = msg_send![result, doubleValue]; - return Some(DefaultValue::Float(v)); - } + _ => { + // @TODO: Verify this area. + None } - } + }; } None diff --git a/src/defaults/value.rs b/src/defaults/value.rs index d9c619e..d53df96 100644 --- a/src/defaults/value.rs +++ b/src/defaults/value.rs @@ -1,35 +1,44 @@ -//! - use std::collections::HashMap; use objc::{class, msg_send, sel, sel_impl}; use objc_id::Id; -use crate::foundation::{id, YES, NO, nil, NSInteger, NSDictionary, NSString}; +use crate::foundation::{id, YES, NO, NSData, NSInteger, NSDictionary, NSString}; +/// Represents a Value that can be stored or queried with `UserDefaults`. +/// +/// In general, this wraps a few types that should hopefully work for most cases. Note that the +/// `Value` always owns whatever it holds - this is both for ergonomic considerations, as +/// well as contractual obligations with the underlying `NSUserDefaults` system. #[derive(Clone, Debug, PartialEq)] -pub enum DefaultValue { +pub enum Value { + /// Represents a Boolean value. Bool(bool), + + /// Represents a String value. String(String), + + /// Represents a Float (`f64`) value. Float(f64), - Integer(i64) + + /// Represents an Integer (`i64`) value. + Integer(i64), + + /// Represents Data (bytes). You can use this to store arbitrary things that aren't supported + /// above. You're responsible for moving things back and forth to the necessary types. + Data(Vec) } -impl DefaultValue { - /// A handy initializer for `DefaultValue::Bool`. - pub fn bool(value: bool) -> Self { - DefaultValue::Bool(value) - } - - /// A handy initializer for `DefaultValue::String`; +impl Value { + /// A handy initializer for `Value::String`. pub fn string>(value: S) -> Self { - DefaultValue::String(value.into()) + Value::String(value.into()) } /// Returns `true` if the value is a boolean value. Returns `false` otherwise. pub fn is_boolean(&self) -> bool { match self { - DefaultValue::Bool(_) => true, + Value::Bool(_) => true, _ => false } } @@ -37,7 +46,7 @@ impl DefaultValue { /// If this is a Bool, it returns the associated bool. Returns `None` otherwise. pub fn as_bool(&self) -> Option { match self { - DefaultValue::Bool(v) => Some(*v), + Value::Bool(v) => Some(*v), _ => None } } @@ -45,7 +54,7 @@ impl DefaultValue { /// Returns `true` if the value is a string. Returns `false` otherwise. pub fn is_string(&self) -> bool { match self { - DefaultValue::String(_) => true, + Value::String(_) => true, _ => false } } @@ -53,7 +62,7 @@ impl DefaultValue { /// If this is a String, it returns a &str. Returns `None` otherwise. pub fn as_str(&self) -> Option<&str> { match self { - DefaultValue::String(s) => Some(s), + Value::String(s) => Some(s), _ => None } } @@ -61,7 +70,7 @@ impl DefaultValue { /// Returns `true` if the value is a float. Returns `false` otherwise. pub fn is_integer(&self) -> bool { match self { - DefaultValue::Integer(_) => true, + Value::Integer(_) => true, _ => false } } @@ -69,7 +78,7 @@ impl DefaultValue { /// If this is a int, returns it (`i32`). Returns `None` otherwise. pub fn as_i32(&self) -> Option { match self { - DefaultValue::Integer(i) => Some(*i as i32), + Value::Integer(i) => Some(*i as i32), _ => None } } @@ -77,7 +86,7 @@ impl DefaultValue { /// If this is a int, returns it (`i64`). Returns `None` otherwise. pub fn as_i64(&self) -> Option { match self { - DefaultValue::Integer(i) => Some(*i as i64), + Value::Integer(i) => Some(*i as i64), _ => None } } @@ -85,7 +94,7 @@ impl DefaultValue { /// Returns `true` if the value is a float. Returns `false` otherwise. pub fn is_float(&self) -> bool { match self { - DefaultValue::Float(_) => true, + Value::Float(_) => true, _ => false } } @@ -93,7 +102,7 @@ impl DefaultValue { /// If this is a float, returns it (`f32`). Returns `None` otherwise. pub fn as_f32(&self) -> Option { match self { - DefaultValue::Float(f) => Some(*f as f32), + Value::Float(f) => Some(*f as f32), _ => None } } @@ -101,42 +110,60 @@ impl DefaultValue { /// If this is a float, returns it (`f64`). Returns `None` otherwise. pub fn as_f64(&self) -> Option { match self { - DefaultValue::Float(f) => Some(*f as f64), + Value::Float(f) => Some(*f as f64), + _ => None + } + } + + /// Returns `true` if the value is data. Returns `false` otherwise. + pub fn is_data(&self) -> bool { + match self { + Value::Data(_) => true, + _ => false + } + } + + /// If this is data, returns it (`&[u8]`). If you need to own the underlying buffer, you can + /// extract it yourself. Returns `None` if this is not Data. + pub fn as_data(&self) -> Option<&[u8]> { + match self { + Value::Data(data) => Some(data), _ => None } } } -impl From<&DefaultValue> for id { - /// Shepherds `DefaultValue` types into `NSObject`s that can be stored in `NSUserDefaults`. +impl From for id { + /// Shepherds `Value` types into `NSObject`s that can be stored in `NSUserDefaults`. // These currently work, but may not be exhaustive and should be looked over past the preview // period. - fn from(value: &DefaultValue) -> Self { + fn from(value: Value) -> Self { unsafe { match value { - DefaultValue::Bool(b) => msg_send![class!(NSNumber), numberWithBool:match b { + Value::Bool(b) => msg_send![class!(NSNumber), numberWithBool:match b { true => YES, false => NO }], - DefaultValue::String(s) => NSString::new(&s).into_inner(), - DefaultValue::Float(f) => msg_send![class!(NSNumber), numberWithDouble:*f], - DefaultValue::Integer(i) => msg_send![class!(NSNumber), numberWithInteger:*i as NSInteger] + Value::String(s) => NSString::new(&s).into_inner(), + Value::Float(f) => msg_send![class!(NSNumber), numberWithDouble:f], + Value::Integer(i) => msg_send![class!(NSNumber), numberWithInteger:i as NSInteger], + Value::Data(data) => NSData::new(data).into_inner() } } } } -impl From> for NSDictionary +impl From> for NSDictionary where K: AsRef { - /// Translates a `HashMap` of `DefaultValue`s into an `NSDictionary`. - fn from(map: HashMap) -> Self { + /// Translates a `HashMap` of `Value`s into an `NSDictionary`. + fn from(map: HashMap) -> Self { NSDictionary(unsafe { let dictionary: id = msg_send![class!(NSMutableDictionary), new]; - for (key, value) in map.iter() { + for (key, value) in map.into_iter() { let k = NSString::new(key.as_ref()); let v: id = value.into(); let _: () = msg_send![dictionary, setObject:v forKey:k]; diff --git a/src/foundation/data.rs b/src/foundation/data.rs new file mode 100644 index 0000000..193dfa7 --- /dev/null +++ b/src/foundation/data.rs @@ -0,0 +1,129 @@ +//! A wrapper for `NSData`. +//! +//! This more or less exists to try and wrap the specific APIs we use to interface with Cocoa. Note +//! that in general this is only concerned with bridging for arguments - i.e, we need an `NSData` +//! from a `Vec` to satisfy the Cocoa side of things. It's expected that all processing of data +//! happens solely on the Rust side of things before coming through here. +//! +//! tl;dr this is an intentionally limited API. + +use std::mem; +use std::os::raw::c_void; +use std::slice; + +use block::{Block, ConcreteBlock}; + +use objc::{class, msg_send, sel, sel_impl}; +use objc::runtime::Object; +use objc_id::Id; + +use crate::foundation::{id, BOOL, YES, NO, NSUInteger}; + +/// Wrapper for a retained `NSData` object. +/// +/// Supports constructing a new `NSData` from a `Vec`, wrapping and retaining an existing +/// pointer from the Objective-C side, and turning an `NSData` into a `Vec`. +#[derive(Debug)] +pub struct NSData(pub Id); + +impl NSData { + /// Given a vector of bytes, creates, retains, and returns a wrapped `NSData`. + /// + /// This method is borrowed straight out of [objc-foundation](objc-foundation) by the amazing + /// Steven Sheldon, and just tweaked slightly to fit the desired API semantics here. + /// + /// [objc-foundation]: https://crates.io/crates/objc-foundation + pub fn new(bytes: Vec) -> Self { + let capacity = bytes.capacity(); + + let dealloc = ConcreteBlock::new(move |bytes: *mut c_void, len: usize| unsafe { + // Recreate the Vec and let it drop + let _ = Vec::from_raw_parts(bytes as *mut u8, len, capacity); + }); + let dealloc = dealloc.copy(); + let dealloc: &Block<(*mut c_void, usize), ()> = &dealloc; + + let mut bytes = bytes; + let bytes_ptr = bytes.as_mut_ptr() as *mut c_void; + + unsafe { + let obj: id = msg_send![class!(NSData), alloc]; + let obj: id = msg_send![obj, initWithBytesNoCopy:bytes_ptr + length:bytes.len() + deallocator:dealloc]; + mem::forget(bytes); + NSData(Id::from_ptr(obj)) + } + } + + /// If we're vended an NSData from a method (e.g, a push notification token) we might want to + /// wrap it while we figure out what to do with it. This does that. + pub fn wrap(data: id) -> Self { + NSData(unsafe { + Id::from_ptr(data) + }) + } + + /// A helper method for determining if a given `NSObject` is an `NSData`. + pub fn is(obj: id) -> bool { + let result: BOOL = unsafe { + msg_send![obj, isKindOfClass:class!(NSData)] + }; + + match result { + YES => true, + NO => false, + _ => unreachable!() + } + } + + /// Returns the length of the underlying `NSData` bytes. + pub fn len(&self) -> usize { + unsafe { + let x: NSUInteger = msg_send![&*self.0, length]; + x as usize + } + } + + /// Returns a reference to the underlying bytes for the wrapped `NSData`. + /// + /// This, like `NSData::new()`, is cribbed from [objc-foundation](objc-foundation). + /// + /// [objc-foundation](https://crates.io/crates/objc-foundation) + pub fn bytes(&self) -> &[u8] { + let ptr: *const c_void = unsafe { msg_send![&*self.0, bytes] }; + + // The bytes pointer may be null for length zero + let (ptr, len) = if ptr.is_null() { + (0x1 as *const u8, 0) + } else { + (ptr as *const u8, self.len()) + }; + + unsafe { + slice::from_raw_parts(ptr, len) + } + } + + /// Creates a new Vec, copies the NSData (safely, but quickly) bytes into that Vec, and + /// consumes the NSData enabling it to release (provided nothing in Cocoa is using it). + /// + // A point of discussion: I think this is the safest way to handle it, however I could be + // wrong - it's a bit defensive but I can't think of a way to reliably return an owned set of + // this data without messing up the Objective-C side of things. Thankfully this isn't used too + // often, but still... open to ideas. + pub fn into_vec(self) -> Vec { + let mut data = Vec::new(); + + let bytes = self.bytes(); + data.resize(bytes.len(), 0); + data.copy_from_slice(bytes); + + data + } + + /// Consumes and returns the underlying `NSData`. + pub fn into_inner(mut self) -> id { + &mut *self.0 + } +} diff --git a/src/foundation/dictionary.rs b/src/foundation/dictionary.rs index 5de3879..bf6b9b2 100644 --- a/src/foundation/dictionary.rs +++ b/src/foundation/dictionary.rs @@ -5,7 +5,7 @@ use objc::{class, msg_send, sel, sel_impl}; use objc::runtime::Object; use objc_id::Id; -use crate::foundation::{id, nil, YES, NO, NSString}; +use crate::foundation::id; /// A wrapper for `NSDictionary`. Behind the scenes we actually wrap `NSMutableDictionary`, and /// rely on Rust doing the usual borrow-checking guards that it does so well. diff --git a/src/foundation/mod.rs b/src/foundation/mod.rs index ee6db9b..11e5165 100644 --- a/src/foundation/mod.rs +++ b/src/foundation/mod.rs @@ -1,5 +1,6 @@ -//! This module contains some lightweight wrappers over certain data types that we use throughout -//! the framework. Some of it is pulled/inspired from Servo's cocoa-rs (e.g, the "id" type). While +//! This module contains some lightweight wrappers over Foundation data types. +//! +//! Some of it is pulled/inspired from Servo's cocoa-rs (e.g, the "id" type). While //! this isn't a clone of their module (we don't need everything from there, but remaining //! compatible in case an end-user wants to drop that low is deal), it's worth linking their //! license and repository - they've done really incredible work and it's 100% worth acknowledging. @@ -7,6 +8,12 @@ //! - [core-foundation-rs Repository](https://github.com/servo/core-foundation-rs) //! - [core-foundation-rs MIT License](https://github.com/servo/core-foundation-rs/blob/master/LICENSE-MIT) //! - [core-foundation-rs Apache License](https://github.com/servo/core-foundation-rs/blob/master/LICENSE-APACHE) +//! +//! ## Why? +//! A good question. The existing wrappers tend to use traits over `id`, which works well for some +//! cases, but I found it frustrating and messy trying to work with them. These provide the API I +//! was looking for, and help provide all the proper `retain`/`release` logic needed for the +//! Objective-C side. #![allow(non_camel_case_types)] #![allow(non_upper_case_globals)] @@ -14,30 +21,44 @@ use objc::runtime; pub use objc::runtime::{BOOL, NO, YES}; -pub mod autoreleasepool; +mod autoreleasepool; pub use autoreleasepool::AutoReleasePool; -pub mod array; +mod array; pub use array::NSArray; -pub mod string; -pub use string::NSString; +mod data; +pub use data::NSData; -pub mod dictionary; +mod dictionary; pub use dictionary::NSDictionary; +mod number; +pub use number::NSNumber; + +mod string; +pub use string::NSString; + +/// More or less maps over to Objective-C's `id` type, which... can really be anything. #[allow(non_camel_case_types)] pub type id = *mut runtime::Object; +/// Exactly what it sounds like. #[allow(non_upper_case_globals)] pub const nil: id = 0 as id; +/// Platform-specific. #[cfg(target_pointer_width = "32")] pub type NSInteger = libc::c_int; + +/// Platform-specific. #[cfg(target_pointer_width = "32")] pub type NSUInteger = libc::c_uint; +/// Platform-specific. #[cfg(target_pointer_width = "64")] pub type NSInteger = libc::c_long; + +/// Platform-specific. #[cfg(target_pointer_width = "64")] pub type NSUInteger = libc::c_ulong; diff --git a/src/foundation/number.rs b/src/foundation/number.rs new file mode 100644 index 0000000..e44ec45 --- /dev/null +++ b/src/foundation/number.rs @@ -0,0 +1,88 @@ +//! A wrapper for `NSNumber`. +//! +//! There are a few places where we have to interact with this type (e.g, `NSUserDefaults`) and so +//! this type exists to wrap those unsafe operations. + +use std::ffi::CStr; +use std::os::raw::c_char; + +use objc::{class, msg_send, sel, sel_impl}; +use objc::runtime::Object; +use objc_id::Id; + +use crate::foundation::{id, BOOL, YES, NO, NSInteger}; + +/// Wrapper for a retained `NSNumber` object. +#[derive(Debug)] +pub struct NSNumber(pub Id); + +impl NSNumber { + pub fn bool(value: bool) -> Self { + NSNumber(unsafe { + Id::from_ptr(msg_send![class!(NSNumber), numberWithBool:match value { + true => YES, + false => NO, + _ => unreachable!() + }]) + }) + } + + pub fn integer(value: i64) -> Self { + NSNumber(unsafe { + Id::from_ptr(msg_send![class!(NSNumber), numberWithInteger:value as NSInteger]) + }) + } + + pub fn float(value: f64) -> Self { + NSNumber(unsafe { + Id::from_ptr(msg_send![class!(NSNumber), numberWithDouble:value]) + }) + } + + pub fn objc_type(&self) -> &str { + unsafe { + let t: *const c_char = msg_send![&*self.0, objCType]; + let slice = CStr::from_ptr(t); + slice.to_str().unwrap() + } + } + + pub fn as_i64(&self) -> i64 { + unsafe { + let i: NSInteger = msg_send![&*self.0, integerValue]; + i as i64 + } + } + + pub fn as_f64(&self) -> f64 { + unsafe { + msg_send![&*self.0, doubleValue] + } + } + + /// If we're vended an NSNumber from a method (e.g, `NSUserDefaults` querying) we might want to + /// wrap it while we figure out what to do with it. This does that. + pub fn wrap(data: id) -> Self { + NSNumber(unsafe { + Id::from_ptr(data) + }) + } + + /// A helper method for determining if a given `NSObject` is an `NSNumber`. + pub fn is(obj: id) -> bool { + let result: BOOL = unsafe { + msg_send![obj, isKindOfClass:class!(NSNumber)] + }; + + match result { + YES => true, + NO => false, + _ => unreachable!() + } + } + + /// Consumes and returns the underlying `NSNumber`. + pub fn into_inner(mut self) -> id { + &mut *self.0 + } +} diff --git a/src/foundation/string.rs b/src/foundation/string.rs index f647ec7..24d9459 100644 --- a/src/foundation/string.rs +++ b/src/foundation/string.rs @@ -13,7 +13,7 @@ use objc::{class, msg_send, sel, sel_impl}; use objc::runtime::Object; use objc_id::Id; -use crate::foundation::id; +use crate::foundation::{id, BOOL, YES, NO}; const UTF8_ENCODING: usize = 4; @@ -36,6 +36,16 @@ impl NSString { }) } + pub fn is(obj: id) -> bool { + let result: BOOL = unsafe { msg_send![obj, isKindOfClass:class!(NSString)] }; + + match result { + YES => true, + NO => false, + _ => unreachable!() + } + } + pub fn into_inner(mut self) -> id { &mut *self.0 } diff --git a/src/lib.rs b/src/lib.rs index 3bc93c8..2956e15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -24,9 +24,9 @@ //! //! # Hello World //! -//! ```rust,no_run -//! use cacao::app::{App, AppDelegate}; -//! use cacao::window::Window; +//! ```rust +//! use cacao::macos::app::{App, AppDelegate}; +//! use cacao::macos::window::Window; //! //! #[derive(Default)] //! struct BasicApp { diff --git a/src/notification_center/mod.rs b/src/notification_center/mod.rs index ea098ea..822ea41 100644 --- a/src/notification_center/mod.rs +++ b/src/notification_center/mod.rs @@ -11,9 +11,6 @@ //! integrating with certain aspects of the underlying Cocoa/Foundation/Kit frameworks. //! //! ## Example -//! ```rust,no_run -//! -//! ``` use objc::{class, msg_send, sel, sel_impl}; use objc::runtime::Object;