From db4da242681f245b969fe5c9e50398e5770a2c5c Mon Sep 17 00:00:00 2001 From: Ryan McGrath Date: Tue, 31 Mar 2020 19:00:03 -0700 Subject: [PATCH] Fix BOOL return values from NSUserDefaults, improve documentation --- examples/defaults.rs | 6 ++--- src/defaults/mod.rs | 15 ++++++++--- src/foundation/dictionary.rs | 13 ++++++--- src/foundation/number.rs | 52 ++++++++++++++++++++++++++++-------- 4 files changed, 66 insertions(+), 20 deletions(-) diff --git a/examples/defaults.rs b/examples/defaults.rs index f7db861..765a409 100644 --- a/examples/defaults.rs +++ b/examples/defaults.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; -use cacao::macos::app::{App, AppDelegate}; +use cacao::macos::{App, AppDelegate}; use cacao::defaults::{UserDefaults, Value}; use cacao::foundation::NSData; @@ -18,7 +18,7 @@ impl AppDelegate for DefaultsTest { //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)); + map.insert("BOOL", Value::Bool(false)); println!("Test equivalency:"); 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("X")); - //println!("Retrieved LOL: {:?}", defaults.get("X2")); + println!("Retrieved LOL: {:?}", defaults.get("BOOL")); let bytes = defaults.get("BYTES").unwrap(); println!("Bytes: {:?}", bytes); diff --git a/src/defaults/mod.rs b/src/defaults/mod.rs index 3bab370..b4185a5 100644 --- a/src/defaults/mod.rs +++ b/src/defaults/mod.rs @@ -197,15 +197,24 @@ impl UserDefaults { // `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. + // + // 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) { let number = NSNumber::wrap(result); 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())), + "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 } }; diff --git a/src/foundation/dictionary.rs b/src/foundation/dictionary.rs index 6ad3b4a..b3466ae 100644 --- a/src/foundation/dictionary.rs +++ b/src/foundation/dictionary.rs @@ -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::runtime::Object; use objc_id::Id; @@ -19,18 +16,28 @@ impl Default for 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 { NSDictionary(unsafe { 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) { unsafe { let _: () = msg_send![&*self.0, setObject:object forKey:key.into_inner()]; } } + /// Consumes and returns the underlying `NSMutableDictionary`. pub fn into_inner(mut self) -> id { &mut *self.0 } diff --git a/src/foundation/number.rs b/src/foundation/number.rs index 7420b88..154f4ad 100644 --- a/src/foundation/number.rs +++ b/src/foundation/number.rs @@ -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::os::raw::c_char; @@ -13,10 +8,22 @@ use objc_id::Id; use crate::foundation::{id, BOOL, YES, NO, NSInteger}; /// 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)] pub struct NSNumber(pub Id); 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 { NSNumber(unsafe { 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 { NSNumber(unsafe { 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 { NSNumber(unsafe { 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 { unsafe { 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 { unsafe { 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 { 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) - }) + /// Pulls the underlying `BOOL` value out and passes it back as a `bool`. + /// + /// 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_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`.