原文:AnyObject, Any, and any: When to use which?
AnyObject可以代表任何class类型的实例。Any可以表示任意类型,甚至包括函数(func)类型。any用于描述存在类型。
AnyObject 和 Any 在 SE-355 中引入了一个新选项 any,这让我们的开发人员更难了解它们的区别。每个选项都有其用例和关于何时不使用它们的陷阱。
Any 和 AnyObject 是 Swift 中的特殊类型,用于类型擦除(type erasure),与 any 没有直接关系。请注意本文中的大写 A,因为我将介绍 Any 和任何具有不同解释的内容。让我们深入了解细节!
何时使用 AnyObject?
AnyObject 本质上是一个 protocol:
protocol AnyObject{
}
AnyObject 是所有 class 都隐式遵守的协议。事实上,标准库包含一个表示 AnyObject.Type 的类型别名 AnyClass,即 typealias AnyClass = AnyObject.Type。
print(AnyObject.self) // Prints: AnyObject
print(AnyClass.self) // Prints: AnyObject.Type
所有 class、class type 或 class-only protocols(纯类协议)都可以使用 AnyObject 作为它们的具体类型。基于演示目的,你可以创建一个包含不同类型的数组:
let imageView = UIImageView(image: nil)
let viewController = UIViewController(nibName: nil, bundle: nil)
let mixedArray: [AnyObject] = [
// 我们可以将 UIImageView 和 UIViewController 添加到同一个数组中
// 因为它们都可以转换为 `AnyObject`。
imageView,
viewController,
// `UIViewController` 类型隐式遵守 `AnyObject` 协议,因此也可以添加。
UIViewController.self
]
只有类符合 AnyObject 协议,这意味着你可以使用它来限制仅由引用类型实现的协议:
protocol MyProtocol: AnyObject { }
// 这也意味着,结构体、枚举类型等值类型无法遵守该协议
如果你需要无类型对象的灵活性,你可以使用 AnyObject。我可以给出使用任何对象集合的示例,这些对象在使用时可以转换回具体类型,但我想建议一些不同的东西。
我不记得在我的任何项目中使用过 AnyObject,因为我总是能够使用具体类型来代替。这样做还会产生更易读的代码,让其他开发人员更容易理解。为了向你展示这一点,我创建了这个示例方法,它将图像配置到目标容器中:
func configureImage(_ image: UIImage, in imageDestinations: [AnyObject]) {
for imageDestination in imageDestinations {
switch imageDestination {
case let button as UIButton:
button.setImage(image, for: .normal)
case let imageView as UIImageView:
imageView.image = image
default:
print("Unsupported image destination")
break
}
}
}
通过使用 AnyObject 作为我们的目标,我们总是需要使用默认实现进行转换并考虑转换失败(的情况)。我的喜好是总是使用具体协议重写它:
// 创建一个协议描述图像的目标容器
protocol ImageContainer {
func configureImage(_ image: UIImage)
}
// 让 UIButton 和 UIImageView 都遵守该协议
extension UIButton: ImageContainer {
func configureImage(_ image: UIImage) {
setImage(image, for: .normal)
}
}
extension UIImageView: ImageContainer {
func configureImage(_ image: UIImage) {
self.image = image
}
}
// 在方法中使用协议作为“具体类型”
func configureImage(_ image: UIImage, into destinations: [ImageContainer]) {
for destination in destinations {
destination.configureImage(image)
}
}
生成的代码更干净、可读,并且不再需要处理不受支持的容器。实例需要遵守 ImageContainer 协议才能接收配置的图像。
何时使用 Any?
Any 可以表示任何类型的实例,包括函数类型:
let arrayOfAny: [Any] = [
0,
"string",
{ (message: String) -> Void in print(message) }
]
var anything = [Any]()
anything.append("Any Swift type can be added")
anything.append(0)
anything.append({(foo: String) -> String in "Passed in (foo)"})
对我来说,与 AnyObject 相比,相同的规则适用于 Any,这意味着你应该始终寻求使用具体类型。 Any 通过允许你转换任何类型的实例而更加灵活,与使用具体类型相比,使代码更难预测。
泛型和 Any 类型
Any类型和泛型两者都能用于定义接受两个不同类型参数的函数。- 泛型可以用于定义灵活的函数,类型检查仍然由编译器负责;而
Any类型则可以避开 Swift 的类型系统 (所以应该尽可能避免使用)。
// 在泛型版本的定义中,我们可以清楚地看到返回值和输入值类型完全一样。
func noOp<T>(x: T) -> T {
return x
}
// Any 版本中,返回值是任意类型 — 甚至可以是和原来的输入值不同的类型。
func noOpAny(x: Any) -> Any {
return x // 这里避开了 Swift 的类型系统,可以返回任意类型,结果可能会导致各种意想不到的错误!
}
何时使用 any?
any 是在 SE-335 中引入的,看起来与 Any 和 AnyObject 相似,但用途不同,因为你使用它来指示存在类型(existential)的使用。以下示例代码使用本文前面示例中的示例代码演示了图像配置器:
struct ImageConfigurator {
var imageContainer: any ImageContainer
func configureImage(using url: URL) {
// 注:这不是有效下载图像的方法,这里只是用作一个简单的例子。
let image = UIImage(data: try! Data(contentsOf: url))!
imageContainer.configureImage(image)
}
}
let iconImageView = UIImageView()
var configurator = ImageConfigurator(imageContainer: iconImageView)
configurator.configureImage(using: URL(string: "https://picsum.photos/200/300")!)
let image = iconImageView.image
如你所见,我们通过使用 any 关键字标记我们的 imageContainer 属性来指示使用的 ImageContainer 是一个存在类型。使用 any 标记协议将从 Swift 6 开始强制执行,因为它将表明以这种方式使用协议对性能的影响。
存在类型具有重大限制和性能影响,并且比使用具体类型更昂贵,因为你可以动态更改它们。以下代码是此类更改的示例:
let button = UIButton()
configurator.imageContainer = button
我们的 imageContainer 属性可以表示遵守 ImageContainer 协议的任何值,并允许我们将其从 UIImageView 实例更改为 UIButton 实例。需要动态内存才能使这成为可能,从而消除了编译器优化这段代码的可能性。在引入 any 关键字之前,没有明确的提示向开发人员表明这种性能成本。
远离 any
在某种程度上,你可以争辩 any、Any 和 AnyObject 有一些共同点:谨慎使用它们。
你可以使用泛型重写上面的代码示例并消除对动态内存的需求:
struct ImageConfigurator<Destination: ImageContainer> {
var imageContainer: Destination
}
意识到性能影响并知道如何改写代码是作为 Swift 开发人员必须具备的一项基本技能。
总结
Any、any 和 AnyObject 看起来很相似,但有重要的区别。在我看来,最好重写你的代码并消除使用这些关键字的需要。这样做通常会产生更具可读性和可预测性的代码。
如果你想进一步提高 Swift 知识,请查看 Swift 类别页面。如果您有任何其他提示或反馈,请随时与我联系或在 Twitter 上向我发送推文。
谢谢!