在Swift中将泛型可选类型转换为具体类型

7

我正在研究Swift中的泛型,并遇到了一个无法解决的问题:如果我将一个值强制转换为泛型参数的类型,则不会执行转换。如果我尝试使用静态类型进行相同的操作,则可以正常工作。

class SomeClass<T> {
    init?() {
        if let _ = 4 as? T {
            println("should work")
        } else {
            return nil
        }
    }
}

if let _ = SomeClass<Int?>() {
    println("not called")
}

if let _ = 4 as? Int? {
    println("works")
}

有人能解释这种行为吗?两种情况不应该是等价的吗?

更新

上面的例子被最大程度地简化了。下面的例子更好地说明了需要进行转换。

class SomeClass<T> {
    init?(v: [String: AnyObject]) {
        if let _ = v["k"] as? T? {
            print("should work")
        } else {
            print("does not")
            return nil
        }
    }
}

if let _ = SomeClass<Int?>(v: ["k": 4]) {
    print("not called")
}

if let _ = SomeClass<Int>(v: ["k": 4]) {
    print("called")
}

第二次更新

在@matt让我了解了AnyObjectAny之后,@Darko在评论中指出字典使我的示例过于复杂,因此这是我下一步的改进。

class SomeClass<T> {
    private var value: T!

    init?<U>(param: U) {
        if let casted = param as? T {
            value = casted
        } else {
            return nil
        }
    }
}


if let _ = SomeClass<Int?>(param: Int(4)) {
    println("not called")
}

if let _ = SomeClass<Int>(param: Int(4)) {
    println("called")
}

if let _ = Int(4) as? Int? {
    println("works")
}

if let _ = (Int(4) as Any) as? Int? {
    println("Cannot downcast from Any to a more optional type 'Int?'")
}

我之前尝试过使用init?(param: Any),但这会导致最后一个if中说明的相同问题,在其他地方讨论过。所以问题就在于:有没有人真正将任何东西转换为通用可选类型?在任何情况下?如果有,我很乐意接受任何有效的示例。

我也卡在这个问题上了,感觉就像是语言里的一个漏洞。你最后解决了吗? - Chris Hatton
我刚刚在Xcode 8.2.1项目中再次尝试了第三个示例(Playgrounds目前无法使用),并按预期运行,打印“not called”和“called”。我还可以将“T!”更改为“T”。 - Sebastian
3个回答

3

这实际上与泛型无关,而是与AnyObject(以及类型转换的工作原理)有关。请考虑以下内容:

    let d = ["k":1]
    let ok = d["k"] is Int?
    print (ok) // true

    // but:

    let d2 = d as [String:AnyObject]
    let ok2 = d2["k"] is Int?
    print (ok2) // false, though "is Int" succeeds

由于您的初始化器将字典转换为 [String:AnyObject],您也处于同样的境地。


那么为什么 'let ok2 = d2["k"] is Int' 会成功呢?dict总是将optional添加到返回值中,除此之外,Int不像AnyObject一样是一个类类型。 - Darko
因为当你说“is”时,Optional会得到特殊处理;我们假装你对Optional内部的东西提出了“is”的问题。所以现在我们正在问“这个AnyObject能否被转换为Int”——它可以,因为它是NSNumber。但是一个AnyObject不能被转换为Optional<Int> - matt
换句话说,一旦你意识到原问题中的泛型是一个完全的红鱼,这里发生的一切都被我书中关于类型转换(http://www.apeth.com/swiftBook/ch04.html#_casting)和 AnyObject(http://www.apeth.com/swiftBook/ch04.html#_anyobject)的讨论所覆盖。 - matt
我不明白为什么它不一致。Optional<NSNumber> 不是一个类类型。它没有桥接到 Objective-C。你不能将 Int?NSNumber? 强制向上转换为 AnyObject,因此你永远不可能将 AnyObject 转换为这些类型中的任何一个。令我惊讶的是编译器居然让你提出这个问题。 - matt
非常感谢您的详细解释!您让我意识到了AnyAnyObject之间的区别(不幸的是,Any似乎有不同的问题:https://dev59.com/Al4c5IYBdhLWcg3wXZcC)。然而,我仍然不明白为什么我的第一个示例不起作用,这与`AnyObject`无关...还是有关系吗? - Sebastian
显示剩余2条评论

1

现在它可以正常工作 - 在Playgrounds和项目中都是如此。在Swift 5.4中重新测试:

class SomeClass<T> {
    private var value: T

    init?<U>(param: U) {
        if let casted = param as? T {
            value = casted
        } else {
            return nil
        }
    }
}


if let _ = SomeClass<Int?>(param: Int(4)) {
    print("called")
}

if let _ = SomeClass<Int>(param: Int(4)) {
    print("called")
}

这将按预期两次打印called


附注:我的之前的回复是在2017年,当时我说它按预期工作。不确定为什么被删除了。这个问题刚刚又收到了一个赞,所以我想分享一下当前的情况。 - Sebastian

0
据我所见,您更新的代码的目标是找出传递的参数(字典的值)是否为类型T。因此,您误用了as?强制转换来检查类型。实际上,您想要的是“is”运算符。
class SomeClass<T> {
    init?(v: [String: AnyObject]) {
        if v["k"] is T {
            print("called if AnyObject is of type T")
        } else {
            print("called if AnyObject is not of type T")
            return nil
        }
    }
}

if let _ = SomeClass<Int>(v: ["k": 4]) {
    print("called")
}

if let _ = SomeClass<Int?>(v: ["k": 4]) {
    print("not called")
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接