Подтвердить что ты не робот

Сравните AnyObjects в 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.

4b9b3361

Ответ 1

Используйте оператор === из документации Swift:

Swift также предоставляет два идентификационных оператора (=== и! ==), которые вы используете для проверки того, ссылаются ли ссылки на два объекта на один и тот же экземпляр объекта.

Ответ 2

Я не думаю, что это возможно. Проблема в том, что 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 имеет правильный тип).

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

Если вы хотите использовать это в 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
        }
    }
}

Ответ 7

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

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

Ответ 8

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

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

Ответ 9

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