Finalize some NSString handling that I'd been mulling over. This should be much more sound and transparent regarding ownership while keeping the same general approach that's been working so far.

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

View file

@ -4,7 +4,6 @@ use std::collections::HashMap;
use cacao::macos::{App, AppDelegate}; use cacao::macos::{App, AppDelegate};
use cacao::defaults::{UserDefaults, Value}; use cacao::defaults::{UserDefaults, Value};
use cacao::foundation::NSData;
#[derive(Default)] #[derive(Default)]
struct DefaultsTest; struct DefaultsTest;
@ -15,30 +14,31 @@ impl AppDelegate for DefaultsTest {
defaults.register({ defaults.register({
let mut map = HashMap::new(); let mut map = HashMap::new();
//map.insert("LOL", Value::string("laugh")); map.insert("testbool", Value::Bool(true));
//map.insert("X", Value::Integer(1)); map.insert("testint", Value::Integer(42));
//map.insert("X2", Value::Float(1.0)); map.insert("testfloat", Value::Float(42.));
map.insert("BOOL", Value::Bool(false)); map.insert("teststring", Value::string("Testing"));
let bytes = "BYTES TEST".to_string().into_bytes();
map.insert("testdata", Value::Data(bytes));
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")); let testbool = defaults.get("testbool").unwrap().as_bool().unwrap();
//println!("Retrieved LOL: {:?}", defaults.get("X")); assert_eq!(testbool, true);
println!("Retrieved LOL: {:?}", defaults.get("BOOL"));
let bytes = defaults.get("BYTES").unwrap(); let testint = defaults.get("testint").unwrap().as_i64().unwrap();
println!("Bytes: {:?}", bytes); assert_eq!(testint, 42);
let data = match std::str::from_utf8(bytes.as_data().unwrap()) {
let testfloat = defaults.get("testfloat").unwrap().as_f64().unwrap();
assert_eq!(testfloat, 42.);
let teststring = defaults.get("teststring").unwrap();
assert_eq!(teststring.as_str().unwrap(), "Testing");
let bytes = defaults.get("testdata").unwrap();
let s = match std::str::from_utf8(bytes.as_data().unwrap()) {
Ok(s) => s, Ok(s) => s,
Err(e) => { Err(e) => {
eprintln!("Error converting bytes {}", e); eprintln!("Error converting bytes {}", e);
@ -46,8 +46,8 @@ impl AppDelegate for DefaultsTest {
} }
}; };
println!("Retrieved Bytes: {}", data); assert_eq!(s, "BYTES TEST");
println!("All UserDefaults tests pass");
App::terminate(); App::terminate();
} }

View file

@ -56,10 +56,10 @@ impl FileManager {
create:NO create:NO
error:nil]; error:nil];
NSString::wrap(msg_send![dir, absoluteString]).to_str() NSString::wrap(msg_send![dir, absoluteString])
}; };
Url::parse(directory).map_err(|e| e.into()) Url::parse(directory.to_str()).map_err(|e| e.into())
} }
/// Given two paths, moves file (`from`) to the location specified in `to`. This can result in /// Given two paths, moves file (`from`) to the location specified in `to`. This can result in

View file

