Сравните AnyObjects в Swift, не приводя их к определенному типу

Попытка сравнить два объекта типа AnyObject с помощью оператора '==', определенного в протоколе Equatable, приводит к ошибке компиляции в Swift. Кто-нибудь нашел способ сравнить такие объекты, не зная реального типа объектов, которые можно использовать для даункинга?

Предпосылкой для этого вопроса является то, что у меня есть словарь Dictionary, где значения должны предоставляться через подстрочный индекс, а затем в какой-то момент мне нужно сравнить значения в словаре, чтобы убедиться, что они уникальны.

РЕДАКТИРОВАТЬ Вот фрагмент, демонстрирующий проблему.

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

Обратите внимание, что универсальный тип, такой как , объявленный в определении класса и используемый как тип значения словаря, не решит проблему, поскольку его нельзя использовать вместе с подклассом NSManagedObject.

9 ответов

Используйте оператор === из документации 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;
    }
}

Интересно, что C#, например, обходит это, определяя IComparable интерфейс как имеющий метод:

int CompareTo(Object obj)

Это позволяет каждому IComparable объект, чтобы сравнить себя с любым другим объектом (но функция должна всегда делать свою собственную проверку типа здесь, чтобы убедиться, obj имеет правильный тип).

Swift 3 не работает с === для сравнения сравните, если объекты имеют тип Any, Причина в Any может содержать целое число и число с плавающей запятой или любую другую вещь, которая на самом деле не является объектом. Так что вам придется бросить его NSObject сравнить 2 предмета. подобно

if (obj1 as! NSObject) === (obj2 as! NSObject)
{
//logic goes here
}

Это должно работать довольно хорошо для большинства случаев использования:

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

Посмотрите на реализацию:

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

Редактировать:

С вашими словарями должно быть что-то вроде этого:

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"])

Если вы хотите использовать это в Objective-C, почему бы не сделать properties 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
        }
    }
}

Я немного стесняюсь даже предлагать это, но я тоже столкнулся с этим. Мой сценарий состоял в том, что я сравнивал 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
}

Я использую эти методы, когда мне нужно сравнить значения, которые не равны, но их содержание есть. Волшебство в закрытии сравнения. Очень просто написать, когда вы хотите сосредоточиться на других вещах, а не на том, чтобы сделать каждый класс уравновешенным.

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

Попытка сравнить два объекта типа AnyObject с помощью оператора '==', определенного в протоколе Equatable, приводит к ошибке компиляции в Swift. Кто-нибудь нашел способ конкурировать с такими объектами, не зная реальный тип объектов, которые могут быть использованы для броска вниз?

Проблема в том, как вы сравниваете два произвольных объекта на предмет равенства контента? Там нет общего определения этого. Если объекты были экземплярами NSObject, или по крайней мере NSObjectProtocolто есть isEqual:, но для объектов, которые не являются, как вы можете это сделать?

Другие вопросы по тегам