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:
@@ -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()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user