時々、とても奇妙なクラスに出会うことがある。
CAMetalLayerはまさにそれで、実機ではiOS8+、シミュレータではiOS13+が必要になる。
これはシミュレータでMetalが使えるようになったのがiOS13~だったからという歴史的経緯によるものだ。
ちなみに内部的には実機とシミュレータでSDKが別れているため、それぞれのSDKごとにavailableが定義されて実現している。
実機SDK
API_AVAILABLE(macos(10.11), ios(8.0), watchos(2.0), tvos(9.0))
@interface CAMetalLayer : CALayer
シミュレータSDK
@available(iOS 13.0, *)
open class CAMetalLayer : CALayer {
さて、問題はアプリでCAMetalLayer
を利用するときで、iOS12未満もサポートしているアプリだと次のような挙動になる。
実機 | シミュレータ | |
---|---|---|
iOS12 | ○ | コンパイルエラー |
iOS13 | ○ | ○ |
この挙動をどう見るか難しいところだが、シミュレータ相手であればさっとコンパイルだけ通したいこともあると思う。
そんなときは
public protocol CAMetalLayerInterface: class {
var pixelFormat: MTLPixelFormat { get set }
var framebufferOnly: Bool { get set }
var presentsWithTransaction: Bool { get set }
func nextDrawable() -> CAMetalDrawable?
}
#if targetEnvironment(simulator)
@available(iOS 13, *)
extension CAMetalLayer: CAMetalLayerInterface {}
#else
@available(iOS 12, *)
extension CAMetalLayer: CAMetalLayerInterface {}
#endif
open class AnimationView: UIView {
typealias LayerClass = CAMetalLayerInterface & CALayer
override open class var layerClass: Swift.AnyClass {
#if targetEnvironment(simulator)
if #available(iOS 13, *) {
return CAMetalLayer.self
} else {
preconditionFailure("Not support simurator older than iOS12.")
}
#else
return CAMetalLayer.self
#endif
}
private var gpuLayer: LayerClass { self.layer as! LayerClass }
}
こんな感じで型を消しつつ、iOS12 & Simulator以外のときに型を入れてあげるのが良い気がする。
#if canImport(QuartzCore.CAMetalLayer)
とか
#if targetEnvironment(simulator) && available(iOS 13, *)
とか出来ればいいのになーって思ってしまった。レアケースだけど…