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
无奖竞猜,Never
和 Void
分别是什么类型? 🤔
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.