在Swift中,我有一个具有以下基本前提的自定义结构:
包装器结构,可以包含符合BinaryInteger
的任何类型,例如Int,UInt8,Int16等。
protocol SomeTypeProtocol {
associatedtype NumberType
var value: NumberType { get set }
}
struct SomeType<T: BinaryInteger>: SomeTypeProtocol {
typealias NumberType = T
var value: NumberType
}
以及Collection的扩展:
extension Collection where Element: SomeTypeProtocol {
var values: [Element.NumberType] {
return self.map { $0.value }
}
}
例如,这很好用:
let arr = [SomeType(value: 123), SomeType(value: 456)]
// this produces [123, 456] of type [Int] since literals are Int by default
arr.values
我想做的是完全相同的事情,但是对于
SomeType<T>?
let arr: [SomeType<Int>?] = [SomeType(value: 123), SomeType(value: 456)]
// this doesn't work, obviously
arr.values
// but what I want is this:
arr.values // to produce [Optional(123), Optional(456)]
我已经尝试了很多尝试来解决这个问题,并且进行了大量的研究,但是我希望Swift的任何资深人士都能对此有所启发。
这是我预想的样子,但这是行不通的:
extension Collection where Element == Optional<SomeType<T>> {
var values: [T?] {
return self.map { $0?.value }
}
}
这是不使用泛型即可达成目标的笨拙方法,并且有效:
extension Collection where Element == Optional<SomeType<Int>> {
var values: [Int?] {
return self.map { $0?.value }
}
}
let arr: [SomeType<Int>?] = [SomeType(value: 123), SomeType(value: 456)]
arr.values // [Optional(123), Optional(456)]
但是,它需要为符合BinaryInteger的每个已知类型手动编写扩展名,并且在不手动更新代码的情况下,不会自动包括采用BinaryInteger的将来类型。
// "..." would contain the var values code from above, copy-and-pasted
extension Collection where Element == Optional<SomeType<Int>> { ... }
extension Collection where Element == Optional<SomeType<Int8>> { ... }
extension Collection where Element == Optional<SomeType<UInt8>> { ... }
extension Collection where Element == Optional<SomeType<Int16>> { ... }
extension Collection where Element == Optional<SomeType<UInt16>> { ... }
extension Collection where Element == Optional<SomeType<Int32>> { ... }
extension Collection where Element == Optional<SomeType<UInt32>> { ... }
extension Collection where Element == Optional<SomeType<Int64>> { ... }
extension Collection where Element == Optional<SomeType<UInt64>> { ... }
编辑2018年6月23日:
解决方案#1-完全通用,但必须是函数,不能计算属性
在Ole's reply上扩展:
优点:如果
values()
变成func
而不是计算属性,那么这是一个很好的解决方案。缺点:没有一种将这种方法实现为计算属性的方法,Swift的快速帮助弹出窗口在检查代码中的
values()
时显示[T]和[T?]。即:它只是说func values<T>() -> [T] where T : BinaryInteger
,它不是非常有用的信息或Swifty。但是,它当然仍然是强类型的。extension Collection {
func values<T>() -> [T] where Element == SomeType<T> {
return map { $0.value }
}
func values<T>() -> [T?] where Element == SomeType<T>? {
return map { $0?.value }
}
}
解决方案#2- optional 协议(protocol)解决方法
在Martin's reply上扩展:
优点:允许使用计算属性(由于不需要func parens,因此最终用户可以使用更干净的属性),并在Xcode的快速帮助弹出窗口中显示推断的类型。
缺点:从内部代码的角度来看,这不是很优雅,因为它需要一种解决方法。但不一定是缺点。
// Define OptionalType
protocol OptionalType {
associatedtype Wrapped
var asOptional: Wrapped? { get }
}
extension Optional: OptionalType {
var asOptional: Wrapped? {
return self
}
}
// Extend Collection
extension Collection where Element: SomeTypeProtocol {
var values: [Element.NumberType] {
return self.map { $0.value }
}
}
extension Collection where Element: OptionalType, Element.Wrapped: SomeTypeProtocol {
var values: [Element.Wrapped.NumberType?] {
return self.map { $0.asOptional?.value }
}
}
最佳答案
我不知道现在是否有一个更简单的解决方案,但是您可以使用与How can I write a function that will unwrap a generic property in swift assuming it is an optional type?和Creating an extension to filter nils from an Array in Swift中相同的“技巧”,这个想法可以追溯到this Apple Forum Thread。
首先定义所有 optional 选项都遵循的协议(protocol):
protocol OptionalType {
associatedtype Wrapped
var asOptional: Wrapped? { get }
}
extension Optional : OptionalType {
var asOptional: Wrapped? {
return self
}
}
现在可以将所需的扩展名定义为
extension Collection where Element: OptionalType, Element.Wrapped: SomeTypeProtocol {
var values: [Element.Wrapped.NumberType?] {
return self.map( { $0.asOptional?.value })
}
}
并按预期工作:
let arr = [SomeType(value: 123), nil, SomeType(value: 456)]
let v = arr.values
print(v) // [Optional(123), Optional(456)]
print(type(of: v)) // Array<Optional<Int>>