追記: 本件は Xcode 8.1 のときのもので、Xcode 8.2 以降では Touch Bar 関連の availability は macOS 10.12.2 に変更になったためこの問題は解消されています。
CotEditor 開発でトラップを踏んだので知見の共有です。
NSTouchBar 関連の API は macOS 10.12.1 SDK 以降で使用でき、Swift では #available(macOS 10.12.1, *)
を噛ませれば呼び出しできます。ところが、macOS 10.12.1 は実のところ複数のビルドが出回っており、一般的に出回っている初期のビルドには NSTouchBar が入っていないので、呼び出すとアプリケーションが死にます。しかも呼び出した途端即クラッシュではなく、じわじわと「ウインドウが出ない」などの不具合が出ます。
つまり、以下はコンパイルは通りますが不十分です。
// setup touchbar
if #available(macOS 10.12.1, *) {
// NSTouchBar には触っていないが Touch Bar 関連の API なのでアウト
NSApp.isAutomaticCustomizeTouchBarMenuItemEnabled = true
}
このように、ダサい方法でさらに本当に NSTouchBar があるかチェックする必要があります。
// setup touchbar
if #available(macOS 10.12.1, *), NSClassFromString("NSTouchBar") != nil {
NSApp.isAutomaticCustomizeTouchBarMenuItemEnabled = true
}
ちなみに makeTouchBar()
なんかは、そもそも Touch Bar に対応していないと呼び出されないので、@available(macOS 10.12.1, *)
で十分です。ただし、そこで独自メソッドを用意して他のスコープから呼ぶときは注意しましょう。
@available(macOS 10.12.1, *)
extension DocumentViewController: NSTouchBarDelegate {
override func makeTouchBar() -> NSTouchBar? {
let touchBar = NSTouchBar()
touchBar.delegate = self
touchBar.customizationIdentifier = .touchBar
// and other touchBar settings...
return touchBar
}
func myOriginalMethodForTouchBar() {
guard let touchBar = self.touchBar else { return }
// nantara kantara
}
}
extension DocumentViewController {
func updateSomething() {
// これの呼び出しも NSClassFromString("NSTouchBar") != nil を噛ませるべき
if #available(macOS 10.12.1, *), NSClassFromString("NSTouchBar") != nil {
self.myOriginalMethodForTouchBar()
}
}
}
Touch Bar を開発するような人は、Touch Bar 対応ビルドを入れていると思うので開発環境で問題が顕在化しません。注意が必要です。
これがビルド違いだなんて。本来なら Apple が macOS のバージョンナンバーを 10.12.2 にあげるべき案件だと思います。私はもう激おこ👹です。
追記
現在出ている Xcode 8.2 beta 2 では、Touch Bar 関連の API は NS_AVAILABLE_MAC(10_12_2)
に変更になっているようです。そりゃそうだよな。ふがー 😡。
追記2
ちゃんと読めばランタイムチェックしろって Xcode 8.1 のリリースノートに書いてあります。
- In Swift code, do an availability check for macOS 10.12.1, and a runtime check for Touch Bar class availability. For example:
NSClassFromString("NSTouchBar") != nil