# InputMethodKit 的一個堪稱陳年大糞的設計缺陷 很多中文打字用戶,無論用原廠輸入法、還是任何一款副廠輸入法,都會有中英文混打的需求。 十幾年前開始自從 macOS 10.12 Sierra 引入基於 CpLk 的中英文輸入法切換功能以來,這個問題到現在就都沒消停過:「敲 CpLk 切換中英文輸入法時會卡頓」。每台電腦跑每個版本的 macOS 時的發病嚴重程度不一:有的可以忽略,有的卡到罵娘。 長久以來,對此問題的討論,往往被歸咎於輸入法本身:要麼是在用的副廠輸入法本身被抱怨,要麼是原廠中文輸入法本身被抱怨。 筆者按順序羅列這些事實。先講兩個技術層面的: - InputMethodKit 的 IMKInputController 所有 API 呼叫都是在 MainActor 上的。然而,因為 ObjC Header 層面曝露的 API 與 Swift Concurrency 不相容的緣故,如果你要給輸入法做 Swift Concurrency Modernization 的話,你需要一些 Dirty Trick 方便將這些 API 傳入的參數重新從 MainActor 強制解讀。 - macOS 10.7 開始引入 Objective-C ARC。這套 ARC 系統對 NSObject 副本的析構時機不可控,你無法用手動介入的方式使其暫緩析構或重新利用。而 InputMethodKit 的 API 在設計時是針對 macOS 10.5 Leopard 設計的。這就帶來了一些與 ARC 有關的 MainActor 調度壓力問題(甚至阻塞)。下文會提到具體的問題情形。 再來討論使用者打字場景上的事實: - macOS 10.12 的這個 CpLk 切換功能的本質不是中英文打字模式切換,而是輸入法切換。 - macOS 哪怕英文打字也是由一個專門的輸入法負責的。大部分英語鍵盤的電腦上,這個輸入法叫 Apple ABC,對應美規鍵盤。 - 每個輸入法在剛被切換出來時,會觸發這個輸入法自身的 IMKInputController Instance 的創建以及其 activateServer 操作(以及可能有的一系列追加操作)。然後才是這個 Client 之前對接的輸入法的 IMKInputController 副本的 Deactivation。 - 很多中英文混合打字的用戶經常會在 ABC 與中文輸入法之間來回切換。由於這種情況下兩者所服務的 IMKTextInput Client 是相同的,所以就出現了 MainActor 塞車。而且,過於高頻的來回切換,會給 IMKInputController 所用的 Objective-C ARC 帶來壓力。ARC 廢件釋放與物件交互都發生在 MainActor 上,必然會發生塞車。 - 「在同一個 client 切換輸入法」的過程會牽涉到前後兩個 IMKInputController 副本各自的對 client() 的操作。輸入法開發者現在最佳的範式就是讓 deactivateServer 在 MainActor 上 Async 脫手操作、且不在 deactivateServer 階段做 client() API 的文字寫入/內容顯示交互,因為這種擦除操作會由系統代勞。但是,這個由系統代勞的擦除操作也是發生在 MainActor 上的。這就出現了 MainActor 的任務的時序衝突。InputMethodKit 內部應該是有自己的方式處理這個衝突,然而代價就是阻塞開銷。 - 有些輸入法難免會在 activateServer 階段引入與 client() 有關的交互,但這個開銷可能在所難免,因為你可能必須得對 client 套用指定的 Ukelele 佈局。再加上 client() 身為 IMKTextInput Client 沒有真正意義上的 Async API,輸入法開發者只能假設所有這類 Client 的這些操作都是 MainActor 阻塞操作,然後乾瞪眼。 這就導致那些經常用 CpLk 超高頻中英切換打字的使用者們必然會罵娘。但他們不知道問題爛在系統層面,於是就只能罵輸入法。或罵系統內建注音爛,或罵自己在用的副廠輸入法不修故障。 這個現狀恐怕真的沒有解決方法,要淘汰的是 macOS 的整個 InputMethodKit 體系。整個 API 交互體系都需要刮骨療毒重新設計。 或者,自力救濟(下述幾條都很重要): - 所有 macOS 中文用戶都請關掉以 CpLk(「中/英」鍵)切換中英文輸入法的特性,讓系統遵循 macOS 10.11 El Capitan 為止的 CpLk 行為。 - 中文輸入法開發者們都請集體給自己的輸入法實裝 CpLk 英打模式。英打模式下的 Layout 同步問題也可以用 client().overrideKeyboard() 來解決,不過這是個阻塞操作。而且,如果接收打字的視窗是輸入法自己的視窗的話,務必 MainActor Async 執行 `client().overrideKeyboard()`,否則必然會卡死幾十秒。 - IMKInputController Subclass 不要擁有任何物件。所有與打字有關的業務模組全部塞到 singleton 或者可以複用的 instance 裡面。instance 与 IMKInputController Subclass 之间可以使用其他通讯交互手段。 P.S.: macOS 26 在 AppKit / InputMethodKit 的 NSObject Types 的 ARC 回收方面的效率低下之故障反而放大了這個問題。 $ EOF.
×
Sign in
Email
Password
Forgot password
or
Sign in via Google
Sign in via Facebook
Sign in via X(Twitter)
Sign in via GitHub
Sign in via Dropbox
Sign in with Wallet
Wallet (
)
Connect another wallet
Continue with a different method
New to HackMD?
Sign up
By signing in, you agree to our
terms of service
.