这是我 1 月 22 日在知乎 iOS 团队内部的一次分享

这一次没有使用 Keynote,尝试性地全程使用 Playground,效果还不错

Playground 版本可以查看这里

Any

什么是 Any? 🤔

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

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. doc

The 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.

Swift value type Objective-C reference type
AffineTransform NSAffineTransform
Array NSArray
Calendar NSCalendar
CharacterSet NSCharacterSet
Data NSData
DateComponents NSDateComponents
DateInterval NSDateInterval
Date NSDate
Decimal NSDecimalNumber
Dictionary NSDictionary
IndexPath NSIndexPath
IndexSet NSIndexSet
Locale NSLocale
Measurement NSMeasurement
Notification NSNotification
Swift numeric types NSNumber
PersonNameComponents NSPersonNameComponents
Set NSSet
String NSString
TimeZone NSTimeZone
URL NSURL
URLComponents NSURLComponents
URLQueryItem NSURLQueryItem
URLRequest NSURLRequest
UUID NSUUID

Write More Swift 🐤

安利一个工具

Attabench