在Java之后我开始学习swift.在Java中,我可以使用任何对象作为HashSet的键,因为它具有默认值hashCode并equals基于对象标识符.如何在Swift中实现相同的行为?
Aar*_*sen 26
如果您正在使用类而不是结构,则可以使用ObjectIdentifier结构.请注意,您还必须==为您的类定义以符合Equatable(Hashable要求它).它看起来像这样:
class MyClass: Hashable { }
func ==(lhs: MyClass, rhs: MyClass) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
class MyClass: Hashable {
var hashValue: Int {
return ObjectIdentifier(self).hashValue
}
}
Run Code Online (Sandbox Code Playgroud)
Mar*_*eIV 12
与其使用Hashable协议扩展您的类,然后复制每个类的实现,不如扩展Hashable协议本身,将其限制为AnyObject,然后将共享实现放在那里。这样,通过简单地使您的类符合Hashable,它们将自动选择该共享实现。
如此处接受的答案所示,一些实现将在其类类型上使用扩展来实现Hashable. 这种方法的问题在于,您必须为违反 DRY 原则(即不要重复自己)定义的每种类型复制实现。
这是这种方法的一个例子......
extension SomeClass : Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs === rhs
}
}
extension SomeOtherClass : Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs === rhs
}
}
extension YetAnotherClass : Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs === rhs
}
}
Run Code Online (Sandbox Code Playgroud)
这是很多重复的代码!
我的建议是颠倒事物。不是扩展单个类类型,而是扩展Hashable协议本身,然后将其约束到AnyClass并将实现放在那里。这样做会自动将该实现应用于所有只指定符合协议的类,不需要特定于类的实现。
这是这种方法的外观......
extension Hashable where Self: AnyObject {
func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self))
}
}
extension Equatable where Self: AnyObject {
static func == (lhs:Self, rhs:Self) -> Bool {
return lhs === rhs
}
}
Run Code Online (Sandbox Code Playgroud)
注意:虽然我可以将相等函数直接添加到
Hashable扩展中,但通过将其应用于 Equatable(Hashable 隐式符合),您可以使用相同的技术将实例相等应用于类类型。
有了上述两个就位,我们现在可以做到这一点......
extension SomeClass : Hashable {}
extension SomeOtherClass : Hashable {}
extension YetAnotherClass : Hashable {}
Run Code Online (Sandbox Code Playgroud)
没有重复的代码。只是符合协议。
当然Hashable也Equatable隐含地给你,所以这些现在也都有效......
let a = SomeClass()
let b = a
let msg = (a == b)
? "They match! :)"
: "They don't match. :("
print(msg)
// Prints They match! :)
Run Code Online (Sandbox Code Playgroud)
注意:这不会干扰
Hashable直接实现的类,因为特定于类的定义更加明确,因此它具有优先权,它们可以和平共处。
更进一步,Equatable如果你想隐式地(即不需要手动遵守协议)使所有对象类型实现Equatable使用身份来测试相等性——我个人想知道为什么默认情况下它不这样做(如果需要,您仍然可以覆盖每个类型)--您可以将泛型与全局定义的相等运算符一起使用,将约束设置为AnyObject
这是代码...
func == <T:AnyObject>(lhs: T, rhs: T) -> Bool {
return lhs === rhs
}
func != <T:AnyObject>(lhs: T, rhs: T) -> Bool {
return !(lhs == rhs)
}
Run Code Online (Sandbox Code Playgroud)
注意:如果你走这条路线,为了完整起见,你还应该定义
!=运营商。您需要明确地执行此操作,这与==在扩展中定义运算符不同,编译器不会自动为您合成它。
有了上述内容,您现在可以执行此操作...
class Foo {} // Note no protocols or anything else specified. Equality 'just works'
let a = Foo()
let b = a
var msg = (a == b)
? "They match! :)"
: "They don't match. :("
print(msg)
// Prints They match! :)
let c = Foo()
var msg = (a == c)
? "They don't match! :)"
: "They match. :("
print(msg)
// Prints They don't match! :)
Run Code Online (Sandbox Code Playgroud)
如上所述,您仍然可以使用特定于类型的相等版本。这是因为它们优先于AnyObject版本,因为它们更具体,因此可以与上面提供的默认引用平等和平共处。
这是一个假设上述内容已到位的示例,但仍然为 定义了一个显式的相等版本Laa,仅基于id。
class Laa {
init(_ id:String){
self.id = id
}
let id:String
}
// Override implicit object equality and base it on ID instead of reference
extension Laa : Equatable {
static func == (lhs:Laa, rhs:Laa) -> Bool {
return lhs.id == rhs.id
}
}
Run Code Online (Sandbox Code Playgroud)
注意:如果您覆盖相等的对象也实现了
Hashable,则必须确保相应的哈希值也按照定义相等,相等的对象应该产生相同的哈希值。
也就是说,如果您将Hashable扩展限制在AnyObject这篇文章的顶部,并简单地标记您的类,Hashable您将获得基于对象标识的默认实现,因此它不会匹配共享相同 ID 的不同类实例(因此被认为是相等的),因此您必须明确确保也实现哈希函数。编译器不会为您捕获此信息。
同样,只有当您覆盖相等并且您的类实现Hashable. 如果是这样,这里是如何做到这一点...
实现可散列Hee以遵循相等/可散列关系:
extension Hee : Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(id) // Easiest to simply use ID here since that's what Equatable above is based on
}
}
Run Code Online (Sandbox Code Playgroud)
最后,这是你如何使用它......
let hee1 = Hee("A")
let hee2 = Hee("A")
let msg2 = (hee1 == hee2)
? "They match! :)"
: "They don't match. :("
print(msg2)
// Prints 'They match! :)'
let set = Set<Hee>()
set.append(hee1)
set.append(hee2)
print("Set Count: \(set.count)")
// Prints 'Set Count: 1'
Run Code Online (Sandbox Code Playgroud)
在Swift中,类型必须符合Hashable且Equatable要在诸如a Dictionary或a 的数据结构中使用Set。但是,可以使用对象的“对象标识符”添加“自动一致性”。在下面的代码中,我实现了一个可重用的类来自动执行此操作。
注意,Swift 4.2更改了Hashable实现方式,因此您不再覆盖hashValue。相反,您可以覆盖hash(into:)。
open class HashableClass {
public init() {}
}
// MARK: - <Hashable>
extension HashableClass: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(ObjectIdentifier(self).hashValue)
}
// `hashValue` is deprecated starting Swift 4.2, but if you use
// earlier versions, then just override `hashValue`.
//
// public var hashValue: Int {
// return ObjectIdentifier(self).hashValue
// }
}
// MARK: - <Equatable>
extension HashableClass: Equatable {
public static func ==(lhs: HashableClass, rhs: HashableClass) -> Bool {
return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}
}
Run Code Online (Sandbox Code Playgroud)
要使用,只需带上您的类和子类HashableClass,然后一切都应该正常工作!
class MyClass: HashableClass {
}
Run Code Online (Sandbox Code Playgroud)
Swift 5 准系统
对于使类可散列的准系统实现,我们只需遵守Equatable和Hashable协议。如果我们很了解我们的对象,我们就可以确定使用哪个或哪些属性来使其相等和可散列。
class CustomClass: Equatable, Hashable {
let userId: String
let name: String
let count: Int
init(userId: String, name: String, count: Int) {
self.userId = userId
self.name = name
self.count = count
}
/* Equatable protocol simply means establishing a predicate to
determine if two instances of the same type are equal or unequal based
on what we consider equal and unequal. In this class, userId makes
the most sense so if we were to compare two instances (left-hand-side
versus right-hand-side), we would compare their userId values. When
it comes time to compare these objects with each other, the machine
will look for this function and use it to make that determination. */
static func == (lhs: CustomClass, rhs: CustomClass) -> Bool {
return lhs.userId == rhs.userId
}
/* Hashable protocol is similar to Equatable in that it requires us to
establish a predicate to determine if two instances of the same
type are equal or unequal, again based on what we consider equal and
unequal. But in this protocol we must feed that property into a
function which will produce the object's hash value. And again, userId
makes the most sense because each instance carries a unique value. If
userId was not unique, we could combine multiple properties to
generate a unique hash. */
func hash(into hasher: inout Hasher) {
hasher.combine(userId)
//hasher.combine(name) if userId was not unique, we could have added this
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4834 次 |
| 最近记录: |