Fix BOOL return values from NSUserDefaults, improve documentation

This commit is contained in:
Ryan McGrath 2020-03-31 19:00:03 -07:00
parent 8c39ea6f94
commit db4da24268
No known key found for this signature in database
GPG key ID: 811674B62B666830
4 changed files with 66 additions and 20 deletions

View file

@ -2,7 +2,7 @@
use std::collections::HashMap; use std::collections::HashMap;
use cacao::macos::app::{App, AppDelegate}; use cacao::macos::{App, AppDelegate};
use cacao::defaults::{UserDefaults, Value}; use cacao::defaults::{UserDefaults, Value};
use cacao::foundation::NSData; use cacao::foundation::NSData;
@ -18,7 +18,7 @@ impl AppDelegate for DefaultsTest {
//map.insert("LOL", Value::string("laugh")); //map.insert("LOL", Value::string("laugh"));
//map.insert("X", Value::Integer(1)); //map.insert("X", Value::Integer(1));
//map.insert("X2", Value::Float(1.0)); //map.insert("X2", Value::Float(1.0));
map.insert("BOOL", Value::bool(true)); map.insert("BOOL", Value::Bool(false));
println!("Test equivalency:"); println!("Test equivalency:");
let s = "BYTES TEST".to_string().into_bytes(); let s = "BYTES TEST".to_string().into_bytes();
@ -34,7 +34,7 @@ impl AppDelegate for DefaultsTest {
//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("BOOL"));
let bytes = defaults.get("BYTES").unwrap(); let bytes = defaults.get("BYTES").unwrap();
println!("Bytes: {:?}", bytes); println!("Bytes: {:?}", bytes);

View file

@ -197,15 +197,24 @@ impl UserDefaults {
// `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.
//
// BOOL returns as "c", which... something makes me feel weird there, but testing it seems
// reliable.
//
// For context: https://nshipster.com/type-encodings/
if NSNumber::is(result) { if NSNumber::is(result) {
let number = NSNumber::wrap(result); let number = NSNumber::wrap(result);
return match number.objc_type() { return match number.objc_type() {
"q" => Some(Value::Integer(number.as_i64())), "c" => Some(Value::Bool(number.as_bool())),
"d" => Some(Value::Float(number.as_f64())), "d" => Some(Value::Float(number.as_f64())),
"q" => Some(Value::Integer(number.as_i64())),
x => {
// Debugging code that should be removed at some point.
#[cfg(debug_assertions)]
println!("Code: {}", x);
_ => {
// @TODO: Verify this area.
None None
} }
}; };

View file

@ -1,6 +1,3 @@
//! A wrapper for `NSDictionary`, which aims to make dealing with the class throughout this
//! framework a tad bit simpler.
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;
@ -19,18 +16,28 @@ impl Default for NSDictionary {
} }
impl NSDictionary { impl NSDictionary {
/// Constructs an `NSMutableDictionary` and retains it.
///
/// Why mutable? It's just easier for working with it, as they're (mostly) interchangeable when
/// passed around in Objective-C. We guard against mutation on our side using the standard Rust
/// object model. You can, of course, bypass it and `msg_send![]` yourself, but it'd require an
/// `unsafe {}` block... so you'll know you're in special territory then.
pub fn new() -> Self { pub fn new() -> Self {
NSDictionary(unsafe { NSDictionary(unsafe {
Id::from_ptr(msg_send![class!(NSMutableDictionary), new]) Id::from_ptr(msg_send![class!(NSMutableDictionary), new])
}) })
} }
/// Inserts an object into the backing NSMutablyDictionary.
///
/// This intentionally requires `NSString` be allocated ahead of time.
pub fn insert(&mut self, key: NSString, object: id) { pub fn insert(&mut self, key: NSString, object: id) {
unsafe { unsafe {
let _: () = msg_send![&*self.0, setObject:object forKey:key.into_inner()]; let _: () = msg_send![&*self.0, setObject:object forKey:key.into_inner()];
} }
} }
/// Consumes and returns the underlying `NSMutableDictionary`.
pub fn into_inner(mut self) -> id { pub fn into_inner(mut self) -> id {
&mut *self.0 &mut *self.0
} }

View file

@ -1,8 +1,3 @@
//! 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::ffi::CStr;
use std::os::raw::c_char; use std::os::raw::c_char;
@ -13,10 +8,22 @@ use objc_id::Id;
use crate::foundation::{id, BOOL, YES, NO, NSInteger}; use crate::foundation::{id, BOOL, YES, NO, NSInteger};
/// Wrapper for a retained `NSNumber` object. /// Wrapper for a retained `NSNumber` object.
///
/// In general we strive to avoid using this in the codebase, but it's a requirement for moving
/// objects in and out of certain situations (e.g, `UserDefaults`).
#[derive(Debug)] #[derive(Debug)]
pub struct NSNumber(pub Id<Object>); pub struct NSNumber(pub Id<Object>);
impl NSNumber { impl NSNumber {
/// 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)
})
}
/// Constructs a `numberWithBool` instance of `NSNumber` and retains it.
pub fn bool(value: bool) -> Self { pub fn bool(value: bool) -> Self {
NSNumber(unsafe { NSNumber(unsafe {
Id::from_ptr(msg_send![class!(NSNumber), numberWithBool:match value { Id::from_ptr(msg_send![class!(NSNumber), numberWithBool:match value {
@ -26,18 +33,25 @@ impl NSNumber {
}) })
} }
/// Constructs a `numberWithInteger` instance of `NSNumber` and retains it.
pub fn integer(value: i64) -> Self { pub fn integer(value: i64) -> Self {
NSNumber(unsafe { NSNumber(unsafe {
Id::from_ptr(msg_send![class!(NSNumber), numberWithInteger:value as NSInteger]) Id::from_ptr(msg_send![class!(NSNumber), numberWithInteger:value as NSInteger])
}) })
} }
/// Constructs a `numberWithDouble` instance of `NSNumber` and retains it.
pub fn float(value: f64) -> Self { pub fn float(value: f64) -> Self {
NSNumber(unsafe { NSNumber(unsafe {
Id::from_ptr(msg_send![class!(NSNumber), numberWithDouble:value]) Id::from_ptr(msg_send![class!(NSNumber), numberWithDouble:value])
}) })
} }
/// Returns the `objCType` of the underlying `NSNumber` as a Rust `&str`. This flag can be used
/// to inform you how you should pull the underlying data out of the `NSNumber`.
///
/// For more information:
/// [https://nshipster.com/type-encodings/](https://nshipster.com/type-encodings/)
pub fn objc_type(&self) -> &str { pub fn objc_type(&self) -> &str {
unsafe { unsafe {
let t: *const c_char = msg_send![&*self.0, objCType]; let t: *const c_char = msg_send![&*self.0, objCType];
@ -46,6 +60,10 @@ impl NSNumber {
} }
} }
/// Pulls the underlying `NSInteger` value out and passes it back as an `i64`.
///
/// Note that this _does not check_ if the underlying type is actually this. You are
/// responsible for doing so via the `objc_type()` method.
pub fn as_i64(&self) -> i64 { pub fn as_i64(&self) -> i64 {
unsafe { unsafe {
let i: NSInteger = msg_send![&*self.0, integerValue]; let i: NSInteger = msg_send![&*self.0, integerValue];
@ -53,18 +71,30 @@ impl NSNumber {
} }
} }
/// Pulls the underlying `double` value out and passes it back as an `f64`.
///
/// Note that this _does not check_ if the underlying type is actually this. You are
/// responsible for doing so via the `objc_type()` method.
pub fn as_f64(&self) -> f64 { pub fn as_f64(&self) -> f64 {
unsafe { unsafe {
msg_send![&*self.0, doubleValue] msg_send![&*self.0, doubleValue]
} }
} }
/// If we're vended an NSNumber from a method (e.g, `NSUserDefaults` querying) we might want to /// Pulls the underlying `BOOL` value out and passes it back as a `bool`.
/// wrap it while we figure out what to do with it. This does that. ///
pub fn wrap(data: id) -> Self { /// Note that this _does not check_ if the underlying type is actually this. You are
NSNumber(unsafe { /// responsible for doing so via the `objc_type()` method.
Id::from_ptr(data) pub fn as_bool(&self) -> bool {
}) let result: BOOL = unsafe {
msg_send![&*self.0, boolValue]
};
match result {
YES => true,
NO => false,
_ => unreachable!()
}
} }
/// A helper method for determining if a given `NSObject` is an `NSNumber`. /// A helper method for determining if a given `NSObject` is an `NSNumber`.