Any & AnyObject in Swift

这是我 1 月 22 日在知乎 iOS 团队内部的一次分享。这一次没有使用 Keynote,尝试性地全程使用 Playground,效果还不错。Playground 版本可以查看这里

Any

什么是 Any? 🤔
Any can represent an instance of any type at all, including function types. docAny 可以代表一个任意类型的实例,包括函数类型。

Types to Any

所有能够看到的类型都能被转换成 Any
var any: Any = "Any"

// static type is Any, dynamic type is Int
any = 42

type(of: any)


// static type is Any, dynamic type is String
any = "string"

// static type is Any, dynamic type is (Int, String)
any = (42, "42")

// static type is Any, dynamic type is [Int]
any = [1, 2, 3]

// static type is Any, dynamic type is [Int: Int]
any = [1: 2]

Closures can be Any

any = {}

// ... actually is

let c: () -> () = {}

Functions are just named closures, so it can be Any

func function() {}

any = function

// ... actually is

let f: () -> () = function

let fs: () -> () = function.self

// 上面用 self 和不用 self 有什么区别呢?🤔

1
1.self

Enums can be Any

💡 Optionals are enums
any = Optional<Int>(1) as Any
// ... actually is
let o: Optional<Int> = Optional<Int>(1)
// or
let o1: Int? = Optional<Int>(1)

any = Optional<Int>.self
// ... actually is
let o2: Optional<Int>.Type = Optional<Int>.self
// or
let o3: Int?.Type = Optional<Int>.self

Structs and classes can be Any

💡 String is a struct
any = "abc"

any = String.self
// ... actually is
let s: String.Type = String.self
💡 KeyPath is a class
any = \\String.count as Any
any = KeyPath<String, Int>.self

let k2: KeyPath<String, Int>.Type = KeyPath<String, Int>.self
// ... can be
let k1: AnyObject.Type = KeyPath<String, Int>.self
// or
let k: AnyClass = KeyPath<String, Int>.self

Protocols can be Any

💡 Error is a protocol
any = Error.self
// protocol is not like other types (sturct, class ...)
// so this is not correct
//let e1: Error.Type = Error.self
// ... actually is
let e: Error.Protocol = Error.self

// this is correct
struct TestError: Error {}
let e2: Error.Type = TestError.self
Any means anything
所以它也能代表它自己 😎
any = Any.self
// ... actually is
let ap: Any.Protocol = Any.self

无奖竞猜,NeverVoid 分别是什么类型? 🤔

any = Never.self
any = Void.self
any = AnyObject.self
Never 是个 empty case enum,Void 是 () empty tuple

Any To Types

上面介绍了从任意类型转换成 Any,那么如何把 Any 转换成想要的类型呢?(Downcast)
let thing: Any = 42

// if let
if let integer = thing as? Int {
    integer
}

// ... or simpler,但并不推荐
thing as! Int

// or use switch
let things: [Any] = [
  	0,
    0.0,
    "hello",
    42,
    (0, 0),
    Optional<Int>.self,
    { (name: String) -> String in "Hello, (name)" }
]

for thing in things {
  switch thing {
    case 0 as Int:
        "zero as an Int"
    case 0 as Double:
        "zero as a Double"
    case let someInt as Int:
        "an integer value of (someInt)"
    case let someDouble as Double where someDouble > 0:
        "a positive double value of (someDouble)"
    case is Double:
        "some other double value that I don't want to print"
    case is Optional<Int>.Type:
        "type optional int.self"
    case let someString as String:
        "a string value of "(someString)""
    case let (x, y) as (Double, Double):
        "an (x, y) point at (x), (y)"
    case let stringConverter as (String) -> String:
        stringConverter("Michael")
    default:
        "something else"
    }
}

AnyObject

What is AnyObject?

AnyObject can represent an instance of any class type. docThe protocol to which all classes implicitly conform.
let v: AnyObject = AnyClassInstance
一起来看一下 AnyObject 的源码
public typealias AnyObject
这语法很神奇,typealias 后面没有等号 - -,不要试图写出,因为根本不会编译过
import Cocoa

var any: AnyObject.Protocol = AnyObject.self
let any1: AnyObject = NSObject()
let any2: AnyObject.Type = NSObject.self
比较早使用过 Swift 的同学对 AnyObject 一定不会陌生,因为在 Swift 3 之前,AnyObject 是作为 Objective-C id 的存在
swift(<3.0)

id label = [UILabel new];
// equals
var label: AnyObject = UILabel()

id as Any

但是随着 swift-evolution 的这个 proposal SE-0116
在 Swift 3 中,Objective-C 的 id 被 map 到了 Any
swift(>=3.0)

id label = [UILabel new];
// equals
var label: Any = UILabel()

这么做的目的是什么?🤔

struct 🐱 {
    let name: String
}

extension Notification.Name {
    static let CatDidMeow = Notification.Name(rawValue: "cat.did.meow")
}
在 Swift 3.0 之前,如果我想把这个 struct 通过 Notification 的 userinfo 的 object 发送出去,需要做下面这些事情,
swift(<3.0)

// 注意这里 userInfo Object 的类型是 AnyObject
NotificationCenter.default
    .post(
        name: Notification.Name,
        object: AnyObject?
        userInfo: [NSObject : AnyObject]?)

// 需要手动创建一个 Class 类型的 Box 把 value types object 给包起来
final class Box<T>: NSObject {
    let value: T
    init(_ value: T) {
        self.value = value
    }
}

