10. Swift 类 Class.png

创建类

  1. class Person {
  2. var name: String = ""
  3. var age: Int?
  4. var id: String?
  5. init(name: String) {
  6. self.name = name
  7. }
  8. func introduce() {
  9. print("Person (name: \(name))")
  10. }
  11. }
  12. let p = Person(name: "huangjian")
  13. p.introduce()

恒等运算符

Swift 中除了 class 类型是引用类型,其他整型,浮点型,数组,结构体等都是值类型。
值类型的每个实例没有独一无二的标识,而引用类型的每个实例都有独一无二的标识。

=== 只能用于判断两个类的实例是不是同一个实例,即判断内存地址是否一致。
== 可以用于任何数据类型的判断,包括值类型和引用类型。
如果要使用 == ,都需遵循 Equatable 协议,并实现 == 静态方法,以此来判断两实例是否等同。

  1. extension Person: Equatable {
  2. static func == (lhs: Person, rhs: Person) -> Bool {
  3. return lhs.id == rhs.id
  4. }
  5. }
  6. let p1 = Person(name: "huangjian")
  7. p1.age = 28
  8. p1.id = "10001"
  9. let p2 = Person(name: "jack")
  10. p2.age = 56
  11. p2.id = "10001"
  12. print(p1 == p2) // true 因为两个对象的属性 id 相等,所以他们是同一个人

备注:[参考]


构造器

构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。
这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。

Swift 默认构造函数使用 init() 方法,与 Objective-C 中的构造器不同,
Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。

类实例也可以通过定义析构器(deinitializer)在类实例释放之前执行清理内存的工作。

存储型属性的初始赋值

类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。
存储属性在构造器中赋值时,它们的值是被直接设置的,不会触发任何属性观测器。

存储属性在构造器中赋值流程:

  1. 创建初始值。
  2. 在属性定义中指定默认属性值。
  3. 初始化实例,并调用 init() 方法。

指定构造器和便利构造器

类构造器分为指定构造器和便利构造器

  • 一个指定构造器必须调用它直系父类的一个指定构造器。
  • 一个便利构造器必须调用这个类自身的另一个构造器。
  • 一个便利构造器最终一定会调用一个指定构造器。

总的来说就是,便利构造器是为你类的初始化工作提供方便的,
它们最终一定要依赖于那些真正使你类能正常工作的初始化工作,这也就是指定构造器的工作。

  1. class MainClass {
  2. var prop: Int
  3. init(prop: Int) {
  4. self.prop = prop
  5. }
  6. }
  7. class SubClass: MainClass {
  8. var prop2: Int
  9. init(prop: Int, prop2: Int) {
  10. self.prop2 = prop2
  11. super.init(prop: prop)
  12. }
  13. }

便利构造器:

  1. init 之前有关键字 convenience
  2. 少量参数
  3. 通过调用自身的其他初始化方法 self.init,并给某些参数设置默认值
  1. class MainClass {
  2. var prop: Int
  3. init(prop: Int) {
  4. self.prop = prop
  5. }
  6. }
  7. class SubClass: MainClass {
  8. var prop2: Int
  9. init(prop: Int, prop2: Int) {
  10. self.prop2 = prop2
  11. super.init(prop: prop)
  12. }
  13. override convenience init(prop: Int) {
  14. self.init(prop: prop, prop2: 0)
  15. }
  16. }

可失败构造器

如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器。

变量初始化失败可能的原因有:

  • 传入无效的参数值。
  • 缺少某种所需的外部资源。
  • 没有满足特定条件。

为了妥善处理这种构造过程中可能会失败的情况。你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在 init 关键字后面加添问号 init?

  1. class Person {
  2. var name: String = ""
  3. init?(name: String) {
  4. if name.isEmpty {
  5. return nil
  6. }
  7. self.name = name
  8. }
  9. }
  10. let p = Person(name: "")
  11. print(p?.name)

析构

在一个类的实例被释放之前,析构函数被立即调用。
用关键字 deinit 来标示析构函数,类似于初始化函数用 init 来标示。

Swift 会自动释放不再需要的实例以释放资源,通过自动引用计数(ARC)处理实例的内存管理。

通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。
例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前关闭该文件。
再如,在析构函数中移除监听、移除通知、销毁对象、销毁定时器等。

在类的定义中,每个类最多只能有一个析构函数。析构函数不带任何参数,在写法上不带括号

  1. class MyClass {
  2. init() {
  3. print("MyClass.init...")
  4. }
  5. deinit {
  6. print("MyClass.deinit...")
  7. }
  8. }
  9. var my: MyClass? = MyClass()
  10. my = nil

属性

Swift 属性可分为存储属性和计算属性,let 修饰的常量只能是普通的存储属性!

存储属性

存储属性就是存储在类的实例里的一个常量或变量

延迟存储属性

