V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ShikiSuen
V2EX  ›  Apple

很多副厂 mac 输入法忘记给 IMKInputController 实作那种没有参数的建构子。

  •  
  •   ShikiSuen · 346 天前 · 553 次点击
    这是一个创建于 346 天前的主题,其中的信息可能已经有所发展或是发生改变。

    如果切到输入法的时候、输入法正在运行、且有对当前 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; }
        }
    
    ShikiSuen
        1
    ShikiSuen  
    OP
       346 天前
    V2EX 的贴文无法更新,所以我在稀土掘金也放了一份。
    https://juejin.cn/post/7236196894988369957/
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2201 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 02:37 · PVG 10:37 · LAX 19:37 · JFK 22:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.