本文主要讲解在实际应用中,为什么在提供通用的滤镜支持时,应该考虑显式声明CIFilter。
隐式类型推断
下面是一个隐式类型推断的sepiaTone(棕褐色调滤镜):
struct ContentView: View {
@State private var currentFilter = CIFilter.sepiaTone()
func applyProcessing() {
currentFilter.intensity = Float(filterIntensity)
...
}
...
}
隐式类型推断:
在这种情况下,Swift 会根据右侧的初始化值 CIFilter.sepiaTone() 自动推断 currentFilter 的类型。
因为 CIFilter.sepiaTone() 返回的是具体的 CISepiaTone 类型,currentFilter 的类型将被推断为 CISepiaTone。
访问具体滤镜的特性:
这种写法允许直接访问 CISepiaTone 的特定属性(如 .intensity),而不需要再进行类型转换。
currentFilter.intensity = Float(filterIntensity)
用途:
如果确定 currentFilter 始终是 CISepiaTone 类型,并需要直接操作其特定属性(如 intensity),这种写法更方便。
如果需要更通用的滤镜支持,可以显式声明 currentFilter 为 CIFilter 类型,但这样会失去对特定协议属性(如 intensity)的直接访问。
显式声明
@State private var currentFilter:CIFilter = CIFilter.sepiaTone()
显式声明类型为 CIFilter:
在这种情况下,currentFilter 的类型是明确设定为 CIFilter。
即使 CIFilter.sepiaTone() 实际上是一个 CISepiaTone 类型的实例,它会被 向上转型 为通用的 CIFilter 类型。
这是 Swift 的 多态特性,允许子类实例(CISepiaTone)被赋值为父类类型(CIFilter)。
类型约束明确:
后续代码中,currentFilter 只能使用 CIFilter 公共接口,无法直接访问 CISepiaTone 的专有属性(如 .intensity)。
currentFilter.intensity = Float(filterIntensity) // 不再可用
因此应该考虑使用setValue替代intensity属性的访问。
currentFilter.setValue(filterIntensity, forKey: kCIInputIntensityKey)
用途:
适合需要动态切换滤镜类型的场景,比如通过按钮切换 CIFilter.sepiaTone() 和 CIFilter.gaussianBlur() 等不同滤镜类型。
为什么不能动态改变类型?
可能有人会疑惑,既然可以进行隐式类型推断,那么给CIFilter赋值新的滤镜,CIFilter也应该能重新推断类型,为什么还有显式声明为CIFilter类型。
虽然 Swift 的类型推断很强大,但对于属性(如 @State)的类型,类型一旦确定就不能在运行时动态改变。这是因为 Swift 是一门强类型语言,所有属性的类型在编译时就已经固定。
当声明 @State private var currentFilter = CIFilter.sepiaTone() 时:
1、类型推断结果是 CISepiaTone。
这是因为 CIFilter.sepiaTone() 返回一个 CIFilter 实例,且遵循了 CISepiaTone 协议。
2、Swift 在编译时确定了 currentFilter 的类型为 CISepiaTone。
这意味着之后无论赋值为其他滤镜类型,如 CIFilter.gaussianBlur(),它都需要兼容 CISepiaTone 协议。
赋值新滤镜时的问题
如果尝试以下代码:
currentFilter = CIFilter.gaussianBlur()
此时会报错,原因是 CIFilter.gaussianBlur() 返回的类型是 CIGaussianBlur,它与 CISepiaTone 协议不兼容。即使两者都是 CIFilter 的子类,它们遵循的是不同的滤镜协议,Swift 不允许隐式类型更改。
报错内容为:
Value of type 'any CIFilter & CIGaussianBlur' does not conform to 'CISepiaTone' in assignment
如何解决?
如果需要在运行时切换不同类型的滤镜,应该将 currentFilter 显式声明为 CIFilter,例如:
@State private var currentFilter: CIFilter = CIFilter.sepiaTone()
在这种情况下:
1、currentFilter 的类型被固定为 CIFilter,是一个通用的滤镜类型。
2、在赋值新滤镜时:
currentFilter = CIFilter.gaussianBlur()
这是合法的,因为 CIFilter 是所有滤镜的基类。
为什么推荐显式声明为 CIFilter?
显式声明为 CIFilter 可以让的代码在切换滤镜时更加灵活:
可以动态更改滤镜类型。
不需要为每个滤镜分别声明对应的类型协议。
不过,缺点是会失去协议中方便的类型安全支持(如 CISepiaTone.intensity),必须通过 setValue(_:forKey:) 和 value(forKey:) 来设置和获取参数。