Content-Length: 532671 | pFad | https://github.com/NativeScript/NativeScript/commit/6c1479b255b82cd77e674a27ec2ce35a9a4bc3e7

ad feat(ios): handle scene delegates with soft reboot · NativeScript/NativeScript@6c1479b · GitHub
Skip to content

Commit 6c1479b

Browse files
committed
feat(ios): handle scene delegates with soft reboot
1 parent 51b957a commit 6c1479b

1 file changed

Lines changed: 92 additions & 5 deletions

File tree

packages/core/application/application.ios.ts

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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
131141
class 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

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/NativeScript/NativeScript/commit/6c1479b255b82cd77e674a27ec2ce35a9a4bc3e7

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy