• Swift 中的.self究竟是个什么鬼?
  • 发布于 5小时前
  • 7 热度
    0 评论
前言
昨天码友群里有人问了个问题:"为什么 UITableView 注册 Cell 地时候要传 SomeCell.self 而不是直接传 SomeCell?这个 .self 到底是什么鬼"?我一看,这还真是个好问题!很多人写了好几年 iOS,对这个 .self 都是"知其然不知其所以然"的状态。其实这背后涉及到的是 Swift 中一个相当有意思地概念——元类型(Metatype)。今天就来把这个"看起来高深,其实挺简单"地概念掰开揉碎了讲。

元类型到底是什么鬼
简单点说:元类型就是"类型地类型"。
听起来有点绕?咱们看个例子就明白了:
let name = "小明"
print(type(of: name))  // String
这里 name 是个字符串,它的类型是 String。那么问题来了:String 这个类型,它自己的类型是什么?
答案是:String.Type!
let stringType: String.Type = String.self
print(type(of: stringType))  // String.Type
看到区别了吗?
.name 是一个实例,类型是 String

.String.self 是一个元类型,类型是 String.Type


说人话就是:元类型让你可以把"类型"当作值来传递和操作。就像你可以把数字、字符串当变量传来传去一样,类型本身也能当"值"用。

.self 是什么
.self 其实就是获取某个类型元类型地语法糖:
let stringMetatype = String.self
let intMetatype = Int.self
let arrayMetatype = Array<String>.self
这些都是元类型,你可以把它们存储在变量里、传给函数、甚至放在数组中,就跟普通变量一样:
let types: [Any.Type] = [String.self, Int.self, Bool.self]
print(types)  // [String, Int, Bool]
实战
1. UITableView 注册 Cell
这是最经典的例子:
// 注册自定义 Cell
tableView.register(MyCustomCell.self, forCellReuseIdentifier: "MyCell")
为啥不能直接传 MyCustomCell 呢?原因很简单:
MyCustomCell 是个类型,编译器不知道咋"传递"一个类型
MyCustomCell.self 是元类型,可以当值传递
register 方法需要知道具体要创建哪种类型地 Cell,元类型正好提供了这个信息
2. JSON 解码
用过 Codable 的都写过这样的代码:
let user = try JSONDecoder().decode(User.self, from: jsonData)
这里的 User.self 告诉解码器:"我要解码成 User 类型的对象"。
要是没有元类型,你咋告诉 decode 方法要解码成什么类型呢?

3. 泛型实例化
有时候你需要根据运行时的条件创建不同类型的实例:
func create<T>(_ type: T.Type) -> T where T: NSObject {
    return type.init()
}

let label = create(UILabel.self)
let button = create(UIButton.self)
这里通过传递元类型,让函数知道要创建什么类型的对象。

进阶玩法:运行时类型检查
元类型还能用来做运行时类型检查,这个有时候挺有用地:
func processValue(_ value: Any) {
    let valueType = type(of: value)
    
    if valueType == String.self {
        print("这是个字符串:\(value)")
    } else if valueType == Int.self {
        print("这是个整数:\(value)")
    } else {
        print("不知道这是啥玩意:\(value)")
    }
}
processValue("Hello")  // 这是个字符串:Hello
processValue(42)       // 这是个整数:42
或者更牛逼一点,检查是否符合某个协议:
protocol Drawable {
    func draw()
}

struct Circle: Drawable {
    func draw() { print("画个圆") }
}

func checkAndDraw(_ type: Any.Type) {
    iflet drawableType = type as? Drawable.Type {
        // 这里可以调用 drawableType 的初始化方法
        print("\(type) 可以画画")
    } else {
        print("\(type) 不会画画")
    }
}

checkAndDraw(Circle.self)  // Circle 可以画画
checkAndDraw(String.self)  // String 不会画画
自定义类型的元类型
对于自己定义地类型,元类型也是可以用地:
struct Person {
    let name: String
    let age: Int
    
    staticfunc create(name: String, age: Int) -> Person {
        returnPerson(name: name, age: age)
    }
}
let personType: Person.Type = Person.self
// 可以通过元类型调用静态方法,挺神奇地
let person = personType.create(name: "小红", age: 25)
print(person)  // Person(name: "小红", age: 25)
元类型 vs 实例类型:新手最容易搞混地地方
新手最容易搞混地就是这个:
struct Car {
    let brand: String
}

let car = Car(brand: "Tesla")
// 堆代码 duidaima.com
// 这些都是不同的东西:
let carInstance = car              // 实例,类型是 Car
let carType = Car.self            // 元类型,类型是 Car.Type  
let carInstanceType = type(of: car) // 运行时获取的元类型,也是 Car.Type

print(type(of: carInstance))  // Car
print(type(of: carType))      // Car.Type
print(type(of: carInstanceType)) // Car.Type
小技巧:怎么获取类型名称
有时候你需要获取类型地字符串名称,比如调试地时候:
extension Any.Type {
    var typeName: String {
        return String(describing: self)
    }
}
print(String.self.typeName)     // "String"
print([Int].self.typeName)      // "Array<Int>"
print(Car.self.typeName)        // "Car"
// 非常方便
这个在调试、日志记录时特别有用,我经常这么干。

性能方面:元类型有开销吗?
元类型本身基本没有性能开销,它们在编译时就确定了。但要注意:
// 这样效率高,编译时就确定了
let type1 = String.self
// 这样效率稍低,需要运行时才知道
let type2 = type(of: someValue)
所以,如果能在编译时确定类型,优先用 .self。

写在最后
元类型看起来挺高深地,其实就是 Swift 提供地一种"把类型当值用"地机制。一旦理解了这个概念,你会发现很多之前"不理解"地 API 设计其实都挺合理。
评论区聊聊,你在哪些地方用过元类型,有没有踩过什么坑?
用户评论