[ios] Fix iOS demo build.

Several recent changes to the way that subclass registration is done
inadvertently impacted part of how the iOS side of things works. iOS
app initialization requires passing known class names to
`UIApplicationMain` to let the system instantiate things; we append a
random suffix to class names by default to avoid issues on macOS where
classes that are being loaded from a bundle seem to collide in the ObjC
runtime.

This change brings in two new methods in `foundation/class` to
explicitly bypass the dynamic subclass suffix generation for the rare
cases (such as the above) where we know we need it.
This commit is contained in:
Ryan McGrath 2023-06-08 15:51:59 -07:00
parent 44ad4886e7
commit 3e3122f54c
No known key found for this signature in database
GPG key ID: DA6CBD9233593DEA
7 changed files with 87 additions and 24 deletions

View file

@ -58,6 +58,11 @@ impl ClassMap {
ClassMap(RwLock::new(HashMap::new())) ClassMap(RwLock::new(HashMap::new()))
} }
/// A publicly accessible load method that just passes through our global singleton.
pub fn static_load(class_name: &'static str, superclass_name: Option<&'static str>) -> Option<*const Class> {
CLASSES.load(class_name, superclass_name)
}
/// Attempts to load a previously registered class. /// Attempts to load a previously registered class.
/// ///
/// This checks our internal map first, and then calls out to the Objective-C runtime to ensure /// This checks our internal map first, and then calls out to the Objective-C runtime to ensure
@ -110,6 +115,31 @@ impl ClassMap {
} }
} }
/// Calls through to `load_or_register_class_with_optional_generated_suffix`, specifying that we
/// should append a random suffix to the generated class name. This is important for situations
/// where we may be loading classes from e.g two different bundles and need to avoid collision.
///
/// Some parts of the codebase (e.g, iOS UIApplication registration) may need to know the name
/// ahead of time and are not concerned about potential duplications. These cases should feel free
/// to call through to `load_or_register_class_with_optional_generated_suffix` directly, as they
/// are comparatively rare in nature.
///
/// > In the future, this indirection may be removed and the return type of
/// > `load_or_register_class_with_optional_generated_suffix` will be altered to return the generated
/// > class name - but most cases do not need this and it would be a larger change to orchestrate at
/// > the moment.
#[inline(always)]
pub fn load_or_register_class<F>(
superclass_name: &'static str,
subclass_name: &'static str,
config: F
) -> *const Class
where
F: Fn(&mut ClassDecl) + 'static
{
load_or_register_class_with_optional_generated_suffix(superclass_name, subclass_name, true, config)
}
/// Attempts to load a subclass, given a `superclass_name` and subclass_name. If /// Attempts to load a subclass, given a `superclass_name` and subclass_name. If
/// the subclass cannot be loaded, it's dynamically created and injected into /// the subclass cannot be loaded, it's dynamically created and injected into
/// the runtime, and then returned. The returned value can be used for allocating new instances of /// the runtime, and then returned. The returned value can be used for allocating new instances of
@ -124,7 +154,12 @@ impl ClassMap {
/// ///
/// There's definitely room to optimize here, but it works for now. /// There's definitely room to optimize here, but it works for now.
#[inline(always)] #[inline(always)]
pub fn load_or_register_class<F>(superclass_name: &'static str, subclass_name: &'static str, config: F) -> *const Class pub fn load_or_register_class_with_optional_generated_suffix<F>(
superclass_name: &'static str,
subclass_name: &'static str,
should_append_random_subclass_name_suffix: bool,
config: F
) -> *const Class
where where
F: Fn(&mut ClassDecl) + 'static F: Fn(&mut ClassDecl) + 'static
{ {
@ -141,7 +176,8 @@ where
// guarantees that we almost always have a unique name to register with the ObjC runtime). // guarantees that we almost always have a unique name to register with the ObjC runtime).
// //
// For more context, see: https://github.com/ryanmcgrath/cacao/issues/63 // For more context, see: https://github.com/ryanmcgrath/cacao/issues/63
let objc_subclass_name = format!( let objc_subclass_name = match should_append_random_subclass_name_suffix {
true => format!(
"{}_{}_{}", "{}_{}_{}",
subclass_name, subclass_name,
superclass_name, superclass_name,
@ -151,7 +187,10 @@ where
let t = u128::from(s) * (u128::from(s ^ 0xe7037ed1a0b428db)); let t = u128::from(s) * (u128::from(s ^ 0xe7037ed1a0b428db));
((t >> 64) as u64) ^ (t as u64) ((t >> 64) as u64) ^ (t as u64)
}) })
); ),
false => format!("{}_{}", subclass_name, superclass_name)
};
match ClassDecl::new(&objc_subclass_name, unsafe { &*superclass }) { match ClassDecl::new(&objc_subclass_name, unsafe { &*superclass }) {
Some(mut decl) => { Some(mut decl) => {

View file

@ -28,7 +28,8 @@ mod array;
pub use array::NSArray; pub use array::NSArray;
mod class; mod class;
pub use class::load_or_register_class; pub use class::{load_or_register_class, load_or_register_class_with_optional_generated_suffix};
pub(crate) use class::ClassMap;
mod data; mod data;
pub use data::NSData; pub use data::NSData;

View file

@ -4,9 +4,16 @@
use objc::runtime::Class; use objc::runtime::Class;
use crate::foundation::load_or_register_class; use crate::foundation::load_or_register_class_with_optional_generated_suffix;
/// Used for injecting a custom UIApplication. Currently does nothing. /// Used for injecting a custom UIApplication. Currently does nothing.
pub(crate) fn register_app_class() -> *const Class { pub(crate) fn register_app_class() -> *const Class {
load_or_register_class("UIApplication", "RSTApplication", |decl| unsafe {}) let should_generate_suffix = false;
load_or_register_class_with_optional_generated_suffix(
"UIApplication",
"RSTApplication",
should_generate_suffix,
|decl| {}
)
} }

View file

@ -6,7 +6,7 @@ use objc::runtime::{Class, Object, Sel};
use objc::{sel, sel_impl}; use objc::{sel, sel_impl};
//use crate::error::Error; //use crate::error::Error;
use crate::foundation::{id, load_or_register_class, BOOL, YES}; use crate::foundation::{id, load_or_register_class_with_optional_generated_suffix, BOOL, YES};
use crate::uikit::app::{AppDelegate, APP_DELEGATE}; use crate::uikit::app::{AppDelegate, APP_DELEGATE};
use crate::uikit::scene::{SceneConnectionOptions, SceneSession}; use crate::uikit::scene::{SceneConnectionOptions, SceneSession};
@ -41,7 +41,9 @@ extern "C" fn configuration_for_scene_session<T: AppDelegate>(this: &Object, _:
/// Registers an `NSObject` application delegate, and configures it for the various callbacks and /// Registers an `NSObject` application delegate, and configures it for the various callbacks and
/// pointers we need to have. /// pointers we need to have.
pub(crate) fn register_app_delegate_class<T: AppDelegate>() -> *const Class { pub(crate) fn register_app_delegate_class<T: AppDelegate>() -> *const Class {
load_or_register_class("NSObject", "RSTAppDelegate", |decl| unsafe { let should_generate_suffix = false;
load_or_register_class_with_optional_generated_suffix("NSObject", "RSTAppDelegate", should_generate_suffix, |decl| unsafe {
// Launching Applications // Launching Applications
decl.add_method( decl.add_method(
sel!(application:didFinishLaunchingWithOptions:), sel!(application:didFinishLaunchingWithOptions:),

View file

@ -149,13 +149,22 @@ impl<T, W, F> App<T, W, F> {
let c_args = args.iter().map(|arg| arg.as_ptr()).collect::<Vec<*const c_char>>(); let c_args = args.iter().map(|arg| arg.as_ptr()).collect::<Vec<*const c_char>>();
let mut s = NSString::new("RSTApplication"); let mut s = NSString::new("RSTApplication_UIApplication");
let mut s2 = NSString::new("RSTAppDelegate"); let mut s2 = NSString::new("RSTAppDelegate_NSObject");
unsafe { unsafe {
println!("RUNNING?!");
UIApplicationMain(c_args.len() as c_int, c_args.as_ptr(), s.into(), s2.into()); UIApplicationMain(c_args.len() as c_int, c_args.as_ptr(), s.into(), s2.into());
} }
self.pool.drain(); //self.pool.drain();
}
}
impl<T, W, F> Drop for App<T, W, F> {
fn drop(&mut self) {
println!("DROPPING");
//self.pool.drain();
} }
} }

View file

@ -2,7 +2,7 @@ use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use objc_id::Id; use objc_id::Id;
use crate::foundation::{id, NSString}; use crate::foundation::{id, NSString, ClassMap};
use crate::uikit::scene::SessionRole; use crate::uikit::scene::SessionRole;
/// A wrapper for UISceneConfiguration. /// A wrapper for UISceneConfiguration.
@ -15,6 +15,9 @@ impl SceneConfig {
/// Creates a new `UISceneConfiguration` with the specified name and session role, retains it, /// Creates a new `UISceneConfiguration` with the specified name and session role, retains it,
/// and returns it. /// and returns it.
pub fn new(name: &str, role: SessionRole) -> Self { pub fn new(name: &str, role: SessionRole) -> Self {
let delegate_class = ClassMap::static_load("RSTWindowSceneDelegate", Some("UIResponder"))
.expect("A crucial iOS step was missed - the scene delegate class is either not loaded or misnamed");
SceneConfig(unsafe { SceneConfig(unsafe {
let name = NSString::new(name); let name = NSString::new(name);
let role = NSString::from(role); let role = NSString::from(role);
@ -23,7 +26,7 @@ impl SceneConfig {
let config: id = msg_send![cls, configurationWithName:name sessionRole:role]; let config: id = msg_send![cls, configurationWithName:name sessionRole:role];
let _: () = msg_send![config, setSceneClass: class!(UIWindowScene)]; let _: () = msg_send![config, setSceneClass: class!(UIWindowScene)];
let _: () = msg_send![config, setDelegateClass: class!(RSTWindowSceneDelegate)]; let _: () = msg_send![config, setDelegateClass: delegate_class];
Id::from_ptr(config) Id::from_ptr(config)
}) })

View file

@ -1,7 +1,7 @@
use objc::runtime::{Class, Object, Protocol, Sel}; use objc::runtime::{Class, Object, Protocol, Sel};
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use crate::foundation::{id, load_or_register_class}; use crate::foundation::{id, load_or_register_class_with_optional_generated_suffix};
use crate::uikit::app::SCENE_DELEGATE_VENDOR; use crate::uikit::app::SCENE_DELEGATE_VENDOR;
use crate::uikit::scene::{Scene, SceneConnectionOptions, SceneSession, WindowSceneDelegate}; use crate::uikit::scene::{Scene, SceneConnectionOptions, SceneSession, WindowSceneDelegate};
use crate::utils::load; use crate::utils::load;
@ -45,7 +45,9 @@ extern "C" fn scene_will_connect_to_session_with_options<T: WindowSceneDelegate>
/// Registers an `NSObject` application delegate, and configures it for the various callbacks and /// Registers an `NSObject` application delegate, and configures it for the various callbacks and
/// pointers we need to have. /// pointers we need to have.
pub(crate) fn register_window_scene_delegate_class<T: WindowSceneDelegate, F: Fn() -> Box<T>>() -> *const Class { pub(crate) fn register_window_scene_delegate_class<T: WindowSceneDelegate, F: Fn() -> Box<T>>() -> *const Class {
load_or_register_class("UIResponder", "RSTWindowSceneDelegate", |decl| unsafe { let should_generate_suffix = false;
load_or_register_class_with_optional_generated_suffix("UIResponder", "RSTWindowSceneDelegate", false, |decl| unsafe {
let p = Protocol::get("UIWindowSceneDelegate").unwrap(); let p = Protocol::get("UIWindowSceneDelegate").unwrap();
// A spot to hold a pointer to // A spot to hold a pointer to