如果切到输入法的时分、输入法正在运转、且有对当时 IMKTextInput Client 创立控制会话副本、且该副本正常的话,执行的会是 这个副本的 setValue(),传入的参数是当时输入法装置副本的 identifier (有些输入法会有多个装置副本,对应不同的输入形式,比方体系内建简体中文输入法就有全拼或双拼形式、内建繁体中文输入法会有注音或仓颉形式,等)。setValue() 在被呼叫的时分,如果发现这个控制会话副本没有被正确 activateServer() 的话,可能会重新 activateServer() 一遍。

如果切到输入法的时分、输入法没有运转(或没有对当时 IMKTextInput Client 创立控制会话副本)的话,会启动副本。但此刻因为副本是直接 init() 的,没有跑 activateServer(),所以无法视为正常启动,输入法天然也就无法正常工作(输入法选单也会「啥也不显现、仅显现省略号」)、需求你重新切一下。

怎么说呢,activateServer() 实际上是 IMKInputController 的两个建构子( Constructor )之一。许多 macOS 副厂输入法开发者不知道这玩意另有这么一个不需求任何参数的建构子。

许多 mac 渠道的输入法都需求一个扩大规划、来应对这个情况。我开发的威注音输入法对此的应对战略是这样的:

一、给 IMKInputController 另写一个共用的建构子:

  /// 所有建構子都會執行的共用部分,在 super.init() 之後執行。
  private func construct(client theClient: (IMKTextInput & NSObjectProtocol)? = nil) {
    DispatchQueue.main.async { [self] in
      // 威注音限制:设定 inputHandler 以及同步中心词库偏好设定。
      inputHandler = InputHandler(
        lm: LMMgr.currentLM, uom: LMMgr.currentUOM, pref: PrefMgr.shared
      )
      inputHandler?.delegate = self
      syncBaseLMPrefs() // 同步中心词库偏好设定。
      // 所有输入法共用:下述兩行很有必要,否則輸入法會在手動重啟之後無法立刻收效。
      let maybeClient = theClient ?? client()  // 留意:这样过后仍旧是 nullable IMKTextInput 类型。
      // 这儿 client 是不是 nil 都无所谓,activateServer() 自己会处理:只要是 null ,就可以啥也不做(或者仅载入词库)。
      activateServer(maybeClient)
      // 威注音限制:GCD 會觸發 inputMode 的 didSet ,所以不用擔心。
      inputMode = .init(rawValue: PrefMgr.shared.mostRecentInputMode) ?? .imeModeNULL
    }
  }

二、给 IMKInputController 实作 .init() 这个无参数的建构子。

  /// 對用以設定委任物件的控制器型別進行初期化處理。
  override public init() {
    super.init()
    construct(client: client())
  }

三、让 IMKInputController 的 .init(server:delegate:client:) 这个有参数的建构子也利用 construct()

这样区分的原因是:有参数的建构子会接收体系传入的变数;而无参数的建构子会主动读取 client()

      /// 對用以設定委任物件的控制器型別進行初期化處理。
      ///
      /// inputClient 參數是客體應用側存在的用以藉由 IMKServer 伺服器向輸入法傳訊的物件。該物件始終恪守 IMKTextInput 協定。
      /// - Remark: 所有由委任物件實裝的「被協定要求實裝的办法」都會有一個用來接受客體物件的參數。在 IMKInputController 內部的型別不需求接受這個參數,因為已經有「 client()」這個參數存在了。
      /// - Parameters:
      ///   - server: IMKServer
      ///   - delegate: 客體物件
      ///   - inputClient: 用以接受輸入的客體應用物件
      override public init!(server: IMKServer!, delegate: Any!, client inputClient: Any!) {
        super.init(server: server, delegate: delegate, client: inputClient)
        let theClient = inputClient as? (IMKTextInput & NSObjectProtocol)
        construct(client: theClient)
      }

思路大致如此。看似有简化的地步,只叹我有类型安全洁癖。


补充:为什么说 activateServer() 这玩意是建构子呢?因为他便是建构子。

RemObjects 的开发套装将 IMKInputController 的 API 解析成 C# 格式时,里面没有 activateServer() ,但有public static this withServer(InputMethodKit.IMKServer server) @delegate(id @delegate) client(id inputClient);这一行建构子。这就足以阐明全部。

至于那个没有参数的建构子哪里来的?是 NSObject 就都会有这东西。

        class InputMethodKit.IMKInputController : NSObject, InputMethodKit.IIMKStateSetting, InputMethodKit.IIMKMouseHandling
        {
            private id _private;
            [NonSwiftOnly]
            public id initWithServer(InputMethodKit.IMKServer server) @delegate(id @delegate) client(id inputClient);
            [InitFromClassFactoryMethod]
            [Alias]
            [SwiftOnly]
            public static this withServer(InputMethodKit.IMKServer server) @delegate(id @delegate) client(id inputClient);
            public void updateComposition();
            public void cancelComposition();
            [NonSwiftOnly]
            public NSMutableDictionary<id,id> compositionAttributesAtRange(NSRange range);
            [Alias]
            [SwiftOnly]
            public NSMutableDictionary<id,id> compositionAttributes(NSRange range);
            public NSRange selectionRange();
            public NSRange replacementRange();
            [NonSwiftOnly]
            public NSDictionary<id,id> markForStyle(NSInteger style) atRange(NSRange range);
            [Alias]
            [SwiftOnly]
            public NSDictionary<id,id> mark(NSInteger style) at(NSRange range);
            [NonSwiftOnly]
            public void doCommandBySelector(SEL aSelector) commandDictionary(NSDictionary<id,id> infoDictionary);
            [Alias]
            [SwiftOnly]
            public void doCommand(SEL aSelector) commandDictionary(NSDictionary<id,id> infoDictionary);
            public void hidePalettes();
            public NSMenu menu();
            public InputMethodKit.IMKServer server();
            public UNKNON_CONSTRAINED_TYPE<id,IIMKTextInput,INSObject> client();
            public void inputControllerWillClose();
            public void annotationSelected(NSAttributedString annotationString) forCandidate(NSAttributedString candidateString);
            public void candidateSelectionChanged(NSAttributedString candidateString);
            public void candidateSelected(NSAttributedString candidateString);
            public id @delegate { get; set; }
        }