斯威夫特:任何人都能咬

任何任何? 在Swift中很奇怪。 我们使用Swift Reflection API作为研究的基础,探索未知的深处,并在与Any一起工作时发现Swift Cast API的某些不一致行为。

最近,在使用Swift Reflection API时,我偶然发现了一个痛苦的问题。 基本上,当类型转换为AnyAny时? 遵守协议Swift束手无策,并开始把我迷住了。

事不宜迟,这是吃掉我,强奸我并杀死剩下的代码的代码(请注意,按照该特定顺序):

您可以在我以前的博客文章中了解有关强制转换功能的更多信息:

Swift中的类型推断
我要坦白。 我真的对在我的代码中各处编写iflet和guard感到厌倦。 有… blog.idapgroup.com

如果我们为嵌套属性运行代码,结果将与预期的一样:

 类型=可选(嵌套) 
巢状
嵌套可查询
嵌套为AnyObject的Greetable

当我们尝试使用optionalNested属性运行printCasts时,有趣的部分开始了:

电话的结果就像T-Rex试图藏在衣柜里戴上帽子一样奇怪:

 类型=可选(可选(嵌套)) 
巢状
嵌套为AnyObject的Greetable

Swift尝试将Optional <Optional >嵌套中 ,但是当解压缩Nested符合的Greetable协议时却失败了,但是当我们完成中间转换时,设法对协议进行了双重可选的解包到AnyObject

在最基本的层次上,这确实很糟糕,因为我们不能使用相同的函数来处理Any所隐藏的不同值,并且我们没有任何非显式的方式来了解其中的内容。

请注意,这里的不规则行为是double可选项的展开。 optionalNested键的值本身是可选的,因为该属性是可选的,并且在第一次打包时将其包装在另一个可选中 叫做。 对于optionalNested ,这三个条件都不应该起作用。 这一点对于从Type显式展开是有效的 键入 ,其中类型!=任何

这导致编译器错误: 从“ Int ??”开始向下转换 设为’Int’仅解开可选内容。 我尝试玩耍,发现另一个有趣的行为:

打印1 。 因此,似乎在强制转换为Any时,就像编译器会自动解包一样 然后,如果将其强制转换为Type,则会导致另一个展开。

那么,我们应该如何解决呢? 我们可以尝试对Optional <Optional >进行强制转换

可悲的是,那是行不通的,因为编译器甚至不会考虑编译它。 它只会返回一个错误: error:无法从“ Any?”向下转换 到更可选的类型“ Greetable?” 这让我感到无所适从,因为Any可以是任何类型,意味着它可以是内部的Optional其他包装器(例如,Either,Result等)。 似乎在空间和时间上,对Any的定义还不够好(想像一下,某人在swiftc编写良好,不会滞后或崩溃的平行宇宙中有多么幸运)。

强制转换为AnyObjectAny是一个hack,我们当然应该避免这种情况,因为我们永远无法确定为什么它真正起作用以及将来的编译器版本是否会破坏它。 更好的方法是重写编译器检查并将其强制转换为适当的类型。 我们可以通过使用泛型转换函数来做到这一点:

就类型而言,这可以为我们带来更好的结果,但是嵌套仍未从double可选对象中解包。

 类型=可选(可选(嵌套)) 
巢状
可选的可打招呼吗?

但是最好的解决方案是实际上避免使用Any吗? 不惜一切代价,并尽可能明确。 而是围绕泛型构建您的代码。 另一方面,没有太多的Swift API可以返回类型为Any的结果,在后台它可以是TypeOptional 。 因此,如果您使用它们,请考虑正确的语言行为(即使这会导致代码膨胀)。 不要忘了涵盖与测试相关的所有内容。 我的意思是,您的代码,图书馆代码,孩子,妻子/丈夫,狗,猫,仓鼠,房屋,汽车,父母和您珍惜的其他任何东西都是必需的,而您永远不会知道,编译器何时会改变它的行为。 而且甚至不要尝试说我没有警告过您。

就我而言,我只是忽略了基于Any的Swift Reflection API(即Mirror)的类型系统,并假装我在属性中使用了普通的可能值。 此外,我在投射以防万一之前将Optional 显式地解包为Any 。 这使我避免了隐式的双重可选展开,并感到自己安全可靠(尽管我不确定这样做确实有帮助,因为BetterPrintCasts的工作方式相同):

导致以下输出为optionalNestednested属性:

 巢状 
可选的可打招呼吗?

就是这样,伙计们。 祝您有美好的一天,无论您身在何处,都要保持干燥。