Further work on wrapping NSUserDefaults

This commit is contained in:
Ryan McGrath 2020-03-30 01:35:11 -07:00
parent ccaf61f56f
commit e4ddfb975a
No known key found for this signature in database
GPG key ID: 811674B62B666830
5 changed files with 164 additions and 54 deletions

30
examples/defaults.rs Normal file
View file

@ -0,0 +1,30 @@
//! This tests the `defaults` module to ensure things behave as they should.
use cacao::macos::app::{App, AppDelegate};
use cacao::defaults::UserDefaults;
#[derive(Default)]
struct DefaultsTest;
impl AppDelegate for DefaultsTest {
fn did_finish_launching(&self) {
let mut defaults = UserDefaults::standard();
match defaults.get_string("LOL") {
Some(s) => {
println!("Retrieved {}", s);
},
None => {
defaults.set_string("LOL", "laugh");
println!("Run this again to get a laugh");
}
}
App::terminate();
}
}
fn main() {
App::new("com.cacao.defaults-test", DefaultsTest::default()).run();
}

123
src/defaults.rs Normal file
View file

@ -0,0 +1,123 @@
//! 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()];
}
}
}

View file

@ -85,6 +85,7 @@ pub mod color;
pub mod dragdrop;
pub mod error;
pub mod events;
pub mod defaults;
pub mod filesystem;
pub mod foundation;
pub mod geometry;
@ -98,7 +99,6 @@ pub mod user_notifications;
pub mod user_activity;
pub(crate) mod utils;
pub mod user_defaults;
pub mod view;

View file

@ -38,7 +38,7 @@ use objc_id::Id;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, YES, NO, NSUInteger, AutoReleasePool};
use crate::foundation::{id, nil, YES, NO, NSUInteger, AutoReleasePool};
use crate::macos::menu::Menu;
use crate::notification_center::Dispatcher;
@ -236,4 +236,13 @@ impl App {
let _: () = msg_send![app, setMainMenu:main_menu];
});
}
/// Terminates the application, firing the requisite cleanup delegate methods in the process.
///
/// This is typically called when the user chooses to quit via the App menu.
pub fn terminate() {
shared_application(|app| unsafe {
let _: () = msg_send![app, terminate:nil];
});
}
}

View file

@ -1,52 +0,0 @@
//! Wraps `NSUserDefaults`, providing an interface to store and query small amounts of data.
//!
//! It mirrors much of the API of the standard Rust `HashMap`, but uses `NSUserDefaults` as a
//! backing store.
use objc::{class, msg_send, sel, sel_impl};
use objc::runtime::Object;
use objc_id::Id;
use crate::foundation::{id, NSString};
#[derive(Debug)]
pub struct UserDefaults(pub Id<Object>);
impl Default for UserDefaults {
fn default() -> Self {
UserDefaults::standard()
}
}
impl UserDefaults {
pub fn standard() -> Self {
UserDefaults(unsafe {
Id::from_ptr(msg_send![class!(NSUserDefaults), standardUserDefaults])
})
}
pub fn new() -> Self {
UserDefaults(unsafe {
let alloc: id = msg_send![class!(NSUserDefaults), alloc];
Id::from_ptr(msg_send![alloc, init])
})
}
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(&self, key: &str) {
let key = NSString::new(key);
unsafe {
let _: () = msg_send![&*self.0, removeObjectForKey:key.into_inner()];
}
}
}