延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性,且仅会计算一次,在属性声明前使用 lazy 来标示一个延迟存储属性。

注意:
必须将延迟存储属性声明成变量(使用 var 关键字),因为属性的值在实例构造完成之前可能无法得到。
而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。

延迟存储属性一般用于:

  • 延迟对象的创建
  • 当属性的值依赖于其他未知类

格式:

  1. lazy var Property: Type = Value
  1. lazy var Property: Type = xxxMethod(...)
  1. lazy var Property: Type = {
  2. Code
  3. }()

例子:

  1. class MyClass {
  2. lazy var other = OtherClass()
  3. init() {
  4. print("MyClass.init...")
  5. }
  6. }
  7. class OtherClass {
  8. var name = "Swift"
  9. init() {
  10. print("OtherClass.init...")
  11. }
  12. }
  13. var my = MyClass()
  14. print(my.other.name)

计算属性

  • 计算属性不直接存储值,而是提供一个 getter 来获取值,一个可选的 setter 来间接设置其他属性或变量的值。
  • 计算属性的赋值是赋值给其他变量,计算属性的获取是通过其他变量来获取的,更不能赋初始值。
  • 必须使用 var 关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。
  1. class Person {
  2. var firstName: String = ""
  3. var lastName: String = ""
  4. var fullName: String {
  5. set {
  6. /**
  7. 可以通过默认提供的 `newValue` 来获取新值,
  8. 或者通过自定义参数 `set(value)` 来获取新值,二选一
  9. */
  10. let array: [Substring] = newValue.split(separator: " ")
  11. if array.count == 2 {
  12. firstName = String(array.first!)
  13. lastName = String(array.last!)
  14. }
  15. }
  16. get {
  17. return "\(firstName) \(lastName)"
  18. }
  19. }
  20. }
  21. do {
  22. let p = Person()
  23. p.fullName = "Steve Jobs"
  24. print(p.firstName)
  25. print(p.lastName)
  26. }
  27. do {
  28. let p = Person()
  29. p.firstName = "Steve"
  30. p.lastName = "Jobs"
  31. print(p.fullName)
  32. }

只读计算属性

如果计算属性只有 get 而没有 set,则该计算属性为只读计算属性。
只读计算属性的 getter 方法可以隐藏,将方法体置于外层。

静态属性

也称类属性,类型属性,作用于类而非类的实例。与实例属性语法相同,在属性定义前添加 static 关键字修饰即可

  1. class Person {
  2. static let maxAge = 200
  3. }
  4. print(Person.maxAge)

属性观察器

只能用于普通的存储属性,而且是可变属性 (变量 var)

属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。不需要为无法重载的计算属性添加属性观察器,因为可以通过 setter 直接监控和响应值的变化。

可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。

可以为属性添加如下的一个或全部观察器:

  • willSet 在设置新的值之前调用
  • didSet 在新的值被设置之后立即调用
  • willSet 和 didSet 观察器在属性初始化过程中不会被调用
  1. class Clock {
  2. var counter: Int = 0 {
  3. willSet {
  4. /**
  5. 可以通过默认提供的 `newValue` 来获取新值,
  6. 或者通过自定义参数 `willSet(value)` 来获取新值,二选一
  7. */
  8. print("[willSet] newValue: \(newValue), oldValue: \(self.counter)")
  9. }
  10. didSet {
  11. /**
  12. 可以通过默认提供的 `oldValue` 来获取旧值,
  13. 或者通过自定义参数 `didSet(value)` 来获取旧值,二选一
  14. */
  15. print("[didSet] oldValue: \(oldValue), newValue: \(self.counter)")
  16. }
  17. }
  18. }
  19. let cc = Clock()
  20. cc.counter = 3
  21. /**
  22. [willSet] newValue: 3, oldValue: 0
  23. [didSet] oldValue: 0, newValue: 3
  24. */

备注:计算属性和属性观察器所描述的模式也可以用于全局变量和局部变量。


下标脚本

举例来说,用下标脚本访问一个数组 (Array) 实例中的元素可以这样写 someArray[index] ,访问字典 (Dictionary) 实例中的元素可以这样写 someDictionary[key]。

下标脚本可以认为是访问对象、集合或序列的快捷方式,不需要再调用实例的特定的赋值和访问方法。

下标脚本允许你通过在实例后面的方括号中传入一个或者多个的索引值来对实例进行访问和赋值。同样可以根据自身需要提供多个下标脚本实现。下标脚本的方法为 subscript,方法的参数和返回值可以完全自定义。

  1. class MyClass {
  2. var array: Array<String> = []
  3. subscript(index: Int) -> String? {
  4. set {
  5. if index <= array.count && newValue != nil {
  6. array.insert(newValue!, at: index)
  7. } else {
  8. print(index, array.count)
  9. }
  10. }
  11. get {
  12. if index >= array.count {
  13. return nil
  14. }
  15. return array[index]
  16. }
  17. }
  18. }
  19. let my = MyClass()
  20. my[0] = "apple"
  21. my[1] = "orange"
  22. my[2] = "banana"
  23. my[4] = nil
  24. print(my.array)

