Swift Generic Issue扩展API

svgewumm  于 2023-05-16  发布在  Swift
关注(0)|答案(2)|浏览(94)

在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>

// Data the API gives me:
struct ActivitySelection {
    public var applicationTokens: Set<ApplicationToken>
    public var categoryTokens: Set<ActivityCategoryToken>
    public var webDomainTokens: Set<WebDomainToken>
}

我试着这样做:

// 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

然后当我尝试使用我的扩展时:

let tokens = selection.tokensFor(activity: .Applications)

我得到这个错误:

  • XCode Error: Generic parameter 'T' could not be inferred

如果我尝试这个:

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
    }

我得到:

  • 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>]'

我也试过这样做:

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
    }

但我得到错误:

  • 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>]'

我该怎么做?我认为泛型是这个问题的解决方案。
由于Token<T>已经是一个泛型类型,我如何使用它来通用地使用3种不同的令牌类型?

0x6upsns

0x6upsns1#

使用enum不起作用的原因是枚举值只能在运行时知道:

let tokens = tokensFor(activity: randomlyGenerateAnActivity())

如果randomlyGenerateAnActivity随机选择三种情况之一,那么tokens的编译时类型是什么?编译器无法知道。
一个解决方案是创建这样一个类型,一个 Package Token<T>的非泛型类型,用类似的东西替换Token<T>中涉及T的所有内容,除了T被替换为Any。由于您不想显示Token<T>有哪些成员,因此我不能给予任何代码示例。
另一种解决方案是让tokensFor接受一个 metatype 作为参数。

// 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
    }
}

现在tokensFor将返回的类型在编译时总是已知的。
请注意,除了使用if...else if来检查类型,您还可以在协议中添加一个静态属性,以返回相应的密钥路径:

// implement this property in each of the three types
static var activitySelectionKeyPath: KeyPath<ActivitySelection, Set<Token<Self>>> { get }

然后你就可以从self[keyPath: T.activitySelectionKeyPath]得到tokensFor中的数组。
因为你在SwiftUI视图中显示token,你应该创建一个泛型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

// in a parent view...
public var body: some View {
    SomeGenericView<Application>()
    SomeGenericView<ActivityCategory>()
    SomeGenericView<WebDomain>()
}
rbl8hiat

rbl8hiat2#

这个签名并不意味着你认为它的意思:

func tokensFor<T>(activity:ActivityType) -> [Token<T>] {

这意味着调用者可以选择任何可能存在的类型(String,Int,他们刚刚定义的随机结构,UIViewController,任何东西),并且这个函数承诺返回一个 Package 该类型的Token。这一切都将在编译时静态地决定。托肯不是这样的你也不是这个意思
相反,目标是返回一个只有在运行时才知道的Token类型。这是协议的标准用法。

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
    }
}

相关问题