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.

This commit is contained in:
Ryan McGrath 2020-03-31 15:22:00 -07:00
parent 3f9c9f992c
commit f4ca9770e1
No known key found for this signature in database
GPG key ID: 811674B62B666830
11 changed files with 388 additions and 99 deletions

View file

@ -28,7 +28,7 @@ struct BasicApp {
} }
impl AppDelegate for 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_minimum_content_size(400., 400.);
self.window.set_title("Hello World!"); self.window.set_title("Hello World!");
self.window.show(); self.window.show();

View file

@ -3,7 +3,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use cacao::macos::app::{App, AppDelegate}; use cacao::macos::app::{App, AppDelegate};
use cacao::defaults::{UserDefaults, DefaultValue}; use cacao::defaults::{UserDefaults, Value};
use cacao::foundation::NSData;
#[derive(Default)] #[derive(Default)]
struct DefaultsTest; struct DefaultsTest;
@ -14,16 +15,39 @@ impl AppDelegate for DefaultsTest {
defaults.register({ defaults.register({
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("LOL", DefaultValue::string("laugh")); //map.insert("LOL", Value::string("laugh"));
map.insert("X", DefaultValue::Integer(1)); //map.insert("X", Value::Integer(1));
map.insert("X2", DefaultValue::Float(1.0)); //map.insert("X2", Value::Float(1.0));
map.insert("BOOL", DefaultValue::bool(true)); 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 map
}); });
println!("Retrieved LOL: {:?}", defaults.get("LOL")); //println!("Retrieved LOL: {:?}", defaults.get("LOL"));
println!("Retrieved LOL: {:?}", defaults.get("X")); //println!("Retrieved LOL: {:?}", defaults.get("X"));
println!("Retrieved LOL: {:?}", defaults.get("X2")); //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(); App::terminate();
} }

View file