// post
NotificationCenter.default
    .post(
    name: .CatDidMeow,
    object: nil
    userInfo: ["cat" : Box(🐱(name: "Kitty"))])

// observe
if let box = notification.userInfo?["cat"] as? Box<🐱> {
    let cat = box.value
}
在 Swift 3.0 之后,不需要 Box 类型的封装也能把 🐱 发送出去了,
swift(>=3.0)
NotificationCenter.default
    .addObserver(forName: .CatDidMeow,
                  object: nil,
                   queue: nil) { notification in
                    if let cat = notification.userInfo?["cat"] as? 🐱 {
                        cat.name
                    }
}

NotificationCenter.default
    .post(name: .CatDidMeow,
        object: nil,
      userInfo: ["cat": 🐱(name: "Kitty")])
✅ 这样做的目的是让 Swift 能够最大力度的使用已有 Cocoa 中的 Objective-C API,也符合苹果希望我们最大力度使用 value types 的愿景
💫 Use more value types

那么是如何实现的呢?🤔

When a Swift value or object is passed into Objective-C as an id parameter, the compiler introduces a universal bridging conversion operation.
也就是说所有的类型都可以被转成 id 类型

@objc Classes

因为 Swift 和 Objective-C 两边都能正常操作,所以不需要特别的支持

Bridged value types

如果是 Foundation 中支持 ObjcBridgable 的类型,可以自动被互相转换成 Swift 和 Objective-C 中不同的类型
let date: NSDate = Date() as NSDate
let data: NSData = Data() as NSData

Unbridged value types

其余类型属于没被 bridge 的 value types,
struct Person {
    let username: String
}
@interface Object : NSObject

- (void)updateUser:(id)user;

@end
// ... generated swift interface

func updateUser(user: Any)


let object = Object()
object.updateUser(Person(username: "abe"))

Person` -> `_Swift_Value *
通过使用 lldb 下断点发现,Swift 会把 unbridged value types 转换成 _Swift_Value * 的类型,在 Swift 中通过 cast 可以转换成对应的类型,但在 Objective-C 中对这种类型无法操作。
When id values are brought into Swift as Any, we use the runtime's existing ambivalent dynamic casting support to handle bridging back to either class references or Swift value types.
@objcMembers
public class SwiftClass: NSObject {
  public class func testID(_ any: Any) {}
}

[SwiftClass testID:someIDVariable]
// ... equals
let object = Object()
let a = object as Any

Quiz 📝

下面的输出都是什么?
let arrayOfAnyObject: [AnyObject] = [
    1 as AnyObject,
    "str" as AnyObject
]

arrayOfAnyObject.forEach { element in
    print(type(of: element))
    if let e = element as? Int { "Int (e)" }
    if let e = element as? String { "String (e)" }
    if let e = element as? NSString { "NSString (e)" }
    if let e = element as? NSNumber { "NSNumber (e)" }
}

let arrayOfAny = arrayOfAnyObject as [Any]
arrayOfAny.forEach { element in
    print(type(of: element))
    if let e = element as? Int { "Int (e)" }
    if let e = element as? String { "String (e)" }
    if let e = element as? NSString { "NSString (e)" }
    if let e = element as? NSNumber { "NSNumber (e)" }
}

let arrayOfAny2: [Any] = [1, "2"]
arrayOfAny2.forEach { element in
    print(type(of: element))
    if let e = element as? Int { "Int (e)" }
    if let e = element as? String { "String (e)" }
    if let e = element as? NSString { "NSString (e)" }
    if let e = element as? NSNumber { "NSNumber (e)" }
}

AnyObject 的其它作用

Weak

protocol SomeDelegate: AnyObject {}

// 🔴 error: 'weak' must not be applied to non-class-bound 'Any'; consider adding a protocol conformance that has a class bound
// weak var d: Any? = nil

import Cocoa
class SomeClass: NSObject {}
extension SomeClass: SomeDelegate {}
var someClassInstance: SomeClass? = SomeClass()
weak var d: AnyObject? = someClassInstance
someClassInstance = nil
d

Any & AnyObject 的其它应用

Then

import Cocoa

let frame = CGRect().with {
    $0.origin.x = 10
    $0.size.width = 100
}

_ = NSTextField().then {
    $0.textColor = .black
    $0.font = .systemFont(ofSize: 15)
}

// or your custom types

class 🐤 {
    var name: String = "bird"
}

extension 🐤: Then {}

let 🕊 = 🐤().with {
    $0.name = "dove"
}

🕊.name

Some Tips

减少 Any 和 AnyObject 类型的使用

不要放弃编译检查,如果无法避免,尽早把 Any 或者 AnyObject 转换成真正的类型,然后再使用

Obective-C 中暴露的接口,如果有容器类型,最好把微弱的泛型约束加上

@property (nonatomic) NSArray<NSDate *> *dueDates;
@property (nonatomic) NSDictionary<NSNumber *, NSString *> *dataDictionary;
@property (nonatomic) NSSet<NSString *> *filter;

⬇️  ✅

public var dueDates: [Date]
public var dataDictionary: [NSNumber : String]
public var filter: Set<String>
@property (nonatomic) NSArray *dueDates;
@property (nonatomic) NSDictionary *dataDictionary;
@property (nonatomic) NSSet *filter;

⬇️ 🔴

public var dueDates: [Any]
public var dataDictionary: [AnyHashable : Any]
public var filter: Set<AnyHashable>

Prefer Swift Value Types to Bridged Objective-C Reference Types

You should almost never need to use a bridged reference type directly in your own code.

Write More Swift 🐤

安利一个工具


© Xinyu 2014 - 2024