继承

Swift 不支持多继承,子类可重写(访问)父类的属性、方法、下标脚本、属性观察器。
如果父类的属性、方法等不可以被子类重写,可在其前面添加 final 关键字。

  1. class Animal {
  2. var name: String = ""
  3. init(name: String) {
  4. self.name = name
  5. }
  6. func run() {
  7. print("Animal(name: \(name)) is running")
  8. }
  9. }
  10. class Dog: Animal {
  11. /**
  12. 通过 `set` 和 `get` 来重写父类的 `name` 属性,
  13. 只能是可变属性,即变量,包括存储型属性和计算型属性
  14. */
  15. override var name: String {
  16. set {
  17. print("Dog.name set ...")
  18. super.name = newValue
  19. }
  20. get {
  21. print("Dog.name get ...")
  22. return super.name
  23. }
  24. }
  25. override init(name: String) {
  26. super.init(name: name)
  27. }
  28. // 重写父类的 `run` 方法
  29. override func run() {
  30. print("Dog(name: \(super.name)) is running")
  31. }
  32. }
  33. let dog = Dog(name: "wangcai")
  34. dog.name = "doudou"
  35. dog.run()

扩展

扩展可以对一个类型添加新的功能,但是不能重写已有的功能。

Swift 中的扩展可以:

  • 添加计算型属性和计算型静态属性
  • 定义实例方法和类型方法
  • 提供新的构造器
  • 定义下标
  • 定义和使用新的嵌套类型
  • 使一个已有类型符合某个协议
  1. class Person {
  2. var firstName: String = ""
  3. var lastName: String = ""
  4. }
  5. extension Person {
  6. var fullName: String {
  7. set {
  8. let array = newValue.split(separator: " ")
  9. if array.count == 2 {
  10. firstName = String(array.first!)
  11. lastName = String(array.last!)
  12. }
  13. }
  14. get {
  15. return "\(firstName) \(lastName)"
  16. }
  17. }
  18. func description() -> String {
  19. return "Person (fullName: \(fullName))"
  20. }
  21. }
  22. let p = Person()
  23. p.firstName = "Steve"
  24. p.lastName = "Jobs"
  25. print(p.description())

协议

类可以遵循协议,并提供具体实现来完成协议定义的方法和功能。

协议对属性的规定

  1. 协议用于指定特定的实例属性或类属性,而不用指定是存储型属性或计算型属性。此外还必须指明是只读的还是可读可写的。
  2. 协议中的通常用var来声明变量属性,在类型声明后加上{ set get }来表示属性是可读可写的,只读属性则用{ get }来表示。

协议对构造器的规定:遵循并实现某协议的构造方法,需要在构造方法最前面添加 required 修饰符。

协议可继承。

  1. protocol MyProtocol1 {
  2. var prop1: Int { set get }
  3. var prop2: String { get }
  4. func method1()
  5. func method2(_: String)
  6. }
  7. protocol MyProtocol2 {
  8. init(name: String)
  9. func method(name: String) -> String
  10. }
  11. class MyClass: MyProtocol1, MyProtocol2 {
  12. // MARK: - MyProtocol1
  13. var prop1: Int = 0
  14. var prop2: String {
  15. get {
  16. return ""
  17. }
  18. }
  19. func method1() {
  20. }
  21. func method2(_ name: String) {
  22. }
  23. // MARK: - MyProtocol2
  24. // 如果遵循协议,需要加上 `required`;如果继承自父类,需要加上 `override`
  25. required init(name: String) {
  26. }
  27. func method(name: String) -> String {
  28. return ""
  29. }
  30. }

你可以在协议的继承列表中,通过添加 class 关键字,限制协议只能适配到类(class)类型。该 class 关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。

  1. protocol MyProtocol: class, Equatable {
  2. }

其他限定:where 语句可以用来设置约束条件、限制类型

  1. import UIKit
  2. protocol MyProtocol where Self: UITableViewController {
  3. }

检验协议的一致性:
你可以使用 is 和 as 操作符来检查是否遵循某一协议或强制转化为某一类型。

  • is 操作符用来检查实例是否遵循了某个协议。
  • as? 返回一个可选值,当实例遵循协议时,返回该协议类型,否则返回 nil。
  • as 用以强制向下转型,如果强转失败,会引起运行时错误。

访问控制

五种访问控制修饰符 openpublicinternalfileprivateprivate

官方:https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html
翻译:https://learnku.com/docs/the-swift-programming-language/4.2/AccessControl/3546
参考:https://www.jianshu.com/p/97cc7b693a60