@ -1,7 +1,7 @@
//! Wraps `NSUserDefaults`, providing an interface to fetch and store small amounts of data. //! 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 //! 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. //! 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 //! It currently supports a number of primitive types, as well as a generic `Data` type for custom
@ -17,13 +17,13 @@
//! ## Example //! ## Example
//! ```rust //! ```rust
//! use std::collections::HashMap; //! use std::collections::HashMap;
//! use cacao::defaults::{UserDefaults, DefaultValue}; //! use cacao::defaults::{UserDefaults, Value};
//! //!
//! let mut defaults = UserDefaults::standard(); //! let mut defaults = UserDefaults::standard();
//! //!
//! defaults.register({ //! defaults.register({
//! let map = HashMap::new(); //! let map = HashMap::new();
//! map.insert("test", DefaultValue::string("value")); //! map.insert("test", Value::string("value"));
//! map //! map
//! }); //! });
//! //!
@ -33,18 +33,15 @@
//! ``` //! ```
use std::collections::HashMap; 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::{class, msg_send, sel, sel_impl};
use objc::runtime::Object; use objc::runtime::Object;
use objc_id::Id; 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; mod value;
pub use value::DefaultValue; pub use value::Value;
/// Wraps and provides methods for interacting with `NSUserDefaults`, which can be used for storing /// 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. /// pieces of information (preferences, or _defaults_) to persist across application launches.
@ -106,17 +103,17 @@ impl UserDefaults {
/// ```rust /// ```rust
/// use std::collections::HashMap; /// use std::collections::HashMap;
/// ///
/// use cacao::defaults::{UserDefaults, DefaultValue}; /// use cacao::defaults::{UserDefaults, Value};
/// ///
/// let mut defaults = UserDefaults::standard(); /// let mut defaults = UserDefaults::standard();
/// ///
/// defaults.register({ /// defaults.register({
/// let mut map = HashMap::new(); /// let mut map = HashMap::new();
/// map.insert("test", DefaultValue::Bool(true)); /// map.insert("test", Value::Bool(true));
/// map /// map
/// }); /// });
/// ``` /// ```
pub fn register<K: AsRef<str>>(&mut self, values: HashMap<K, DefaultValue>) { pub fn register<K: AsRef<str>>(&mut self, values: HashMap<K, Value>) {
let dictionary = NSDictionary::from(values); let dictionary = NSDictionary::from(values);
unsafe { unsafe {
@ -128,14 +125,14 @@ impl UserDefaults {
/// `NSUserDefaults` store, and asynchronously persists to the disk. /// `NSUserDefaults` store, and asynchronously persists to the disk.
/// ///
/// ```rust /// ```rust
/// use cacao::defaults::{UserDefaults, DefaultValue}; /// use cacao::defaults::{UserDefaults, Value};
/// ///
/// let mut defaults = UserDefaults::standard(); /// let mut defaults = UserDefaults::standard();
/// defaults.insert("test", DefaultValue::Bool(true)); /// defaults.insert("test", Value::Bool(true));
/// ``` /// ```
pub fn insert<K: AsRef<str>>(&mut self, key: K, value: DefaultValue) { pub fn insert<K: AsRef<str>>(&mut self, key: K, value: Value) {
let key = NSString::new(key.as_ref()); let key = NSString::new(key.as_ref());
let value: id = (&value).into(); let value: id = value.into();
unsafe { unsafe {
let _: () = msg_send![&*self.0, setObject:value forKey:key]; 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. /// Remove the default associated with the key. If the key doesn't exist, this is a noop.
/// ///
/// ```rust /// ```rust
/// use cacao::defaults::{UserDefaults, DefaultValue}; /// use cacao::defaults::{UserDefaults, Value};
/// ///
/// let mut defaults = UserDefaults::standard(); /// let mut defaults = UserDefaults::standard();
/// defaults.remove("test"); /// 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 /// 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 /// exception of `bool` values, where it will always return either `true` or `false`. This is
/// due to the underlying storage engine used for `NSUserDefaults`. /// 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. /// returns new immutable copies each time, so we're free to vend them as such.
/// ///
/// ```rust /// ```rust
/// use cacao::defaults::{UserDefaults, DefaultValue}; /// use cacao::defaults::{UserDefaults, Value};
/// ///
/// let mut defaults = UserDefaults::standard(); /// 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(); /// let value = defaults.get("test").unwrap().as_str().unwrap();
/// assert_eq!(value, "value"); /// assert_eq!(value, "value");
/// ``` /// ```
pub fn get<K: AsRef<str>>(&self, key: K) -> Option<DefaultValue> { pub fn get<K: AsRef<str>>(&self, key: K) -> Option<Value> {
let key = NSString::new(key.as_ref()); let key = NSString::new(key.as_ref());
let result: id = unsafe { let result: id = unsafe {
@ -186,36 +183,32 @@ impl UserDefaults {
return None; return None;
} }
let is_string: BOOL = unsafe { msg_send![result, isKindOfClass:class!(NSString)] }; if NSData::is(result) {
if is_string == YES { 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(); 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 // 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 // `NSNumber` returned and see what the wrapped encoding type is. `q` and `d` represent
// `NSInteger` (platform specific) and `double` (f64) respectively, but conceivably we // `NSInteger` (platform specific) and `double` (f64) respectively, but conceivably we
// might need others. // might need others.
let is_number: BOOL = unsafe { msg_send![result, isKindOfClass:class!(NSNumber)] }; if NSNumber::is(result) {
if is_number == YES { let number = NSNumber::wrap(result);
unsafe {
let t: *const c_char = msg_send![result, objCType]; return match number.objc_type() {
let slice = CStr::from_ptr(t); "q" => Some(Value::Integer(number.as_i64())),
"d" => Some(Value::Float(number.as_f64())),
if let Ok(code) = slice.to_str() { _ => {
println!("Code: {}", code); // @TODO: Verify this area.
None
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));
}
} }
} };
} }
None None

View file

@ -1,35 +1,44 @@
//!
use std::collections::HashMap; use std::collections::HashMap;
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use objc_id::Id; 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)] #[derive(Clone, Debug, PartialEq)]
pub enum DefaultValue { pub enum Value {
/// Represents a Boolean value.
Bool(bool), Bool(bool),
/// Represents a String value.
String(String), String(String),
/// Represents a Float (`f64`) value.
Float(f64), 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<u8>)
} }
impl DefaultValue { impl Value {
/// A handy initializer for `DefaultValue::Bool`. /// A handy initializer for `Value::String`.
pub fn bool(value: bool) -> Self {
DefaultValue::Bool(value)
}
/// A handy initializer for `DefaultValue::String`;
pub fn string<S: Into<String>>(value: S) -> Self { pub fn string<S: Into<String>>(value: S) -> Self {
DefaultValue::String(value.into()) Value::String(value.into())
} }
/// Returns `true` if the value is a boolean value. Returns `false` otherwise. /// Returns `true` if the value is a boolean value. Returns `false` otherwise.
pub fn is_boolean(&self) -> bool { pub fn is_boolean(&self) -> bool {
match self { match self {
DefaultValue::Bool(_) => true, Value::Bool(_) => true,
_ => false _ => false
} }
} }
@ -37,7 +46,7 @@ impl DefaultValue {
/// If this is a Bool, it returns the associated bool. Returns `None` otherwise. /// If this is a Bool, it returns the associated bool. Returns `None` otherwise.
pub fn as_bool(&self) -> Option<bool> { pub fn as_bool(&self) -> Option<bool> {
match self { match self {
DefaultValue::Bool(v) => Some(*v), Value::Bool(v) => Some(*v),
_ => None _ => None
} }
} }
@ -45,7 +54,7 @@ impl DefaultValue {
/// Returns `true` if the value is a string. Returns `false` otherwise. /// Returns `true` if the value is a string. Returns `false` otherwise.
pub fn is_string(&self) -> bool { pub fn is_string(&self) -> bool {
match self { match self {
DefaultValue::String(_) => true, Value::String(_) => true,
_ => false _ => false
} }
} }
@ -53,7 +62,7 @@ impl DefaultValue {
/// If this is a String, it returns a &str. Returns `None` otherwise. /// If this is a String, it returns a &str. Returns `None` otherwise.
pub fn as_str(&self) -> Option<&str> { pub fn as_str(&self) -> Option<&str> {
match self { match self {
DefaultValue::String(s) => Some(s), Value::String(s) => Some(s),
_ => None _ => None
} }
} }
@ -61,7 +70,7 @@ impl DefaultValue {
/// Returns `true` if the value is a float. Returns `false` otherwise. /// Returns `true` if the value is a float. Returns `false` otherwise.
pub fn is_integer(&self) -> bool { pub fn is_integer(&self) -> bool {
match self { match self {
DefaultValue::Integer(_) => true, Value::Integer(_) => true,
_ => false _ => false
} }
} }
@ -69,7 +78,7 @@ impl DefaultValue {
/// If this is a int, returns it (`i32`). Returns `None` otherwise. /// If this is a int, returns it (`i32`). Returns `None` otherwise.
pub fn as_i32(&self) -> Option<i32> { pub fn as_i32(&self) -> Option<i32> {
match self { match self {
DefaultValue::Integer(i) => Some(*i as i32), Value::Integer(i) => Some(*i as i32),
_ => None _ => None
} }
} }
@ -77,7 +86,7 @@ impl DefaultValue {
/// If this is a int, returns it (`i64`). Returns `None` otherwise. /// If this is a int, returns it (`i64`). Returns `None` otherwise.
pub fn as_i64(&self) -> Option<i64> { pub fn as_i64(&self) -> Option<i64> {
match self { match self {
DefaultValue::Integer(i) => Some(*i as i64), Value::Integer(i) => Some(*i as i64),
_ => None _ => None
} }
} }
@ -85,7 +94,7 @@ impl DefaultValue {
/// Returns `true` if the value is a float. Returns `false` otherwise. /// Returns `true` if the value is a float. Returns `false` otherwise.
pub fn is_float(&self) -> bool { pub fn is_float(&self) -> bool {
match self { match self {
DefaultValue::Float(_) => true, Value::Float(_) => true,
_ => false _ => false
} }
} }
@ -93,7 +102,7 @@ impl DefaultValue {
/// If this is a float, returns it (`f32`). Returns `None` otherwise. /// If this is a float, returns it (`f32`). Returns `None` otherwise.
pub fn as_f32(&self) -> Option<f32> { pub fn as_f32(&self) -> Option<f32> {
match self { match self {
DefaultValue::Float(f) => Some(*f as f32), Value::Float(f) => Some(*f as f32),
_ => None _ => None
} }
} }
@ -101,42 +110,60 @@ impl DefaultValue {
/// If this is a float, returns it (`f64`). Returns `None` otherwise. /// If this is a float, returns it (`f64`). Returns `None` otherwise.
pub fn as_f64(&self) -> Option<f64> { pub fn as_f64(&self) -> Option<f64> {
match self { 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 _ => None
} }
} }
} }
impl From<&DefaultValue> for id { impl From<Value> for id {
/// Shepherds `DefaultValue` types into `NSObject`s that can be stored in `NSUserDefaults`. /// 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 // These currently work, but may not be exhaustive and should be looked over past the preview
// period. // period.
fn from(value: &DefaultValue) -> Self { fn from(value: Value) -> Self {
unsafe { unsafe {
match value { match value {
DefaultValue::Bool(b) => msg_send![class!(NSNumber), numberWithBool:match b { Value::Bool(b) => msg_send![class!(NSNumber), numberWithBool:match b {
true => YES, true => YES,
false => NO false => NO
}], }],
DefaultValue::String(s) => NSString::new(&s).into_inner(), Value::String(s) => NSString::new(&s).into_inner(),
DefaultValue::Float(f) => msg_send![class!(NSNumber), numberWithDouble:*f], Value::Float(f) => msg_send![class!(NSNumber), numberWithDouble:f],
DefaultValue::Integer(i) => msg_send![class!(NSNumber), numberWithInteger:*i as NSInteger] Value::Integer(i) => msg_send![class!(NSNumber), numberWithInteger:i as NSInteger],
Value::Data(data) => NSData::new(data).into_inner()
} }
} }
} }
} }
impl<K> From<HashMap<K, DefaultValue>> for NSDictionary impl<K> From<HashMap<K, Value>> for NSDictionary
where where
K: AsRef<str> K: AsRef<str>
{ {
/// Translates a `HashMap` of `DefaultValue`s into an `NSDictionary`. /// Translates a `HashMap` of `Value`s into an `NSDictionary`.
fn from(map: HashMap<K, DefaultValue>) -> Self { fn from(map: HashMap<K, Value>) -> Self {
NSDictionary(unsafe { NSDictionary(unsafe {
let dictionary: id = msg_send![class!(NSMutableDictionary), new]; 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 k = NSString::new(key.as_ref());
let v: id = value.into(); let v: id = value.into();
let _: () = msg_send![dictionary, setObject:v forKey:k]; let _: () = msg_send![dictionary, setObject:v forKey:k];

129
src/foundation/data.rs Normal file
View file

@ -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<u8>` 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<u8>`, wrapping and retaining an existing
/// pointer from the Objective-C side, and turning an `NSData` into a `Vec<u8>`.
#[derive(Debug)]
pub struct NSData(pub Id<Object>);
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<u8>) -> 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<u8> {
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
}
}

View file

@ -5,7 +5,7 @@ use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object; use objc::runtime::Object;
use objc_id::Id; 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 /// 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. /// rely on Rust doing the usual borrow-checking guards that it does so well.

View file

@ -1,5 +1,6 @@
//! This module contains some lightweight wrappers over certain data types that we use throughout //! This module contains some lightweight wrappers over Foundation data types.
//! the framework. Some of it is pulled/inspired from Servo's cocoa-rs (e.g, the "id" type). While //!
//! 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 //! 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 //! 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. //! 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 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 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) //! - [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_camel_case_types)]
#![allow(non_upper_case_globals)] #![allow(non_upper_case_globals)]
@ -14,30 +21,44 @@
use objc::runtime; use objc::runtime;
pub use objc::runtime::{BOOL, NO, YES}; pub use objc::runtime::{BOOL, NO, YES};
pub mod autoreleasepool; mod autoreleasepool;
pub use autoreleasepool::AutoReleasePool; pub use autoreleasepool::AutoReleasePool;
pub mod array; mod array;
pub use array::NSArray; pub use array::NSArray;
pub mod string; mod data;
pub use string::NSString; pub use data::NSData;
pub mod dictionary; mod dictionary;
pub use dictionary::NSDictionary; 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)] #[allow(non_camel_case_types)]
pub type id = *mut runtime::Object; pub type id = *mut runtime::Object;
/// Exactly what it sounds like.
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
pub const nil: id = 0 as id; pub const nil: id = 0 as id;
/// Platform-specific.
#[cfg(target_pointer_width = "32")] #[cfg(target_pointer_width = "32")]
pub type NSInteger = libc::c_int; pub type NSInteger = libc::c_int;
/// Platform-specific.
#[cfg(target_pointer_width = "32")] #[cfg(target_pointer_width = "32")]
pub type NSUInteger = libc::c_uint; pub type NSUInteger = libc::c_uint;
/// Platform-specific.
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
pub type NSInteger = libc::c_long; pub type NSInteger = libc::c_long;
/// Platform-specific.
#[cfg(target_pointer_width = "64")] #[cfg(target_pointer_width = "64")]
pub type NSUInteger = libc::c_ulong; pub type NSUInteger = libc::c_ulong;

88
src/foundation/number.rs Normal file
View file

@ -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<Object>);
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
}
}

View file

@ -13,7 +13,7 @@ use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object; use objc::runtime::Object;
use objc_id::Id; use objc_id::Id;
use crate::foundation::id; use crate::foundation::{id, BOOL, YES, NO};
const UTF8_ENCODING: usize = 4; 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 { pub fn into_inner(mut self) -> id {
&mut *self.0 &mut *self.0
} }

View file

@ -24,9 +24,9 @@
//! //!
//! # Hello World //! # Hello World
//! //!
//! ```rust,no_run //! ```rust
//! use cacao::app::{App, AppDelegate}; //! use cacao::macos::app::{App, AppDelegate};
//! use cacao::window::Window; //! use cacao::macos::window::Window;
//! //!
//! #[derive(Default)] //! #[derive(Default)]
//! struct BasicApp { //! struct BasicApp {

View file

@ -11,9 +11,6 @@
//! integrating with certain aspects of the underlying Cocoa/Foundation/Kit frameworks. //! integrating with certain aspects of the underlying Cocoa/Foundation/Kit frameworks.
//! //!
//! ## Example //! ## Example
//! ```rust,no_run
//!
//! ```
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object; use objc::runtime::Object;