Swift 是一种强大的面向对象编程语言,它提供了类(Class)这种复合类型,用于定义自定义数据结构。类是 Swift 中的基本构建块之一,它们封装了数据和行为,使我们可以创建复杂的数据模型和功能丰富的应用程序。
一、类的定义
在 Swift 中,我们使用 class 关键字来定义一个类。类的定义包括类名和类体,类体包括属性和方法。
例如,我们可以定义一个简单的 Person 类,如下所示:
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func sayHello() {
print("Hello, my name is \(name), and I'm \(age) years old.")
}
}
二、类的属性
在 Swift 中,类的属性分两种:实例属性和类属性。实例属性是每个类实例都有的值,类属性是所有实例共享的值。
实例属性
实例属性是属于类的每个实例的值。它们可以是存储属性,也可以是计算属性。
- 存储属性:存储常量或变量作为实例的一部分,可以是变量存储属性(由 var 关键字定义)或常量存储属性(由 let 关键字定义)。
class MyClass {
var variableProperty: Int = 0
let constantProperty: Int = 1
}
- 计算属性:不直接存储值,而是提供一个读取器和一个可选的设置器来获取和设置其他属性和值。
class MyClass {
var computedProperty: Int {
get {
return 1
}
set {
// do something with newValue
}
}
}
类属性
类属性是所有实例共享的属性。无论创建了多少个类的实例,这些属性都只有唯一一份。这些属性对于类本身来说是全局的。
类型属性可以是存储类型属性或计算类型属性。它们都必须给出默认值,因为类型本身无法在初始化时给类型属性赋值。
- 存储类型属性:在第一次访问时被延迟初始化。它们用 static 关键字定义在类中。
class MyClass {
static var storedTypeProperty: Int = 1
}
- 计算类型属性:跟计算实例属性一样,计算类型属性返回一个值,而不是存储一个值。它们用 static 关键字定义在类中。
class MyClass {
static var computedTypeProperty: Int {
return 1
}
}
访问和设置类型属性的值是通过类名来进行的:
MyClass.storedTypeProperty = 2
print(MyClass.storedTypeProperty)
print(MyClass.computedTypeProperty)
三、类的方法
类可以定义实例方法和类方法。实例方法由类的实例调用,类型方法由类本身调用。
class MyClass {
func instanceMethod() {
print("This is an instance method.")
}
static func typeMethod() {
print("This is a type method.")
}
}
四、类的继承
在 Swift 中,类可以继承其他类的特性,这是类与结构体的主要区别之一。继承是面向对象编程的一个基本特性,它可以让我们创建基于已存在类的新类,新类可以继承已存在类的特性,并可以添加新的特性。
基础语法
在 Swift 中,类的继承通过在类名后面加上冒号和父类名来表示:
class SubClass: SuperClass {
// 类的定义
}
重写
子类可以提供自己的实现来重写父类的方法、属性或下标。重写需要在重写定义前面加上 override 关键字:
class SuperClass {
func method() {
print("SuperClass method")
}
}
class SubClass: SuperClass {
override func method() {
print("SubClass method")
}
}
防止重写
如果你不希望你的方法、属性或下标被重写,你可以在它们的定义前面加上 final 关键字:
class SuperClass {
final func method() {
print("SuperClass method")
}
}
class SubClass: SuperClass {
// 这里不能重写 method
}
调用父类的实现
在子类的方法、属性或下标中,你可以通过 super 关键字来调用父类的实现:
class SuperClass {
func method() {
print("SuperClass method")
}
}
class SubClass: SuperClass {
override func method() {
super.method()
print("SubClass method")
}
}
五、析构器
在 Swift 中,每个类都有一个析构器,当类的实例被销毁时,析构器会被调用。析构器用于释放类实例可能占用的资源,例如打开的文件句柄、网络连接等。
析构器在类的定义中通过 deinit 关键字来声明,它不接受任何参数,也没有括号:
class MyClass {
deinit {
// 在这里执行析构过程
}
}
当类的实例被销毁时,Swift 会自动调用其析构器。你不需要直接调用析构器。子类的析构器会自动继承父类的析构器,并在子类析构器的末尾调用父类的析构器。
class BaseClass {
deinit {
print("BaseClass deinitialized")
}
}
class SubClass: BaseClass {
deinit {
print("SubClass deinitialized")
}
}
do {
let instance = SubClass()
} // instance 在这里离开作用域,将被销毁,输出 "SubClass deinitialized" 和 "BaseClass deinitialized"
注意,因为 Swift 使用自动引用计数(ARC)来管理内存,所以只有当类的实例的所有引用都被销毁时,实例才会被销毁,析构器才会被调用。如果有内存泄漏,例如两个类实例互相持有对方的强引用,形成了引用循环,那么这两个实例将永远不会被销毁,析构器也永远不会被调用。因此,正确管理内存和避免引用循环是非常重要的。
六、类型转换
在 Swift 中,类类型转换允许我们在运行时检查和解释实例的类型。这在处理类和其子类的实例时特别有用。Swift 提供了三种类型转换运算符:
1. is:检查一个实例是否是一个类的子类型。
class Animal {}
class Dog: Animal {}
let a: Animal = Dog()
if a is Dog {
print("a is a Dog")
}
在上面的代码中,a is Dog 检查 a 是否是 Dog 类型的实例。因为 Dog 是 Animal 的子类,所以这个检查返回 true。
2. as?:向下转型,如果转型失败,返回 nil。
class Animal {}
class Dog: Animal {}
let a: Animal = Animal()
if let d = a as? Dog {
print("a is a Dog")
} else {
print("a is not a Dog")
}
在上面的代码中,a as? Dog 尝试将 a 转型为 Dog 类型。因为 a 实际上是 Animal 类型的实例,所以转型失败,d 是 nil
3. as!:向下转型,如果转型失败,会触发运行时错误。
class Animal {}
class Dog: Animal {}
let a: Animal = Dog()
let d = a as! Dog // 这里不会触发运行时错误,因为 a 实际上是 Dog 类型的实例
在上面的代码中,a as! Dog 尝试将 a 强制转型为 Dog 类型。因为 a 实际上是 Dog 类型的实例,所以转型成功,d 是 Dog 类型的实例。
注意,你应该尽量避免使用 as!,除非你确定转型一定会成功。如果转型失败,as! 会触发运行时错误。在大多数情况下,你应该使用 as?,并通过可选绑定(if let 或 guard let)来处理转型失败的情况。