英文:
Swift Generic Issue extending API
问题
在Swift中,您需要能够以通用方式处理由API提供的不同的Token<T>
。请注意,我了解您在使用Swift 5.8。
API定义如下(仅显示相关部分):
public struct Token<T> : Codable, Equatable, Hashable { }
public struct Application : Equatable, Hashable {
public let token: ApplicationToken?
}
public struct ActivityCategory : Equatable, Hashable {
public let token: ActivityCategoryToken?
}
public struct WebDomain : Equatable, Hashable {
public let token: WebDomainToken?
}
public typealias ApplicationToken = Token<Application>
public typealias ActivityCategoryToken = Token<ActivityCategory>
public typealias WebDomainToken = Token<WebDomain>
// API提供的数据:
struct ActivitySelection {
public var applicationTokens: Set<ApplicationToken>
public var categoryTokens: Set<ActivityCategoryToken>
public var webDomainTokens: Set<WebDomainToken>
}
您尝试的扩展如下:
// 我的自定义ActivityType:
enum ActivityType {
case Applications
case Categories
case WebDomains
}
// 我的API扩展
extension ActivitySelection {
func tokensFor<T>(activity: ActivityType) -> [Token<T>] {
let array: [Token<T>]
switch activity {
case .Applications:
array = Array(self.applicationTokens) as! [Token<T>]
case .Categories:
array = Array(self.categoryTokens) as! [Token<T>]
case .WebDomains:
array = Array(self.webDomainTokens) as! [Token<T>]
}
// 在返回之前进行排序,这里为简单起见省略了排序
return array
}
}
let selection = ActivitySelection() // 返回带有数据的实例
然后,当您尝试使用扩展时:
let tokens = selection.tokensFor(activity: .Applications)
您遇到了以下错误:XCode Error: Generic parameter 'T' could not be inferred
为解决此问题,您可以尝试以下更改:
extension ActivitySelection {
func tokensFor(activity: ActivityType) -> [Any] {
let array: [Any]
switch activity {
case .Applications:
array = Array(self.applicationTokens)
case .Categories:
array = Array(self.categoryTokens)
case .WebDomains:
array = Array(self.webDomainTokens)
}
// 在返回之前进行排序,这里为简单起见省略了排序
return array
}
}
这将返回一个包含Token<T>
的通用数组,但请注意,这会丢失类型信息。这是因为Swift的类型推断可能无法推断出通用类型参数T
,因此我们使用了[Any]
来容纳不同类型的Token
。您需要在使用这个数组时小心处理类型。
这是一种通过泛型来处理不同类型的方法,但需要在使用时小心处理类型不一致的情况。如果您有其他更复杂的需求,可能需要考虑使用协议或更高级的类型擦除技巧来处理。
英文:
In Swift I need to be able to generically work with the various Token<T>
given to me by an API.
Note that I'm Using Swift 5.8.
The API defines the following (only showing relevant parts):
public struct Token<T> : Codable, Equatable, Hashable { }
public struct Application : Equatable, Hashable {
public let token: ApplicationToken?
}
public struct ActivityCategory : Equatable, Hashable {
public let token: ActivityCategoryToken?
}
public struct WebDomain : Equatable, Hashable {
public let token: WebDomainToken?
}
public typealias ApplicationToken = Token<Application>
public typealias ActivityCategoryToken = Token<ActivityCategory>
public typealias WebDomainToken = Token<WebDomain>
// Data the API gives me:
struct ActivitySelection {
public var applicationTokens: Set<ApplicationToken>
public var categoryTokens: Set<ActivityCategoryToken>
public var webDomainTokens: Set<WebDomainToken>
}
I tried doing this:
// My custom ActivityType:
enum ActivityType {
case Applications
case Categories
case WebDomains
}
// My API Extension
extension ActivitySelection {
func tokensFor<T>(activity:ActivityType) -> [Token<T>] {
let array: [Token<T>]
switch activity {
case .Applications:
array = Array(self.applicationTokens) as! [Token<T>]
case .Categories:
array = Array(self.categoryTokens) as! [Token<T>]
case .WebDomains:
array = Array(self.webDomainTokens) as! [Token<T>]
}
// sort before returning here removed for simplicity
return array
}
}
let selection = ActivitySelection() // returns with data
Then when I try to use my extension:
let tokens = selection.tokensFor(activity: .Applications)
I get this error:
XCode Error: Generic parameter 'T' could not be inferred
If I try this:
func tokensFor<T>(activity:Activities) -> [Token<T>] {
let array: [Token<T>]
switch activity {
case .Applications:
array = Array(self.applicationTokens)
case .Categories:
array = Array(self.categoryTokens)
case .WebDomains:
array = Array(self.webDomainTokens)
}
// sort before returning here
return array
}
I get:
Cannot assign value of type '[ApplicationToken]' (aka 'Array<Token<Application>>') to type '[Token<T>]'
Cannot assign value of type '[ActivityCategoryToken]' (aka 'Array<Token<ActivityCategory>>') to type '[Token<T>]'
Cannot assign value of type '[WebDomainToken]' (aka 'Array<Token<WebDomain>>') to type '[Token<T>]'
I've also tried it this way:
func tokensFor(activity:Activities) -> [Token<Any>] {
let array: [Token<Any>]
switch activity {
case .Applications:
array = Array(self.applicationTokens)
case .Categories:
array = Array(self.categoryTokens)
case .WebDomains:
array = Array(self.webDomainTokens)
}
// sort before returning here
return array
}
But I get errors:
Cannot assign value of type '[ApplicationToken]' (aka 'Array<Token<Application>>') to type '[Token<Any>]'
Cannot assign value of type '[ActivityCategoryToken]' (aka 'Array<Token<ActivityCategory>>') to type '[Token<Any>]'
Cannot assign value of type '[WebDomainToken]' (aka 'Array<Token<WebDomain>>') to type '[Token<Any>]'
How can I make this work? I thought generics was the solution to this very problem.
Since Token<T>
is already a generic type, how do I use that to work with the 3 different token types generically?
答案1
得分: 3
以下是您要翻译的内容:
使用enum
不起作用的原因是枚举值只能在运行时知道:
let tokens = tokensFor(activity: randomlyGenerateAnActivity())
如果randomlyGenerateAnActivity
随机选择了三种情况之一,tokens
的编译时类型将是什么?编译器无法知道。
一种解决方法是创建这样一种类型,即一个非泛型类型,包装一个Token<T>
,用与Token<T>
中涉及T
的所有内容相似的东西替换,只不过将T
替换为Any
。由于您不想显示Token<T>
具有哪些成员,我无法为此提供任何代码示例。
另一种解决方法是使tokensFor
接受元类型作为参数。
// 使Application、ActivityCategory和WebDomain符合此协议
public protocol TokenType {
var token: Token<Self>? { get }
}
extension ActivitySelection {
func tokensFor<T: TokenType>(_ tokenType: T.Type) -> [Token<T>] {
let array: [Token<T>]
if tokenType == Application.self {
array = Array(self.applicationTokens) as! [Token<T>]
} else if tokenType == ActivityCategory.self {
array = Array(self.categoryTokens) as! [Token<T>]
} else if tokenType == WebDomain.self {
array = Array(self.webDomainTokens) as! [Token<T>]
} else {
fatalError("Invalid Token Type")
}
// 返回之前删除了排序以简化
return array
}
}
现在,tokensFor
将始终返回在编译时已知的类型。
请注意,您可以在协议中添加一个静态属性,而不是使用if...else if
来检查类型,该属性返回相应的键路径:
// 在三种类型中的每一个实现这个属性
static var activitySelectionKeyPath: KeyPath<ActivitySelection, Set<Token<Self>>> { get }
然后,在tokensFor
中,您可以从self[keyPath: T.activitySelectionKeyPath]
获取数组。
由于您在SwiftUI视图中显示令牌,您应该创建一个泛型View
,并将该泛型参数传递给tokensFor
:
public struct SomeGenericView<T: TokenType>: View {
let activitySelection = ActivitySelection()
public var body: some View {
ForEach(activitySelection.tokensFor(T.self), id: \.something) { token in
Text("\(token)")
}
}
}
然后,您可以创建三个这种SomeGenericView
:
// 在父视图中...
public var body: some View {
SomeGenericView<Application>()
SomeGenericView<ActivityCategory>()
SomeGenericView<WebDomain>()
}
希望这对您有所帮助。
英文:
The reason why using an enum
doesn't work is that enum values could only be known at runtime:
let tokens = tokensFor(activity: randomlyGenerateAnActivity())
If randomlyGenerateAnActivity
randomly chooses one of the three cases, what would be the compile-time type of tokens
? The compiler cannot know.
One solution would be to make such a type, a non-generic type that wraps a Token<T>
, replacing everything in Token<T>
that involves T
with something similar, except T
is replaced with Any
. Since you don't want to show what members Token<T>
has, I can't give any code examples for this.
Another solution would be to make tokensFor
take a metatype as a parameter.
// make Application, ActivityCategory, and WebDomain conform to this
public protocol TokenType {
var token: Token<Self>? { get }
}
extension ActivitySelection {
func tokensFor<T: TokenType>(_ tokenType: T.Type) -> [Token<T>] {
let array: [Token<T>]
if tokenType == Application.self {
array = Array(self.applicationTokens) as! [Token<T>]
} else if tokenType == ActivityCategory.self {
array = Array(self.categoryTokens) as! [Token<T>]
} else if tokenType == WebDomain.self {
array = Array(self.webDomainTokens) as! [Token<T>]
} else {
fatalError("Invalid Token Type")
}
// sort before returning here removed for simplicity
return array
}
}
Now the type that tokensFor
will return is always known at compile time.
Note that instead of using if...else if
to check the type, you could also add a static property in the protocol that returns the corresponding key path:
// implement this property in each of the three types
static var activitySelectionKeyPath: KeyPath<ActivitySelection, Set<Token<Self>>> { get }
Then you can just get the array in tokensFor
from self[keyPath: T.activitySelectionKeyPath]
.
<hr>
Since you are displaying the tokens in a SwiftUI view, you should make a generic View
, and pass that generic parameter into tokensFor
:
public struct SomeGenericView<T: TokenType>: View {
let activitySelection = ActivitySelection()
public var body: some View {
ForEach(activitySelection.tokensFor(T.self), id: \.something) { token in
Text("\(token)")
}
}
}
Then you can make three of this SomeGenericView
:
// in a parent view...
public var body: some View {
SomeGenericView<Application>()
SomeGenericView<ActivityCategory>()
SomeGenericView<WebDomain>()
}
答案2
得分: 1
这个签名不表示你认为的意思:
func tokensFor<T>(activity: ActivityType) -> [Token<T>] {
这意味着调用者可以选择任何可能存在的类型(String、Int、他们刚刚定义的随机结构、UIViewController,或任何类型),而该函数承诺返回包装该类型的 Token。而这一切将在编译时静态确定。这不是 Token 的工作方式,也不是你的意思。
相反,目标是在运行时返回仅在运行时已知的 Token 类型。这是协议的标准用法。
protocol TokenType {
// 这里通常放置调用者需要在返回的令牌上调用的任何方法。
}
extension Token: TokenType {
// 这可能是空的,但如果 Token 需要一些新方法以符合 TokenType,请在这里添加它们。
}
extension ActivitySelection {
// 现在返回一个“在运行时已知的任何类型的 Token”数组
func tokensFor(activity: ActivityType) -> [any TokenType] {
let array: [any TokenType]
switch activity {
case .Applications:
array = Array(self.applicationTokens)
case .Categories:
array = Array(self.categoryTokens)
case .WebDomains:
array = Array(self.webDomainTokens)
}
// 返回之前进行排序的部分已被简化
return array
}
}
英文:
This signature does not mean what you think it means:
func tokensFor<T>(activity:ActivityType) -> [Token<T>] {
This says that the caller can pick any type that can possibly exist (a String, Int, random struct they just defined, UIViewController, anything), and this function promises to return a Token wrapping that type. And this will all be decided statically, at compile-time. That's not how Token works, and it's not what you mean.
Instead, the goal is to return a Token type that is only known at runtime. That is the standard use for a protocol.
protocol TokenType {
// Here you would generally put any methods the caller needs to be
// able to call on the returned token.
}
extension Token: TokenType {
// This can probably be empty, but if Token needs some new methods to
// conform to TokenType, add them here.
}
extension ActivitySelection {
// Now return an Array of "any kind of Token, known at runtime"
func tokensFor(activity: ActivityType) -> [any TokenType] {
let array: [any TokenType]
switch activity {
case .Applications:
array = Array(self.applicationTokens)
case .Categories:
array = Array(self.categoryTokens)
case .WebDomains:
array = Array(self.webDomainTokens)
}
// sort before returning here removed for simplicity
return array
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论