Further work on iOS support, mulling over how to make this cleaner

This commit is contained in:
Ryan McGrath 2020-04-02 13:16:20 -07:00
parent 47ddf7f5a3
commit 0c604c2e84
No known key found for this signature in database
GPG key ID: 811674B62B666830
5 changed files with 57 additions and 99 deletions

View file

@ -16,7 +16,7 @@ use url::Url;
use crate::error::AppKitError;
use crate::foundation::{id, nil, BOOL, YES, NO, NSUInteger, NSArray, NSString};
use crate::ios::app::{APP_PTR, AppDelegate, APP_DELEGATE};
use crate::ios::app::{AppDelegate, APP_DELEGATE};
use crate::user_activity::UserActivity;
#[cfg(feature = "cloudkit")]
@ -49,8 +49,6 @@ pub(crate) fn register_app_delegate_class<T: AppDelegate>() -> *const Class {
let superclass = class!(NSObject);
let mut decl = ClassDecl::new("RSTAppDelegate", superclass).unwrap();
decl.add_ivar::<usize>(APP_PTR);
// Launching Applications
decl.add_method(sel!(application:didFinishLaunchingWithOptions:), did_finish_launching::<T> as extern fn(&Object, _, _, id) -> BOOL);

View file

@ -4,7 +4,7 @@
//! heavily by lifecycle events - in this case, your boilerplate would look something like this:
//!
//! ```rust,no_run
//! use cacao::app::{App, AppDelegate};
//! use cacao::ios::app::{App, AppDelegate};
//! use cacao::window::Window;
//!
//! #[derive(Default)]
@ -17,7 +17,7 @@
//! }
//!
//! fn main() {
//! App::new("com.my.app", BasicApp::default()).run();
//! App::new(BasicApp::default()).run();
//! }
//! ```
//!
@ -43,6 +43,7 @@ use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, nil, YES, NO, NSString, NSUInteger, AutoReleasePool};
use crate::notification_center::Dispatcher;
use crate::utils::activate_cocoa_multithreading;
mod class;
use class::register_app_class;
@ -56,11 +57,10 @@ pub use enums::*;
mod traits;
pub use traits::AppDelegate;
pub(crate) static APP_PTR: &str = "rstAppPtr";
pub(crate) static mut APP_DELEGATE: usize = 0;
//#[link(name="UIApplicationMain")]
extern "C" {
/// Required for iOS applications to initialize.
fn UIApplicationMain(
argc: c_int,
argv: *const *const c_char,
@ -76,30 +76,9 @@ fn shared_application<F: Fn(id)>(handler: F) {
handler(app);
}
/// A helper method for ensuring that Cocoa is running in multi-threaded mode.
/// Wraps `UIApplication` and associated lifecycle pieces.
///
/// Why do we need this? According to Apple, if you're going to make use of standard POSIX threads,
/// you need to, before creating and using a POSIX thread, first create and immediately detach a
/// `NSThread`. This ensures that Cocoa utilizes proper locking in certain places where it might
/// not be doing so for performance reasons.
///
/// In general, you should aim to just start all of your work inside of your `AppDelegate` methods.
/// There are some cases where you might want to do things before that, though - and if you spawn a
/// thread there, just call this first... otherwise you may have some unexpected issues later on.
///
/// _(This is called inside the `App::new()` construct for you already, so as long as you're doing
/// nothing before your `AppDelegate`, you can pay this no mind)._
pub fn activate_cocoa_multithreading() {
unsafe {
let thread: id = msg_send![class!(NSThread), new];
let _: () = msg_send![thread, start];
}
}
/// A wrapper for `NSApplication` on macOS, and `UIApplication` on iOS.
///
/// It holds (retains) a pointer to the Objective-C runtime shared application object, as well as
/// handles setting up a few necessary pieces:
/// Handles setting up a few necessary pieces:
///
/// - It injects an `NSObject` subclass to act as a delegate for lifecycle events.
/// - It ensures that Cocoa, where appropriate, is operating in multi-threaded mode so POSIX
@ -109,40 +88,40 @@ pub fn activate_cocoa_multithreading() {
/// implement the `Dispatcher` trait to receive messages that you might dispatch from deeper in the
/// application.
pub struct App<T = (), M = ()> {
//pub inner: Id<Object>,
//pub objc_delegate: Id<Object>,
pub delegate: Box<T>,
pub pool: AutoReleasePool,
_t: std::marker::PhantomData<M>
}
impl<T> App<T> {
/// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called.
/// If you're wondering where to go from here... you need an `AppDelegate` that implements
/// `did_finish_launching`. :)
/// Handles calling through to `UIApplicationMain()`, ensuring that it's using our custom
/// `UIApplication` and `UIApplicationDelegate` classes.
pub fn run(&self) {
unsafe {
// create a vector of zero terminated strings
let args = std::env::args().map(|arg| CString::new(arg).unwrap() ).collect::<Vec<CString>>();
// convert the strings to raw pointers
let c_args = args.iter().map(|arg| arg.as_ptr()).collect::<Vec<*const c_char>>();
let s = NSString::new("RSTApplication");
let s2 = NSString::new("RSTAppDelegate");
unsafe {
UIApplicationMain(c_args.len() as c_int, c_args.as_ptr(), s.into_inner(), s2.into_inner());
self.pool.drain();
}
self.pool.drain();
}
}
impl<T> App<T> where T: AppDelegate + 'static {
/// Creates an NSAutoReleasePool, configures various NSApplication properties (e.g, activation
/// policies), injects an `NSObject` delegate wrapper, and retains everything on the
/// Objective-C side of things.
pub fn new(_bundle_id: &str, delegate: T) -> Self {
// set_bundle_id(bundle_id);
/// iOS manages creating a new Application (`UIApplication`) differently than you'd expect if
/// you were looking at the macOS side of things.
///
/// In this case, we're primarily concerned with shoving our `AppDelegate` to a place we can
/// retrieve it later on. While this is unsafe behavior, it's ultimately no different than
/// shoving the pointer onto the delegate like we do on the macOS side of things.
///
/// This also handles ensuring that our subclasses exist in the Objective-C runtime *before*
/// `UIApplicationMain` is called.
pub fn new(delegate: T) -> Self {
activate_cocoa_multithreading();
let pool = AutoReleasePool::new();
@ -154,23 +133,6 @@ impl<T> App<T> where T: AppDelegate + 'static {
unsafe {
APP_DELEGATE = delegate_ptr as usize;
}
//(&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize);
/*let inner = unsafe {
let app: id = msg_send![register_app_class(), sharedApplication];
let _: () = msg_send![app, setActivationPolicy:0];
println!("1");
Id::from_ptr(app)
};
let objc_delegate = unsafe {
let delegate_class = register_app_delegate_class::<T>();
let delegate: id = msg_send![delegate_class, new];
let _: () = msg_send![&*inner, setDelegate:delegate];
println!("2");
Id::from_ptr(delegate)
};*/
App {
delegate: app_delegate,
@ -185,15 +147,5 @@ impl<T, M> App<T, M> where M: Send + Sync + 'static, T: AppDelegate + Dispatcher
/// Dispatches a message by grabbing the `sharedApplication`, getting ahold of the delegate,
/// and passing back through there. All messages are currently dispatched on the main thread.
pub fn dispatch(message: M) {
/*
let queue = dispatch::Queue::main();
queue.exec_async(move || unsafe {
let app: id = msg_send![register_app_class(), sharedApplication];
let app_delegate: id = msg_send![app, delegate];
let delegate_ptr: usize = *(*app_delegate).get_ivar(APP_PTR);
let delegate = delegate_ptr as *const T;
(&*delegate).on_message(message);
});*/
}
}

View file

@ -1,4 +1,8 @@
//! iOS.
//! iOS-specific implementations.
//!
//! In general, this framework tries to make things "just work" with regards to AppKit and UIKit
//! differences. With that said, there are certain things that just don't map between the two - for
//! iOS, these things are contained here.
mod app;
pub use app::*;

View file

@ -41,6 +41,7 @@ use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, nil, YES, NO, NSUInteger, AutoReleasePool};
use crate::macos::menu::Menu;
use crate::notification_center::Dispatcher;
use crate::utils::activate_cocoa_multithreading;
mod class;
use class::register_app_class;
@ -63,26 +64,6 @@ fn shared_application<F: Fn(id)>(handler: F) {
handler(app);
}
/// A helper method for ensuring that Cocoa is running in multi-threaded mode.
///
/// Why do we need this? According to Apple, if you're going to make use of standard POSIX threads,
/// you need to, before creating and using a POSIX thread, first create and immediately detach a
/// `NSThread`. This ensures that Cocoa utilizes proper locking in certain places where it might
/// not be doing so for performance reasons.
///
/// In general, you should aim to just start all of your work inside of your `AppDelegate` methods.
/// There are some cases where you might want to do things before that, though - and if you spawn a
/// thread there, just call this first... otherwise you may have some unexpected issues later on.
///
/// _(This is called inside the `App::new()` construct for you already, so as long as you're doing
/// nothing before your `AppDelegate`, you can pay this no mind)._
pub fn activate_cocoa_multithreading() {
unsafe {
let thread: id = msg_send![class!(NSThread), new];
let _: () = msg_send![thread, start];
}
}
/// A wrapper for `NSApplication` on macOS, and `UIApplication` on iOS.
///
/// It holds (retains) a pointer to the Objective-C runtime shared application object, as well as

View file

@ -4,17 +4,20 @@
use core_graphics::base::CGFloat;
use objc::{class, msg_send, sel, sel_impl};
use objc::{Encode, Encoding};
use objc::runtime::Object;
use objc_id::ShareId;
use crate::foundation::id;
/// A generic trait that's used throughout multiple different controls in this framework - acts as
/// a guard for whether something is a (View|etc)Controller. Only needs to return the backing node.
pub trait Controller {
fn get_backing_node(&self) -> ShareId<Object>;
}
/// Utility method for taking a pointer and grabbing the corresponding delegate in Rust. This is
/// theoretically safe:
///
@ -58,3 +61,23 @@ unsafe impl Encode for CGSize {
unsafe { Encoding::from_str(&encoding) }
}
}
/// A helper method for ensuring that Cocoa is running in multi-threaded mode.
///
/// Why do we need this? According to Apple, if you're going to make use of standard POSIX threads,
/// you need to, before creating and using a POSIX thread, first create and immediately detach a
/// `NSThread`. This ensures that Cocoa utilizes proper locking in certain places where it might
/// not be doing so for performance reasons.
///
/// In general, you should aim to just start all of your work inside of your `AppDelegate` methods.
/// There are some cases where you might want to do things before that, though - and if you spawn a
/// thread there, just call this first... otherwise you may have some unexpected issues later on.
///
/// _(This is called inside the `App::new()` construct for you already, so as long as you're doing
/// nothing before your `AppDelegate`, you can pay this no mind)._
pub fn activate_cocoa_multithreading() {
unsafe {
let thread: id = msg_send![class!(NSThread), new];
let _: () = msg_send![thread, start];
}
}