@ -1,11 +1,3 @@
//! A wrapper library for `NSString`, which we use throughout the framework. This is abstracted out
//! for a few reasons, but namely:
//!
//! - It's used often, so we want a decent enough API.
//! - Playing around with performance for this type is ideal, as it's a lot of heap allocation.
//!
//! End users should never need to interact with this.
use std::{slice, str}; use std::{slice, str};
use std::os::raw::c_char; use std::os::raw::c_char;
@ -17,11 +9,16 @@ use crate::foundation::{id, BOOL, YES, NO};
const UTF8_ENCODING: usize = 4; const UTF8_ENCODING: usize = 4;
/// Wraps an underlying `NSString`. /// A wrapper for `NSString`.
///
/// We can make a few safety guarantees in this module as the UTF8 code on the Foundation
/// side is fairly battle tested.
#[derive(Debug)] #[derive(Debug)]
pub struct NSString(pub Id<Object>); pub struct NSString(pub Id<Object>);
impl NSString { impl NSString {
/// Creates a new `NSString`. Note that `NSString` lives on the heap, so this allocates
/// accordingly.
pub fn new(s: &str) -> Self { pub fn new(s: &str) -> Self {
NSString(unsafe { NSString(unsafe {
let nsstring: *mut Object = msg_send![class!(NSString), alloc]; let nsstring: *mut Object = msg_send![class!(NSString), alloc];
@ -30,12 +27,15 @@ impl NSString {
}) })
} }
/// In cases where we're vended an `NSString` by the system, this can be used to wrap and
/// retain it.
pub fn wrap(object: id) -> Self { pub fn wrap(object: id) -> Self {
NSString(unsafe { NSString(unsafe {
Id::from_ptr(object) Id::from_ptr(object)
}) })
} }
/// Utility method for checking whether an `NSObject` is an `NSString`.
pub fn is(obj: id) -> bool { pub fn is(obj: id) -> bool {
let result: BOOL = unsafe { msg_send![obj, isKindOfClass:class!(NSString)] }; let result: BOOL = unsafe { msg_send![obj, isKindOfClass:class!(NSString)] };
@ -46,21 +46,39 @@ impl NSString {
} }
} }
pub fn into_inner(mut self) -> id { /// Helper method for returning the UTF8 bytes for this `NSString`.
&mut *self.0 fn bytes(&self) -> *const u8 {
unsafe {
let bytes: *const c_char = msg_send![&*self.0, UTF8String];
bytes as *const u8
}
}
/// Helper method for grabbing the proper byte length for this `NSString` (the UTF8 variant).
fn bytes_len(&self) -> usize {
unsafe {
msg_send![&*self.0, lengthOfBytesUsingEncoding:UTF8_ENCODING]
}
} }
/// A utility method for taking an `NSString` and bridging it to a Rust `&str`. /// A utility method for taking an `NSString` and bridging it to a Rust `&str`.
pub fn to_str(self) -> &'static str { pub fn to_str(&self) -> &str {
let bytes = self.bytes();
let len = self.bytes_len();
unsafe { unsafe {
let bytes = {
let bytes: *const c_char = msg_send![&*self.0, UTF8String];
bytes as *const u8
};
let len = msg_send![&*self.0, lengthOfBytesUsingEncoding:UTF8_ENCODING];
let bytes = slice::from_raw_parts(bytes, len); let bytes = slice::from_raw_parts(bytes, len);
str::from_utf8(bytes).unwrap() str::from_utf8(bytes).unwrap()
} }
} }
/// A utility method for taking an `NSString` and getting an owned `String` from it.
pub fn to_string(&self) -> String {
self.to_str().to_string()
}
/// Consumes and returns the underlying `NSString` instance.
pub fn into_inner(mut self) -> id {
&mut *self.0
}
} }

View file

@ -195,7 +195,7 @@ extern fn did_receive_remote_notification<T: AppDelegate>(_this: &Object, _: Sel
/// Fires when the application receives a `application:userDidAcceptCloudKitShareWithMetadata:` /// Fires when the application receives a `application:userDidAcceptCloudKitShareWithMetadata:`
/// message. /// message.
#[cfg(feature = "cloudkit")] #[cfg(feature = "cloudkit")]
extern fn accepted_cloudkit_share<T: AppDelegate>(_this: &Object, _: Sel, _: id, metadata: id) { extern fn accepted_cloudkit_share<T: AppDelegate>(this: &Object, _: Sel, _: id, metadata: id) {
let share = CKShareMetaData::with_inner(metadata); let share = CKShareMetaData::with_inner(metadata);
app::<T>(this).user_accepted_cloudkit_share(share); app::<T>(this).user_accepted_cloudkit_share(share);
} }

View file

@ -13,7 +13,6 @@ use crate::utils::load;
/// Retrieves and passes the allowed item identifiers for this toolbar. /// Retrieves and passes the allowed item identifiers for this toolbar.
extern fn allowed_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _: id) -> id { extern fn allowed_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _: id) -> id {
let toolbar = load::<T>(this, TOOLBAR_PTR); let toolbar = load::<T>(this, TOOLBAR_PTR);
println!("Hitting here?");
let identifiers: NSArray = toolbar.allowed_item_identifiers().iter().map(|identifier| { let identifiers: NSArray = toolbar.allowed_item_identifiers().iter().map(|identifier| {
NSString::new(identifier).into_inner() NSString::new(identifier).into_inner()
@ -37,9 +36,9 @@ extern fn default_item_identifiers<T: ToolbarDelegate>(this: &Object, _: Sel, _:
/// Objective-C runtime needs. /// Objective-C runtime needs.
extern fn item_for_identifier<T: ToolbarDelegate>(this: &Object, _: Sel, _: id, identifier: id, _: id) -> id { extern fn item_for_identifier<T: ToolbarDelegate>(this: &Object, _: Sel, _: id, identifier: id, _: id) -> id {
let toolbar = load::<T>(this, TOOLBAR_PTR); let toolbar = load::<T>(this, TOOLBAR_PTR);
let identifier = NSString::wrap(identifier).to_str(); let identifier = NSString::wrap(identifier);
let mut item = toolbar.item_for(identifier); let mut item = toolbar.item_for(identifier.to_str());
&mut *item.objc &mut *item.objc
} }

View file

@ -19,10 +19,10 @@ impl URLRequest {
} }
} }
pub fn url(&self) -> &'static str { pub fn url(&self) -> String {
NSString::wrap(unsafe { NSString::wrap(unsafe {
let url: id = msg_send![&*self.inner, URL]; let url: id = msg_send![&*self.inner, URL];
msg_send![url, absoluteString] msg_send![url, absoluteString]
}).to_str() }).to_string()
} }
} }

View file

@ -26,9 +26,10 @@ impl NotificationCenter {
unsafe { unsafe {
// @TODO: Revisit. // @TODO: Revisit.
let block = ConcreteBlock::new(|_: id, error: id| { let block = ConcreteBlock::new(|_: id, error: id| {
let localized_description = NSString::new(msg_send![error, localizedDescription]).to_str(); let localized_description = NSString::wrap(msg_send![error, localizedDescription]);
if localized_description != "" { let e = localized_description.to_str();
println!("{:?}", localized_description); if e != "" {
println!("{:?}", e);
} }
}); });

View file

@ -19,8 +19,8 @@ use crate::utils::load;
/// Called when an `alert()` from the underlying `WKWebView` is fired. Will call over to your /// Called when an `alert()` from the underlying `WKWebView` is fired. Will call over to your
/// `WebViewController`, where you should handle the event. /// `WebViewController`, where you should handle the event.
extern fn alert<T: WebViewDelegate>(_: &Object, _: Sel, _: id, s: id, _: id, complete: id) { extern fn alert<T: WebViewDelegate>(_: &Object, _: Sel, _: id, _: id, _: id, complete: id) {
let alert = NSString::wrap(s).to_str(); //let alert = NSString::wrap(s).to_str();
// @TODO: This is technically (I think?) a private method, and there's some other dance that // @TODO: This is technically (I think?) a private method, and there's some other dance that
// needs to be done here involving taking the pointer/invoke/casting... but this is fine for // needs to be done here involving taking the pointer/invoke/casting... but this is fine for
@ -47,9 +47,9 @@ extern fn on_message<T: WebViewDelegate>(this: &Object, _: Sel, _: id, script_me
let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR); let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
unsafe { unsafe {
let name = NSString::wrap(msg_send![script_message, name]).to_str(); let name = NSString::wrap(msg_send![script_message, name]);
let body = NSString::wrap(msg_send![script_message, body]).to_str(); let body = NSString::wrap(msg_send![script_message, body]);
delegate.on_message(name, body); delegate.on_message(name.to_str(), body.to_str());
} }
} }
@ -107,9 +107,9 @@ extern fn handle_download<T: WebViewDelegate>(this: &Object, _: Sel, download: i
let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR); let delegate = load::<T>(this, WEBVIEW_DELEGATE_PTR);
let handler = handler as *const Block<(objc::runtime::BOOL, id), c_void>; let handler = handler as *const Block<(objc::runtime::BOOL, id), c_void>;
let filename = NSString::wrap(suggested_filename).to_str(); let filename = NSString::wrap(suggested_filename);
delegate.run_save_panel(filename, move |can_overwrite, path| unsafe { delegate.run_save_panel(filename.to_str(), move |can_overwrite, path| unsafe {
if path.is_none() { if path.is_none() {
let _: () = msg_send![download, cancel]; let _: () = msg_send![download, cancel];
} }