Swift 中集合与字典的角逐
传统的 在这篇文章中,我将讨论另一种更加 Swift 化的方法。这并不是一个能够彻底解决问题的方法,但我认为它是一个在后 Swift 时代中能够更好展示 API 是如何工作的观念模式。 基于字典的设置工作下面的代码是从我自己的一个项目中抽取出来的(如果你熟悉我其他的文章或许会有印象,这是我写的 Movie Maker 类)。这几行 Objective-C 代码创建了一个名为 options 的字典,用来构建一个视频流像素缓冲区: Objective-C NSDictionary *options = @{ (id) kCVPixelBufferCGImageCompatibilityKey : @YES,(id) kCVPixelBufferCGBitmapContextCompatibilityKey : @YES,}; 这个例子中键(key)默认为 let myOptions: [NSString: NSObject] = [ kCVPixelBufferCGImageCompatibilityKey: true,kCVPixelBufferCGBitmapContextCompatibilityKey: true ] 即使在 Swift 中,像这样将值传递给 API 也不是一种很理想的方式。 配置字典的特征下面的例子展示了配置字典(Setting dictionary)的共同特征,这都是值得仔细研究的。
在 Swift 中,上述列出的特性在集合和枚举中显得比字典更加典型。理由如下:
基于这些原因,我觉得在 Swift 中设置集合时使用枚举会比一个 键的转换停下来思考一下我们现在碰到的这个状态。AVFundation 定义了接下来所表示的一系列的键(顺便,这不是一个完整的包含所有像素缓冲键值的集合)。 const CFStringRef kCVPixelBufferPixelFormatTypeKey; const CFStringRef kCVPixelBufferExtendedPixelsTopKey; const CFStringRef kCVPixelBufferExtendedPixelsRightKey; const CFStringRef kCVPixelBufferCGBitmapContextCompatibilityKey; const CFStringRef kCVPixelBufferCGImageCompatibilityKey; const CFStringRef 上面都是一些常量字符串,这些字符串都被用于作为字典的索引。调用者通过使用这些键来创建字典,通过传递任意对象作为键的值。 在 Swift 中,你可以将这个基于键值对存放的数据类型改造成一个简单的枚举:对于不同键所表示的情况,指定特定的值类型。下面例子就是用来表示上面的五种情况。所关联的类型来自现有的像素缓冲键值属性文档。 enum CVPixelBufferOptions { case CGImageCompatibility(Bool) case CGBitmapContextCompatibility(Bool) case ExtendedPixelsRight(Int) case ExtendedPixelsBottom(Int) case PixelFormatTypes([PixelFormatType]) // ... etc ... } 当这些选项像这样设计时,我们就得到了一个可扩展性很强而又对每种可能情况严格规定值类型的枚举类型。为一个可扩展的枚举在每个可能的情况下,严格规定值的类型。和弱字典类型相比,这种方法能够保证类型安全。 此外,在个别枚举案例也会更清晰,更简洁,使用作为数据交互也比名字很长很详细的 Cocoa 形式的常量更好。例如 创建配置集通过重新设计,你可以建立一个看上去应该就像下面例子一样的集合。说「应该」是因为这段代码不能通过编译。 // This does not compile yet let bufferOptions: Set<CVPixelBufferOptions> = [.CGImageCompatibility(true),.CGBitmapContextCompatibility(true)]
// This compiles let bufferOptions: [CVPixelBufferOptions] = [.CGImageCompatibility(true),.CGBitmapContextCompatibility(true)] 数组使用起来是十分友好的,但它无法保证配置元素的单一独立特性。字典能够提供这一点,与此同时也正在推动重新设计数组。 区分值
当创建配置集合时,你希望创建并不像下面例子这样的集合,因为在这个例子中同时存在多个带有冲突的配置成员: [.CGImageCompatibility(true),.CGImageCompatibility(false)] // which one?! 由于有不同的关联值,这显然是两个不同的枚举实例。在这个例子中,你会想让集合丢弃除了第一个被添加到集合的元素的内容,即仅仅留下 通过实现哈希,使你能够比较枚举类型。 实现哈希值对于这个特定的用例,你需要创建一个哈希函数,该函数只考虑唯一的情况,而不是考虑关联值。目前在 Swift 中没有提供此功能的构造函数,所以你需要自己创建这个构造函数。 Swift 中 public var hashValue: Int { get } // hashable public func ==(lhs: Self,rhs: Self) -> Bool // equatable 基本的枚举,例如 下面是 extension CVPixelBufferOptions: Hashable,Equatable { public var hashValue: Int { switch self { case .CGImageCompatibility: return 1 case .CGBitmapContextCompatibility: return 2 case .ExtendedPixelsRight: return 3 case .ExtendedPixelsBottom: return 4 case .PixelFormatTypes: return 5 } } } public func ==(lhs: CVPixelBufferOptions,rhs: CVPixelBufferOptions) -> Bool { return lhs.hashValue == rhs.hashValue } 从最直观的一面可以看出这些哈希值绝对没有任何意义而且也不会暴露给 API 的使用者,所以如果你需要添加额外的值,你也可以这样做。这种做法是有些丑陋以及不那么的 Swift 化,但是很有技巧性。 一旦你添加这些功能,一切都将开始工作。你可以创建配置集合,来保证每一个配置只出现一次以及保证它们的值关联的是正确的类型。 最终的思考在这篇文章中所描述的稍些笨重的哈希方法在使用上远胜于 Cocoa 提供的 Swift 真正所需要的,我想是配置集合与关联值能更好的关联在一起。至少,在所有的枚举项中添加原始值的支持(不只是基本的那些缺乏相关的或内在价值)将是向前迈进一大步。 感谢 Erik Little。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |