fix(keyboard): stabilize Flow startup under memory pressure

Delay competing Rime work, add extension memory telemetry and stress coverage, and refresh the 1.8 release assets and metadata for build 79.
This commit is contained in:
Rocky
2026-08-18 10:24:02 +08:00
parent aa1059135f
commit 0f9280bd00
27 changed files with 809 additions and 105 deletions
+22 -1
View File
@@ -7,9 +7,30 @@
// dyld (usually host coexistence or an oversized Shared+Rime mapping).
#import <Foundation/Foundation.h>
#include <mach/mach.h>
#include <mach/task_info.h>
#include <unistd.h>
static double OSGKeyboardExtPhysFootprintMB(void) {
task_vm_info_data_t info = {0};
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
kern_return_t result = task_info(
mach_task_self(),
TASK_VM_INFO,
(task_info_t)&info,
&count
);
if (result != KERN_SUCCESS) {
return -1;
}
return (double)info.phys_footprint / 1048576.0;
}
__attribute__((constructor))
static void OSGKeyboardExtBootProbe(void) {
NSLog(@"[OSGDiag/boot] dyld.constructor pid=%d", getpid());
NSLog(
@"[OSGDiag/boot] dyld.constructor pid=%d foot=%.1fMB",
getpid(),
OSGKeyboardExtPhysFootprintMB()
);
}
+51 -1
View File
@@ -66,6 +66,18 @@ public final class KeyboardViewController: UIInputViewController {
/// Coalesces host-document refreshes after mutations issued by this keyboard.
private var assistantFieldActionRefreshTask: Task<Void, Never>?
private var memoryTelemetryContext: String {
let language = typingSessionStorage?.language.rawValue ?? "-"
return "surface=\(state.surface.rawValue) language=\(language) "
+ "fullAccess=\(hasFullAccess ? 1 : 0) "
+ "clipboard=\(state.clipboardHistoryEnabled ? 1 : 0)"
}
private func recordMemory(_ stage: String, details: String? = nil) {
KeyboardExtensionMemoryTelemetry.updateContext(memoryTelemetryContext)
KeyboardExtensionMemoryTelemetry.record(stage, details: details)
}
private var editHintScheduler: EditHintScheduler!
private var textInserter: KeyboardTextInserter!
private var flowCoordinator: KeyboardFlowCoordinator!
@@ -104,14 +116,22 @@ public final class KeyboardViewController: UIInputViewController {
// MARK: - Init
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
KeyboardExtensionMemoryTelemetry.begin(
context: "surface=uninitialized language=- fullAccess=- clipboard=-"
)
OSGDiag.log("KVC.init(nib) begin \(OSGDiag.memoryTag())", category: "boot")
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
recordMemory("KVC.init.done")
OSGDiag.log("KVC.init(nib) done \(OSGDiag.memoryTag())", category: "boot")
}
public required init?(coder: NSCoder) {
KeyboardExtensionMemoryTelemetry.begin(
context: "surface=uninitialized language=- fullAccess=- clipboard=-"
)
OSGDiag.log("KVC.init(coder) begin \(OSGDiag.memoryTag())", category: "boot")
super.init(coder: coder)
recordMemory("KVC.init.done")
OSGDiag.log("KVC.init(coder) done \(OSGDiag.memoryTag())", category: "boot")
}
@@ -128,6 +148,11 @@ public final class KeyboardViewController: UIInputViewController {
// Voice-first keyboard hide the misleading "English" subtitle in Settings.
primaryLanguage = "mis"
let preferred = TypingInputConfiguration.preferredSurfaceOnOpen()
recordMemory(
"KVC.viewDidLoad.begin",
details: "preferredSurface=\(preferred.rawValue)"
)
KeyboardExtensionMemoryTelemetry.startBootSampling()
OSGDiag.log(
"KVC.viewDidLoad begin preferredSurface=\(preferred.rawValue) "
+ "fullAccess=\(hasFullAccess) \(OSGDiag.memoryTag())",
@@ -145,19 +170,31 @@ public final class KeyboardViewController: UIInputViewController {
installKeyboardHeight()
configureDictationBehavior()
installServices()
recordMemory("KVC.viewDidLoad.afterInstallServices")
OSGDiag.log("KVC.viewDidLoad after installServices \(OSGDiag.memoryTag())", category: "boot")
// Apply open preference before mounting SwiftUI so the first frame is
// already voice or typing avoids a visible surface flash.
applyPreferredSurfaceOnOpen()
recordMemory("KVC.viewDidLoad.afterPreferredSurface")
OSGDiag.log("KVC.viewDidLoad after preferredSurface surface=\(state.surface.rawValue)", category: "boot")
installTypingContextProviders()
installStateActions()
installSurfaceObservers()
installSwiftUI()
recordMemory("KVC.viewDidLoad.afterInstallSwiftUI")
OSGDiag.log("KVC.viewDidLoad after installSwiftUI \(OSGDiag.memoryTag())", category: "boot")
_ = configSync.loadPersistedConfig()
let configLoadResult = configSync.loadPersistedConfig()
recordMemory(
"KVC.viewDidLoad.afterConfigLoad",
details: "result=\(configLoadResult)"
)
configSync.installDarwinObservers()
flowCoordinator.refreshSessionState()
recordMemory(
"KVC.viewDidLoad.done",
details: "sessionActive=\(FlowSessionBridge.isSessionActive() ? 1 : 0) "
+ "hostReady=\(FlowSessionBridge.isHostReady() ? 1 : 0)"
)
OSGDiag.log(
"KVC.viewDidLoad done surface=\(state.surface.rawValue) "
+ "sessionActive=\(FlowSessionBridge.isSessionActive()) "
@@ -179,6 +216,10 @@ public final class KeyboardViewController: UIInputViewController {
+ "preserve=\(flowCoordinator.preservesLifecycleOnDisappear) \(OSGDiag.memoryTag())",
category: "boot"
)
recordMemory(
"KVC.viewWillDisappear",
details: "preserve=\(flowCoordinator.preservesLifecycleOnDisappear ? 1 : 0)"
)
heightPhase = .idle
flowCoordinator.stopSessionMonitor()
// Remember what the user left on, then pre-position a reused
@@ -215,6 +256,7 @@ public final class KeyboardViewController: UIInputViewController {
+ "fullAccess=\(hasFullAccess) \(OSGDiag.memoryTag())",
category: "boot"
)
recordMemory("KVC.viewWillAppear.begin")
setNeedsUpdateOfScreenEdgesDeferringSystemGestures()
configureDictationBehavior()
KeyboardSetupBridge.markExtensionAppearance(hasFullAccess: hasFullAccess)
@@ -229,12 +271,16 @@ public final class KeyboardViewController: UIInputViewController {
// Re-warm Taptic after host app switches: SwiftUI `onAppear` often
// skips when the extension process is reused, leaving generators cold.
KeyboardHapticFeedback.prepare()
recordMemory("KVC.viewWillAppear.afterHaptics")
if state.surface == .typing {
OSGDiag.log("KVC.viewWillAppear enterTypingMode", category: "boot")
typingSession.enterTypingMode()
refreshEnglishSupplementaryLexicon()
recordMemory("KVC.viewWillAppear.afterTypingEnter")
}
clipboardCapture.keyboardDidAppear()
recordMemory("KVC.viewWillAppear.afterClipboard")
recordMemory("KVC.viewWillAppear.done")
OSGDiag.log(
"KVC.viewWillAppear done surface=\(state.surface.rawValue) \(OSGDiag.memoryTag())",
category: "boot"
@@ -263,6 +309,7 @@ public final class KeyboardViewController: UIInputViewController {
"KVC.viewDidAppear begin surface=\(state.surface.rawValue) \(OSGDiag.memoryTag())",
category: "boot"
)
recordMemory("KVC.viewDidAppear.begin")
disableSystemGestureDelays()
heightPhase = .presented
lockPresentedKeyboardHeight()
@@ -296,6 +343,7 @@ public final class KeyboardViewController: UIInputViewController {
"KVC.viewDidAppear done height=\(targetKeyboardHeight) \(OSGDiag.memoryTag())",
category: "boot"
)
recordMemory("KVC.viewDidAppear.done")
}
public override func textDidChange(_ textInput: UITextInput?) {
@@ -332,6 +380,7 @@ public final class KeyboardViewController: UIInputViewController {
public override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
recordMemory("KVC.didReceiveMemoryWarning")
OSGDiag.log(
"KVC.didReceiveMemoryWarning surface=\(state.surface.rawValue) \(OSGDiag.memoryTag())",
category: "boot"
@@ -621,6 +670,7 @@ public final class KeyboardViewController: UIInputViewController {
category: "boot"
)
state.surface = surface
recordMemory("KVC.applySurface", details: "requested=\(surface.rawValue)")
if surface == .typing {
typingSession.enterTypingMode()
refreshEnglishSupplementaryLexicon()
@@ -65,7 +65,17 @@ final class ClipboardCaptureCoordinator {
func keyboardDidAppear() {
isKeyboardVisible = true
KeyboardExtensionMemoryTelemetry.record(
"clipboard.reload.begin",
details: "enabled=\(state.clipboardHistoryEnabled ? 1 : 0) "
+ "entries=\(history.entries.count)"
)
history.reload()
KeyboardExtensionMemoryTelemetry.record(
"clipboard.reload.done",
details: "enabled=\(state.clipboardHistoryEnabled ? 1 : 0) "
+ "entries=\(history.entries.count)"
)
// A suggestion belongs to one keyboard presentation. Clear any
// presentation state left behind by a reused extension controller.
endCurrentSuggestion()