Skip to main content
 首页 » 编程设计

swift之检查一个类型是否实现了一个协议(protocol)

2024年01月01日52qlqwjy

我正在写 a library为默认的 Swift 类型创建扩展。

我想检查我的数组扩展是否某种类型实现了某种协议(protocol)。例如看这个方法:

extension Array { 
    /// Compares the items using the given comparer and only returns non-equal values 
    /// :returns: the first items that are unique according to the comparer 
    func distinct(comparer: (T, T) -> Bool) -> [T] { 
        var result: [T] = [] 
        outerLoop: for item in self { 
            for resultItem in result { 
                if comparer(item, resultItem) { 
                    continue outerLoop 
                } 
            } 
            result.append(item) 
        } 
        return result 
    } 
} 

现在我想重写这个方法来检查 T 是否是 Equatable 这样的:

/// Compares the items using the given comparer and only returns non-equal values 
/// :returns: the first items that are unique according to the comparer 
func distinct(comparer: ((T, T) -> Bool)?) -> [T] { 
    var result: [T] = [] 
    outerLoop: for item in self { 
        for resultItem in result { 
            if isEquatable ? comparer!(item, resultItem) : item == resultItem { 
                continue outerLoop 
            } 
        } 
        result.append(item) 
    } 
    return result 
} 

其中 isEquatable 是一个 Bool 值,它告诉我 T 是否是 Equatable。我怎样才能找到它?

请您参考如下方法:

目前在 Swift 中没有一个好的方法来做到这一点。*这就是为什么像 sorted 这样的函数要么是自由函数,要么在成员的情况下采用谓词.您正在寻找的测试和转换方法的主要问题是 Equatable 和类似的协议(protocol)具有关联类型或依赖于 Self,因此只能是在通用函数中用作约束。

我猜你的目标是调用者可以跳过提供比较函数,所以它会回退到 Equatable 如果可用?如果不是,会崩溃吗?这里的问题是函数在运行时确定某些东西(参数是 Equatable),而这实际上应该在编译时确定。这不是很好 - 在编译时完全确定这些事情要好得多。

因此您可以编写一个需要 Equatable 的自由函数:

func distinct<C: CollectionType where C.Generator.Element: Equatable> 
  (source: C) -> [C.Generator.Element] { 
 
    var seen: [C.Generator.Element] = [] 
    return filter(source) { 
        if contains(seen, $0) { 
            return false 
        } 
        else { 
            seen.append($0) 
            return true 
        } 
    }    
} 
 
let uniques = distinct([1,2,3,1,1,2])  // [1,2,3] 

然后,如果您尝试用不可比的东西调用它,您会得到一个编译时错误:

let incomparable = [1,2,3] as [Any] 
distinct(incomparable)  // compiler barfs - Any isn’t Equatable 

使用运行时方法,您只会在运行程序时发现这一点。

好消息是,也有好处。为每个元素搜索数组的问题是,对于大型数组,该函数会非常慢,因为对于每个元素,必须线性搜索已见元素列表。如果你用另一个版本重载 distinct 要求元素是 Hashable(Equatable 通常是这样的),你可以使用一个集合来跟踪它们:

func distinct<C: CollectionType where C.Generator.Element: Hashable> 
  (source: C) -> [C.Generator.Element] { 
 
    var seen: Set<C.Generator.Element> = [] 
    return filter(source) { 
        if seen.contains($0) { 
            return false 
        } 
        else { 
            seen.insert($0) 
            return true 
        } 
    } 
} 

在编译时,编译器会选择函数的最佳版本并使用它。如果你的东西是可散列的,那么会选择那个版本,如果它只是等同的,它将使用较慢的版本(这是因为 Hashable 继承自 Equatable,编译器会选择更专业的功能)。在编译时而不是运行时执行此操作意味着您不会为检查支付任何罚款,这一切都是预先确定的。

*有丑陋的方法,但既然目标是吸引人的语法,那有什么意义呢……也许下一个版本将允许对方法进行约束,这会很好。