Ответ 1
Используйте оператор === из документации Swift:
Swift также предоставляет два идентификационных оператора (=== и! ==), которые вы используете для проверки того, ссылаются ли ссылки на два объекта на один и тот же экземпляр объекта.
Попытка сравнить два объекта типа AnyObject с использованием оператора '==', определенного в Equatable protocol, приводит к ошибке компиляции в Swift. Кто-нибудь нашел способ сравнить такие объекты, не зная реального типа объектов, которые могут быть использованы для downcasting?
Фон для этого вопроса состоит в том, что у меня есть словарь Dictionary < String, AnyObject > где значения должны быть предоставлены с помощью индекса, тогда в какой-то момент мне нужно сравнить значения в словаре, чтобы убедиться, что они уникальны.
ИЗМЕНИТЬ Вот фрагмент, демонстрирующий проблему.
@objc(FooObject)
public class FooObject: NSManagedObject {
@NSManaged public var properties: Dictionary<String, AnyObject>
public subscript(property: String) -> AnyObject? {
get {
return properties[property]
}
set(newValue) {
for propertyValue in properties.values {
if propertyValue == newValue { // This line is not compiling: Cannot invoke '==' with AnyObject
println("Values in are expected to be unique!")
// Throw an exception here ...
}
}
properties[property] = newValue
}
}
}
Примечание, что общий тип, как < T: Equatable > объявленный в определении класса и используемый как тип значения словаря, не решит проблему, поскольку он не может использоваться в сочетании с подклассом NSManagedObject.
Используйте оператор === из документации Swift:
Swift также предоставляет два идентификационных оператора (=== и! ==), которые вы используете для проверки того, ссылаются ли ссылки на два объекта на один и тот же экземпляр объекта.
Я не думаю, что это возможно. Проблема в том, что Equatable определяется с помощью метода, который принимает
func ==(a: T, b: T)
и нет способа, во время компиляции, компилятор может найти правильную функцию (поскольку она не знает типы раньше времени).
Единственный способ, которым вы могли бы это сделать, - это иметь представление о типах, которые вы сравнивали. Затем вы можете конкретно назвать соответствующую функцию равенства для каждого типа:
func compare(a: AnyObject, b: AnyObject) {
if let va = a as? Int, vb = b as? Int {if va != vb {return false}}
else if let va = a as? String, vb = b as? String {if va != vb {return false}}
else if let va = a as? Bool, vb = b as? Bool {if va != vb {return false}}
...
else {
// not a type we expected
return false;
}
}
Интересно, что С#, например, оборачивается этим, определяя интерфейс IComparable
как имеющий метод:
int CompareTo(Object obj)
Это позволяет каждому объекту IComparable
сравнивать себя с любым другим объектом (но функция всегда должна выполнять свою собственную проверку типов здесь, чтобы убедиться, что obj
имеет правильный тип).
Swift 3 не работает с ===
для сравнения ссылок, если объекты имеют тип Any
. Причина Any
может содержать целое число и float или любую другую вещь, которая на самом деле не является объектом. Поэтому вам придется отдать его на NSObject
, чтобы сравнить 2 элемента. Как
if (obj1 as! NSObject) === (obj2 as! NSObject)
{
//logic goes here
}
Посмотрите на реализацию:
/// Returns true if these arrays contain the same elements.
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool
это означает, что компилятор должен знать, что параметры левой стороны и правой стороны должны быть одного типа. Таким образом, выполняемая вами функция должна выглядеть так:
func compareFunc <T: Equatable>(par1: T, par2: T) {
...
if par1 == par2 {
...
}
...
}
Edit:
С вашими словарями должно быть что-то вроде этого:
func compareFunc <T: Equatable>(dic1: [String : T], dic2: [String : T]) {
...
if dic1[yourKey] == dic2[yourKey] {
...
}
...
}
Изменить 2:
Пример:
func compareFunc <T: Equatable>(dic1: [String : T], dic2 : [String : T]) -> Bool {
return dic1["key"] == dic2["key"]
}
compareFunc(["key": "value"], ["key": "value"])
Это должно хорошо работать для большинства случаев:
func equalAny<BaseType: Equatable>(lhs: Any, rhs: Any, baseType: BaseType.Type) -> Bool {
guard
let lhsEquatable = lhs as? BaseType,
let rhsEquatable = rhs as? BaseType
else { return false }
return lhsEquatable == rhsEquatable
}
var a: Any = "a" as Any
var b: Any = "b" as Any
equalAny(lhs: a, rhs: b, baseType: NSObject.self) // false
b = "a"
equalAny(lhs: a, rhs: b, baseType: NSObject.self) // true
a = CGFloat(5)
b = 5
equalAny(lhs: a, rhs: b, baseType: NSObject.self) // true
a = ["hello": "world"]
b = ["hello": "world"]
equalAny(lhs: a, rhs: b, baseType: NSObject.self) // true
Если вы хотите использовать это в Objective-C, почему бы не сделать properties
a Dictionary<String, NSObject>
? В конце концов, NSObject
имеет isEqual
, и вы никак не можете поместить примитивные типы в NSDictionary
.
Итак, это будет выглядеть так:
@objc(FooObject)
public class FooObject: NSManagedObject {
@NSManaged public var properties: Dictionary<String, NSObject>
public subscript(property: String) -> NSObject? {
get {
return properties[property]
}
set(newValue) {
for propertyValue in properties.values {
if propertyValue == newValue { // This line is not compiling: Cannot invoke '==' with AnyObject
print("Values in are expected to be unique!")
// Throw an exception here ...
}
}
properties[property] = newValue
}
}
}
Попытка сравнить два объекта типа AnyObject с использованием '==' оператор, определенный в Equatable protocol, приводит к ошибке компиляции в Swift. Кто-нибудь нашел способ собрать такие объекты, не зная реальный тип объектов, которые можно использовать для литья вниз?
Проблема в том, как вы сравниваете два произвольных объекта для равенства контента? Общего определения нет. Если объекты были экземплярами NSObject
или, по крайней мере, NSObjectProtocol
, то есть isEqual:
, но для объектов, которые этого не делают, как вы можете это сделать?
Я использую эти методы, когда мне нужно сравнивать значения, которые не являются равнозначными, но их содержимое. Магия находится в закрытии сравнения. На самом деле просто писать, когда вы хотите сосредоточиться на других вещах, чем сделать каждый класс равным.
/*Pre requsite: Temp and AnotherTemp must be string convertible*/
let a:AnyObject = "1"
let b:AnyObject = Temp("2")
let b:AnyObject = AnotherTemp(3)
let arr:[AnyObject] = [a,b,c]
Utils.has(arr,2,{"\($0)") == $1})//true or false
class Utils{
/**
* EXAMPLE: ["a","b","c"].has("b",{$0 == $1})//true
*/
static func has<T,V>(_ variables:[T],_ match:V,_ method:(T,V)->Bool) -> Bool where V:Equatable{
return first(variables, match, method) != nil
}
/**
* Think of this method as: firstOccurence of something
* Returns the first item that matches PARAM: match according to the constraints in PARAM: method
* EXAMPLE: ["a","b","c"].first("b",{$0 == $1})//b
* EXAMPLE: [("a",0),("b",1)].first("b",{$0.0 == $1}).1//b
* EXAMPLE: [(id:"a",val:0),(id:"b",val:1)].first("b",{$0.id == $1}).val//b
* NOTE: This method should have an extension, but making an extension for two generic variables proved difficult, more research needed, for now use the ArrayParser.first method call
* NOTE: you could do: arr.forEach{/*assert logic goes here*/} but forEach can't return early so you are forced to iterate the entire list
*/
static func first<T,V>(_ variables:[T],_ match:V,_ method:(T,V)->Bool) -> T? where V:Equatable{
for item in variables{
if(method(item,match)){
return item
}
}
return nil
}
}
Я немного смущен, даже предлагаю это, но я тоже столкнулся с этим. Мой сценарий состоял в том, что я сравнивал полученные JSON Dictionaries
, и они фактически жили в 2 разных NSManagedObjectContexts
, поэтому объекты не были бы одинаковыми, хотя значения могут быть.
Во всяком случае, то, что я сделал, это просто создать строки из любых значений и сравнить их. Это взломать, но в моем случае это было для целей тестирования, и оно работает.
set(newValue) {
for propertyValue in properties.values {
if String(describing:propertyValue) == String(describing:newValue){
println("Values in are expected to be unique!")
// Throw an exception here ...
}
}
properties[property] = newValue
}