Search code examples
jsonswiftprotocolsreactive-cocoa

Cannot downcast from an object conforming to Equatable to AnyObject


This has been bugging me. I wished to use Moya + ReactiveCocoa and json-swift.

It does not seem easy without rewriting some of these frameworks as at one point it seems I need to return an AnyObject and yet created some data which conforms to Equatable and hence doesn't allow this.

Here is a toy Playground-ready example to demonstrate an error message

struct JSValue: Equatable {
    let value:String
}

func ==(lhs: JSValue, rhs: JSValue) -> Bool {
    return (lhs.value == rhs.value)
}

var jsv = JSValue(value: "abc")
var anyValue = jsv as AnyObject

The last line errors with this "Cannot downcast from JSValue to non-@objc protocol type 'AnyObject'".

I would normally just avoid casting to AnyObject but I have been trying to use an implementation of JSValue from json-swift and repeat the pattern described inside Moya's implementation of ReactiveCocoa seen with the usage of tryMap here. The signature of the callback required by tryMap in the Moya + RACSignal extension expects a return of an AnyObject.

For those that would prefer to see an example than understand the context, here is what I mean:

import Foundation

import Moya
import ReactiveCocoa

import JSONLib

/*
let MoyaErrorDomain = "Moya"

public enum MoyaErrorCode: Int {
    case ImageMapping = 0
    case JSONMapping
    case StringMapping
    case StatusCode
    case Data
}
*/

public typealias JSParsingResult = (value: JSValue?, error: Error?)

/// Extension for processing raw NSData generated by network access.
public extension RACSignal {

    /// Maps data received from the signal into a literal JSON type. If the conversion fails, the signal errors.
    public func mapLiteralJSON() -> RACSignal {
        return tryMap({ (object, error) -> AnyObject! in
            var json:AnyObject?
            if let response = object as? MoyaResponse {
                let parsingResult = JSON.parse(response.data)
                json = parsingResult.value! as AnyObject // The same error message as described previously.
            }

            // Note: ignore that I am not handling errors yet...
            /*
            if json == nil && error != nil && error.memory == nil {
                var userInfo: [NSObject : AnyObject]?
                if object != nil {
                    userInfo = ["data": object]
                }

                error.memory = NSError(domain: MoyaErrorDomain, code: MoyaErrorCode.JSONMapping.toRaw(), userInfo: userInfo)
            }
            */

            return json
        })
    }
}

Are there any quick ways around this error message, or is my only choice to use a (1) different method of parsing JSON which does not conform to Equatable, or (2) rewrite Moya to use the other version of ReactiveCocoa in which tryMap does not return AnyObject, or (3) not using ReactiveCocoa.

It would be really nice to fix this properly by avoiding AnyObject, but at this point I probably lack both the Swift and ReactiveCocoa skills to do so...

On a non-technical level, what are ways around this? Would it be wise to decrease my exposure to unfinished tech?


Solution

  • You have hit this problem because you are mixing libraries that were written for Objective-C with those that were written for Swift.

    Objective-C id maps to AnyObject, which can be a reference to any class.

    With Swift, if you want a variable that can reference anything you need to use Any.

    Furthermore, with Swift you are encouraged to use structs, where value-type semantic makes sense, for performance reasons. This will no doubt result in structs being used quite frequently within Swift libraries. As a result you can expect significant compatibility issues with Swift libraries and Objective-C libraries.