Almost done with a proper NSUserDefaults wrapper...
This commit is contained in:
parent
e4ddfb975a
commit
3f9c9f992c
|
@ -1,7 +1,9 @@
|
||||||
//! This tests the `defaults` module to ensure things behave as they should.
|
//! This tests the `defaults` module to ensure things behave as they should.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use cacao::macos::app::{App, AppDelegate};
|
use cacao::macos::app::{App, AppDelegate};
|
||||||
use cacao::defaults::UserDefaults;
|
use cacao::defaults::{UserDefaults, DefaultValue};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct DefaultsTest;
|
struct DefaultsTest;
|
||||||
|
@ -10,16 +12,18 @@ impl AppDelegate for DefaultsTest {
|
||||||
fn did_finish_launching(&self) {
|
fn did_finish_launching(&self) {
|
||||||
let mut defaults = UserDefaults::standard();
|
let mut defaults = UserDefaults::standard();
|
||||||
|
|
||||||
match defaults.get_string("LOL") {
|
defaults.register({
|
||||||
Some(s) => {
|
let mut map = HashMap::new();
|
||||||
println!("Retrieved {}", s);
|
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
|
||||||
|
});
|
||||||
|
|
||||||
None => {
|
println!("Retrieved LOL: {:?}", defaults.get("LOL"));
|
||||||
defaults.set_string("LOL", "laugh");
|
println!("Retrieved LOL: {:?}", defaults.get("X"));
|
||||||
println!("Run this again to get a laugh");
|
println!("Retrieved LOL: {:?}", defaults.get("X2"));
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
App::terminate();
|
App::terminate();
|
||||||
}
|
}
|
||||||
|
|
123
src/defaults.rs
123
src/defaults.rs
|
@ -1,123 +0,0 @@
|
||||||
//! Wraps `NSUserDefaults`, providing an interface to store and query small amounts of data.
|
|
||||||
//!
|
|
||||||
//! It may seem a bit verbose at points, but it aims to implement everything on the Objective-C
|
|
||||||
//! side as closely as possible.
|
|
||||||
|
|
||||||
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, NO, BOOL, NSString};
|
|
||||||
|
|
||||||
/// Wraps and provides methods for interacting with `NSUserDefaults`, which can be used for storing
|
|
||||||
/// pieces of information (preferences, or _defaults_) to persist across application restores.
|
|
||||||
///
|
|
||||||
/// This should not be used for sensitive data - use the Keychain for that.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct UserDefaults(pub Id<Object>);
|
|
||||||
|
|
||||||
impl Default for UserDefaults {
|
|
||||||
/// Equivalent to calling `UserDefaults::standard()`.
|
|
||||||
fn default() -> Self {
|
|
||||||
UserDefaults::standard()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UserDefaults {
|
|
||||||
/// Returns the `standardUserDefaults`, which is... exactly what it sounds like.
|
|
||||||
pub fn standard() -> Self {
|
|
||||||
UserDefaults(unsafe {
|
|
||||||
Id::from_ptr(msg_send![class!(NSUserDefaults), standardUserDefaults])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a new user defaults to work with. You probably don't want this, and either want
|
|
||||||
/// `suite()` or `standard()`.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
UserDefaults(unsafe {
|
|
||||||
let alloc: id = msg_send![class!(NSUserDefaults), alloc];
|
|
||||||
Id::from_ptr(msg_send![alloc, init])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a user defaults instance for the given suite name. You typically use this to share
|
|
||||||
/// preferences across apps and extensions.
|
|
||||||
pub fn suite(named: &str) -> Self {
|
|
||||||
let name = NSString::new(named);
|
|
||||||
|
|
||||||
UserDefaults(unsafe {
|
|
||||||
let alloc: id = msg_send![class!(NSUserDefaults), alloc];
|
|
||||||
Id::from_ptr(msg_send![alloc, initWithSuiteName:name.into_inner()])
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove the default associated with the key. If the key doesn't exist, this is a noop.
|
|
||||||
pub fn remove(&mut self, key: &str) {
|
|
||||||
let key = NSString::new(key);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![&*self.0, removeObjectForKey:key.into_inner()];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a bool for the given key. If the key doesn't exist, it returns `false`.
|
|
||||||
///
|
|
||||||
/// Note that behind the scenes, this will coerce certain "truthy" and "falsy" values - this is
|
|
||||||
/// done on the system side, and is not something that can be changed.
|
|
||||||
///
|
|
||||||
/// e.g:
|
|
||||||
/// `"true"`, `"YES"`, `"1"`, `1`, `1.0` will become `true`
|
|
||||||
/// `"false"`, `"NO"`, `"0"`, `0`, `0.0` will become `false`
|
|
||||||
pub fn get_bool(&self, key: &str) -> bool {
|
|
||||||
let key = NSString::new(key);
|
|
||||||
|
|
||||||
let result: BOOL = unsafe {
|
|
||||||
msg_send![&*self.0, boolForKey:key.into_inner()]
|
|
||||||
};
|
|
||||||
|
|
||||||
match result {
|
|
||||||
YES => true,
|
|
||||||
NO => false,
|
|
||||||
_ => unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the bool for the given key to the specified value.
|
|
||||||
pub fn set_bool(&mut self, key: &str, value: bool) {
|
|
||||||
let key = NSString::new(key);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![&*self.0, setBool:match value {
|
|
||||||
true => YES,
|
|
||||||
false => NO
|
|
||||||
} forKey:key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the given String if it exists, mapping Objective-C's `nil` to `None`.
|
|
||||||
pub fn get_string(&self, key: &str) -> Option<String> {
|
|
||||||
let key = NSString::new(key);
|
|
||||||
|
|
||||||
let result: id = unsafe {
|
|
||||||
msg_send![&*self.0, stringForKey:key.into_inner()]
|
|
||||||
};
|
|
||||||
|
|
||||||
if result == nil {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(NSString::wrap(result).to_str().to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the string for the given key to the specified value.
|
|
||||||
pub fn set_string(&mut self, key: &str, value: &str) {
|
|
||||||
let key = NSString::new(key);
|
|
||||||
let value = NSString::new(value);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let _: () = msg_send![&*self.0, setObject:value.into_inner() forKey:key.into_inner()];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
223
src/defaults/mod.rs
Normal file
223
src/defaults/mod.rs
Normal file
|
@ -0,0 +1,223 @@
|
||||||
|
//! 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
|
||||||
|
//! 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
|
||||||
|
//! usage. Note that the `Data` type is stored internally as an `NSData` instance.
|
||||||
|
//!
|
||||||
|
//! Do not use this for storing sensitive data - you want the Keychain for that.
|
||||||
|
//!
|
||||||
|
//! In general, you should expect that some allocations are happening under the hood here, due to
|
||||||
|
//! the way the Objective-C runtime and Cocoa work. Where possible attempts are made to minimize
|
||||||
|
//! them, but in general... well, profile the rest of your code first, and don't call this stuff in
|
||||||
|
//! a loop.
|
||||||
|
//!
|
||||||
|
//! ## Example
|
||||||
|
//! ```rust
|
||||||
|
//! use std::collections::HashMap;
|
||||||
|
//! use cacao::defaults::{UserDefaults, DefaultValue};
|
||||||
|
//!
|
||||||
|
//! let mut defaults = UserDefaults::standard();
|
||||||
|
//!
|
||||||
|
//! defaults.register({
|
||||||
|
//! let map = HashMap::new();
|
||||||
|
//! map.insert("test", DefaultValue::string("value"));
|
||||||
|
//! map
|
||||||
|
//! });
|
||||||
|
//!
|
||||||
|
//! // Ignore the unwrap() calls, it's a demo ;P
|
||||||
|
//! let value = defaults.get("test").unwrap().as_str().unwrap();
|
||||||
|
//! assert_eq!(value, "value");
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
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};
|
||||||
|
|
||||||
|
mod value;
|
||||||
|
pub use value::DefaultValue;
|
||||||
|
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// This should not be used for sensitive data - use the Keychain for that.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UserDefaults(pub Id<Object>);
|
||||||
|
|
||||||
|
impl Default for UserDefaults {
|
||||||
|
/// Equivalent to calling `UserDefaults::standard()`.
|
||||||
|
fn default() -> Self {
|
||||||
|
UserDefaults::standard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UserDefaults {
|
||||||
|
/// Returns the `standardUserDefaults`, which is... exactly what it sounds like.
|
||||||
|
///
|
||||||
|
/// _Note that if you're planning to share preferences across things (e.g, an app and an
|
||||||
|
/// extension) you *probably* want to use `suite()` instead!_
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use cacao::defaults::UserDefaults;
|
||||||
|
///
|
||||||
|
/// let defaults = UserDefaults::standard();
|
||||||
|
///
|
||||||
|
/// let _ = defaults.get("test");
|
||||||
|
/// ```
|
||||||
|
pub fn standard() -> Self {
|
||||||
|
UserDefaults(unsafe {
|
||||||
|
Id::from_ptr(msg_send![class!(NSUserDefaults), standardUserDefaults])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a user defaults instance for the given suite name. You typically use this to share
|
||||||
|
/// preferences across apps and extensions.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use cacao::defaults::UserDefaults;
|
||||||
|
///
|
||||||
|
/// let defaults = UserDefaults::suite("com.myapp.shared");
|
||||||
|
///
|
||||||
|
/// // This value would be shared between apps, extensions, and so on that are in this suite.
|
||||||
|
/// let _ = defaults.get("test");
|
||||||
|
/// ```
|
||||||
|
pub fn suite(named: &str) -> Self {
|
||||||
|
let name = NSString::new(named);
|
||||||
|
|
||||||
|
UserDefaults(unsafe {
|
||||||
|
let alloc: id = msg_send![class!(NSUserDefaults), alloc];
|
||||||
|
Id::from_ptr(msg_send![alloc, initWithSuiteName:name.into_inner()])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// You can use this to register defaults at the beginning of your program. Note that these are
|
||||||
|
/// just that - _defaults_. If a user has done something to cause an actual value to be set
|
||||||
|
/// here, that value will be returned instead for that key.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use std::collections::HashMap;
|
||||||
|
///
|
||||||
|
/// use cacao::defaults::{UserDefaults, DefaultValue};
|
||||||
|
///
|
||||||
|
/// let mut defaults = UserDefaults::standard();
|
||||||
|
///
|
||||||
|
/// defaults.register({
|
||||||
|
/// let mut map = HashMap::new();
|
||||||
|
/// map.insert("test", DefaultValue::Bool(true));
|
||||||
|
/// map
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn register<K: AsRef<str>>(&mut self, values: HashMap<K, DefaultValue>) {
|
||||||
|
let dictionary = NSDictionary::from(values);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.0, registerDefaults:dictionary.into_inner()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Inserts a value for the specified key. This synchronously updates the backing
|
||||||
|
/// `NSUserDefaults` store, and asynchronously persists to the disk.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use cacao::defaults::{UserDefaults, DefaultValue};
|
||||||
|
///
|
||||||
|
/// let mut defaults = UserDefaults::standard();
|
||||||
|
/// defaults.insert("test", DefaultValue::Bool(true));
|
||||||
|
/// ```
|
||||||
|
pub fn insert<K: AsRef<str>>(&mut self, key: K, value: DefaultValue) {
|
||||||
|
let key = NSString::new(key.as_ref());
|
||||||
|
let value: id = (&value).into();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.0, setObject:value forKey:key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Remove the default associated with the key. If the key doesn't exist, this is a noop.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use cacao::defaults::{UserDefaults, DefaultValue};
|
||||||
|
///
|
||||||
|
/// let mut defaults = UserDefaults::standard();
|
||||||
|
/// defaults.remove("test");
|
||||||
|
/// ```
|
||||||
|
pub fn remove<K: AsRef<str>>(&mut self, key: K) {
|
||||||
|
let key = NSString::new(key.as_ref());
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let _: () = msg_send![&*self.0, removeObjectForKey:key.into_inner()];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a `DefaultValue` 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`.
|
||||||
|
///
|
||||||
|
/// Note that this also returns owned values, not references. `NSUserDefaults` explicitly
|
||||||
|
/// returns new immutable copies each time, so we're free to vend them as such.
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// use cacao::defaults::{UserDefaults, DefaultValue};
|
||||||
|
///
|
||||||
|
/// let mut defaults = UserDefaults::standard();
|
||||||
|
/// defaults.insert("test", DefaultValue::string("value"));
|
||||||
|
///
|
||||||
|
/// let value = defaults.get("test").unwrap().as_str().unwrap();
|
||||||
|
/// assert_eq!(value, "value");
|
||||||
|
/// ```
|
||||||
|
pub fn get<K: AsRef<str>>(&self, key: K) -> Option<DefaultValue> {
|
||||||
|
let key = NSString::new(key.as_ref());
|
||||||
|
|
||||||
|
let result: id = unsafe {
|
||||||
|
msg_send![&*self.0, objectForKey:key.into_inner()]
|
||||||
|
};
|
||||||
|
|
||||||
|
if result == nil {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_string: BOOL = unsafe { msg_send![result, isKindOfClass:class!(NSString)] };
|
||||||
|
if is_string == YES {
|
||||||
|
let s = NSString::wrap(result).to_str().to_string();
|
||||||
|
return Some(DefaultValue::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 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
148
src/defaults/value.rs
Normal file
148
src/defaults/value.rs
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
//!
|
||||||
|
|
||||||
|
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};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum DefaultValue {
|
||||||
|
Bool(bool),
|
||||||
|
String(String),
|
||||||
|
Float(f64),
|
||||||
|
Integer(i64)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultValue {
|
||||||
|
/// A handy initializer for `DefaultValue::Bool`.
|
||||||
|
pub fn bool(value: bool) -> Self {
|
||||||
|
DefaultValue::Bool(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handy initializer for `DefaultValue::String`;
|
||||||
|
pub fn string<S: Into<String>>(value: S) -> Self {
|
||||||
|
DefaultValue::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,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is a Bool, it returns the associated bool. Returns `None` otherwise.
|
||||||
|
pub fn as_bool(&self) -> Option<bool> {
|
||||||
|
match self {
|
||||||
|
DefaultValue::Bool(v) => Some(*v),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the value is a string. Returns `false` otherwise.
|
||||||
|
pub fn is_string(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
DefaultValue::String(_) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the value is a float. Returns `false` otherwise.
|
||||||
|
pub fn is_integer(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
DefaultValue::Integer(_) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is a int, returns it (`i32`). Returns `None` otherwise.
|
||||||
|
pub fn as_i32(&self) -> Option<i32> {
|
||||||
|
match self {
|
||||||
|
DefaultValue::Integer(i) => Some(*i as i32),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is a int, returns it (`i64`). Returns `None` otherwise.
|
||||||
|
pub fn as_i64(&self) -> Option<i64> {
|
||||||
|
match self {
|
||||||
|
DefaultValue::Integer(i) => Some(*i as i64),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `true` if the value is a float. Returns `false` otherwise.
|
||||||
|
pub fn is_float(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
DefaultValue::Float(_) => true,
|
||||||
|
_ => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is a float, returns it (`f32`). Returns `None` otherwise.
|
||||||
|
pub fn as_f32(&self) -> Option<f32> {
|
||||||
|
match self {
|
||||||
|
DefaultValue::Float(f) => Some(*f as f32),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is a float, returns it (`f64`). Returns `None` otherwise.
|
||||||
|
pub fn as_f64(&self) -> Option<f64> {
|
||||||
|
match self {
|
||||||
|
DefaultValue::Float(f) => Some(*f as f64),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&DefaultValue> for id {
|
||||||
|
/// Shepherds `DefaultValue` 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 {
|
||||||
|
unsafe {
|
||||||
|
match value {
|
||||||
|
DefaultValue::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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K> From<HashMap<K, DefaultValue>> for NSDictionary
|
||||||
|
where
|
||||||
|
K: AsRef<str>
|
||||||
|
{
|
||||||
|
/// Translates a `HashMap` of `DefaultValue`s into an `NSDictionary`.
|
||||||
|
fn from(map: HashMap<K, DefaultValue>) -> Self {
|
||||||
|
NSDictionary(unsafe {
|
||||||
|
let dictionary: id = msg_send![class!(NSMutableDictionary), new];
|
||||||
|
|
||||||
|
for (key, value) in map.iter() {
|
||||||
|
let k = NSString::new(key.as_ref());
|
||||||
|
let v: id = value.into();
|
||||||
|
let _: () = msg_send![dictionary, setObject:v forKey:k];
|
||||||
|
}
|
||||||
|
|
||||||
|
Id::from_ptr(dictionary)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,31 @@
|
||||||
//! A wrapper for `NSDictionary`, which aims to make dealing with the class throughout this
|
//! A wrapper for `NSDictionary`, which aims to make dealing with the class throughout this
|
||||||
//! framework a tad bit simpler.
|
//! framework a tad bit simpler.
|
||||||
|
|
||||||
|
use objc::{class, msg_send, sel, sel_impl};
|
||||||
use objc::runtime::Object;
|
use objc::runtime::Object;
|
||||||
use objc_id::Id;
|
use objc_id::Id;
|
||||||
|
|
||||||
#[derive(Debug)]
|
use crate::foundation::{id, nil, YES, NO, NSString};
|
||||||
pub struct NSDictionary(Id<Object>);
|
|
||||||
|
|
||||||
impl NSDictionary {}
|
/// 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.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NSDictionary(pub Id<Object>);
|
||||||
|
|
||||||
|
impl Default for NSDictionary {
|
||||||
|
fn default() -> Self {
|
||||||
|
NSDictionary::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NSDictionary {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
NSDictionary(unsafe {
|
||||||
|
Id::from_ptr(msg_send![class!(NSMutableDictionary), new])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_inner(mut self) -> id {
|
||||||
|
&mut *self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue