@@ -127,6 +127,16 @@ function supportsMultipleScenes(): boolean {
127127 return UIApplication . sharedApplication ?. supportsMultipleScenes ;
128128}
129129
130+ /**
131+ * Number of times the JS runtime has been soft-rebooted in this process via
132+ * NativeScriptRuntime.reloadApplication / restartWithConfig. 0 on first boot.
133+ * Provided as a global by the iOS runtime (v9+); older runtimes report 0.
134+ */
135+ function getRuntimeReloadCount ( ) : number {
136+ const runtime = ( globalThis as any ) . NativeScriptRuntime ;
137+ return runtime && typeof runtime . reloadCount === 'number' ? runtime . reloadCount : 0 ;
138+ }
139+
130140@NativeClass
131141class Responder extends UIResponder implements UIApplicationDelegate {
132142 get window ( ) : UIWindow {
@@ -269,6 +279,13 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
269279
270280 private _notificationObservers : NotificationObserver [ ] = [ ] ;
271281
282+ // Strong references to delegates recreated after an in-process soft reboot
283+ // (NativeScriptRuntime.reloadApplication). UIApplication.delegate is an
284+ // `assign` property and UIScene keeps its own reference to the delegate we
285+ // replace, so without these the fresh instances would be deallocated.
286+ private _softRebootAppDelegate : UIApplicationDelegate ;
287+ private _softRebootSceneDelegates = new Map < UIScene , UIWindowSceneDelegate > ( ) ;
288+
272289 displayedOnce = false ;
273290 displayedLinkTarget : CADisplayLinkTarget ;
274291 displayedLink : CADisplayLink ;
@@ -323,6 +340,8 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
323340 }
324341
325342 private runAsEmbeddedApp ( ) {
343+ this . _reattachNativeDelegatesAfterSoftReboot ( ) ;
344+
326345 // TODO: this rootView should be held alive until rootController dismissViewController is called.
327346 const rootView = this . createRootView ( this . _rootView , true ) ;
328347 if ( ! rootView ) {
@@ -357,6 +376,13 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
357376 window = UIWindow . alloc ( ) . initWithWindowScene ( targetScene ) ;
358377 this . _setWindowForScene ( window , targetScene ) ;
359378 this . _setupWindowForScene ?.( window , targetScene ) ;
379+
380+ // If the scene's delegate was recreated after a soft reboot, point it
381+ // at the new window so `scene.delegate.window` queries resolve.
382+ const freshSceneDelegate = this . _softRebootSceneDelegates . get ( targetScene ) ;
383+ if ( freshSceneDelegate ) {
384+ freshSceneDelegate . window = window ;
385+ }
360386 }
361387 }
362388
@@ -367,12 +393,12 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
367393 // May be null on a freshly recreated window — expected; the replace-root
368394 // path below sets it. Only the embedder path needs an existing controller.
369395 const rootController = window . rootViewController ;
370- const embedderDelegate = NativeScriptEmbedder . sharedInstance ( ) . delegate ;
396+ const embedderDelegate = NativeScriptEmbedder . sharedInstance ( ) . delegate ;
371397
372- // Embed into host app requires an existing root view controller
373- if ( embedderDelegate && ! rootController ) {
374- return ;
375- }
398+ // Embed into host app requires an existing root view controller
399+ if ( embedderDelegate && ! rootController ) {
400+ return ;
401+ }
376402
377403 const controller = this . getViewController ( rootView ) ;
378404
@@ -406,6 +432,67 @@ export class iOSApplication extends ApplicationCommon implements IiOSApplication
406432 this . notifyAppStarted ( ) ;
407433 }
408434
435+ /**
436+ * After an in-process soft reboot (NativeScriptRuntime.reloadApplication /
437+ * restartWithConfig), the Objective-C delegate classes created by the
438+ * previous JS isolate still exist and UIKit keeps dispatching to their
439+ * now-inert instances: their method callbacks bail out because the isolate
440+ * that implemented them is gone. Notification-center observers are
441+ * re-registered by the new isolate, but delegate-based dispatch (custom
442+ * UIApplicationDelegate methods like push-token/openURL callbacks, and the
443+ * UIScene delegates used by scene-lifecycle apps) stays pinned to the old
444+ * bundle. Recreate those delegates from this bundle's classes and re-point
445+ * UIKit at them.
446+ */
447+ private _reattachNativeDelegatesAfterSoftReboot ( ) : void {
448+ if ( getRuntimeReloadCount ( ) <= 0 ) {
449+ // First boot: UIApplicationMain (or the host app) set up delegates.
450+ return ;
451+ }
452+
453+ if ( isEmbedded ( ) ) {
454+ // The host app owns the UIApplication delegate; never touch it.
455+ return ;
456+ }
457+
458+ const app = UIApplication . sharedApplication ;
459+ if ( ! app ) {
460+ return ;
461+ }
462+
463+ // Fresh application delegate from the new bundle. Assigning `delegate`
464+ // does not retain (unlike the UIApplicationMain launch path), so keep a
465+ // strong reference ourselves.
466+ this . delegate ??= Responder as any ;
467+ const freshDelegate = ( < any > this . delegate ) . new ( ) as UIApplicationDelegate ;
468+ this . _softRebootAppDelegate = freshDelegate ;
469+ app . delegate = freshDelegate ;
470+
471+ // Re-point already-connected scenes at fresh scene delegates so scene
472+ // lifecycle and user-implemented scene delegate methods (shortcuts,
473+ // openURLContexts, userActivity continuation, etc.) reach this isolate.
474+ // Newly connecting scenes are covered by the fresh application delegate's
475+ // applicationConfigurationForConnectingSceneSessionOptions, which returns
476+ // this bundle's SceneDelegate class.
477+ if ( this . supportsScenes ( ) ) {
478+ this . _softRebootSceneDelegates . clear ( ) ;
479+ const scenes = app . connectedScenes ?. allObjects ;
480+ for ( let i = 0 ; scenes && i < scenes . count ; i ++ ) {
481+ const scene = scenes . objectAtIndex ( i ) ;
482+ if ( ! ( scene instanceof UIWindowScene ) ) {
483+ continue ;
484+ }
485+ const freshSceneDelegate = SceneDelegate . new ( ) as UIWindowSceneDelegate ;
486+ scene . delegate = freshSceneDelegate ;
487+ this . _softRebootSceneDelegates . set ( scene , freshSceneDelegate ) ;
488+ }
489+ }
490+
491+ if ( Trace . isEnabled ( ) ) {
492+ Trace . write ( `Reattached application delegate${ this . _softRebootSceneDelegates . size ? ` and ${ this . _softRebootSceneDelegates . size } scene delegate(s)` : '' } after soft reboot (reloadCount: ${ getRuntimeReloadCount ( ) } )` , Trace . categories . NativeLifecycle ) ;
493+ }
494+ }
495+
409496 private getViewController ( rootView : View ) : UIViewController {
410497 let viewController : UIViewController = rootView . viewController || rootView . ios ;
411498
0 commit comments