andriys
andriys

Reputation: 2222

Casting AnyObject to T

Code:

class User
{
    class var BoolProperty: Bool
    {
        get {
            var anyObject: AnyObject? = getValue("BoolProperty")

            if let value = anyObject as? Bool {
                return value
            }
            else {
                return false
            }
        }
        set(value) {
            setValue("BoolProperty", value: value)
        }
    }

    private class func getValue(key: String) -> AnyObject?
    {
        var store = NSUserDefaults.standardUserDefaults();
        return store.objectForKey(key) as AnyObject?
    }
}

passes the test:

class UserTests: XCTestCase
{
    func testFields()
    {
        User.BoolProperty = true
        var result = User.BoolProperty
        XCTAssertEqual(true, result)
    }
}

but the following code doesn't pass the same test, which uses T instead of Bool for casting:

class User
{
    class var BoolProperty: Bool
    {
        get {
            return get("BoolProperty", defaultValue: false)
        }
        set(value) {
            setValue("BoolProperty", value: value)
        }
    }

    private class func getValue(key: String) -> AnyObject?
    {
        var store = NSUserDefaults.standardUserDefaults();
        return store.objectForKey(key) as AnyObject?
    }

    private class func get<T>(key: String, defaultValue: T) -> T
    {
        var anyObject: AnyObject? = getValue(key)

        if let value = anyObject as? T {
            return value
        }
        else {
            return defaultValue
        }
    }
}

it seems, that for some reason if let value = anyObject as? T always returns false when casting to T.

In C# this is a classic example of working with untyped collections and I was wondering what's the right approach to achieve the same in Swift.

Upvotes: 1

Views: 1811

Answers (1)

Rob Napier
Rob Napier

Reputation: 299275

The problem is that an NSNumber is not a Bool. It looks like a Bool, and it can be converted to a Bool, but it's really a different type. Your anyObject is really an NSNumber, and asking for it to be as? T where T is Bool is too far. That's still likely a compiler bug, because it should be the same as a generic as without, but it's where the problem is happening.

You need to specialize the generic function to NSNumber:

get {
return Bool(get("BoolProperty", defaultValue: NSNumber(bool: false)))
}

It's still probably worth opening a radar. It shouldn't behave differently with and without the generic.

That said, part of the trouble is the use of AnyObject. A Bool is not an AnyObject. It's an Any. When you try to assign it to an AnyObject, it gets converted into an NSNumber. I don't know of any documentation of this; I've worked it out empirically in playgrounds. Compare the results of let a:AnyObject = false and let a:Any = false.

In theory, this should fix it all:

var anyObject: Any? = getValue(key)

But it currently crashes the compiler.

Upvotes: 4

Related Questions