Swift补缺#1:swift中的结构化存储

100 阅读9分钟

swift中有三种结构化存储类型:enumstructclass,其中enumstruct为值类型,class是引用类型。
作为引用类型,class表达的是一个我们需要关注具体生命周期的对象;而对于值类型的enumstruct对象,我们更多的仅关注其存储的值。

枚举 enum

当我们面对需要“把一组有相关意义的值定义成一个独立的类型”这样的任务时,Swift为我们提供了一种类型安全的方式,便是enum。
Swift对枚举的能力做出了极大的扩展,有以下变化:

  1. 可以设置枚举的rawValue,并且值类型不仅限于Int
  2. 可以为枚举项绑定关联值 (associated values)
  3. 可以为枚举添加属性和函数

RawValue

与oc不同的是,Swift中,枚举的类型不仅限于整数类型。常见的,我们可以为枚举指定IntString类型,或不指定rawValue类型。

  1. Int类型
    和常见的枚举使用方式一致。如果不设置rawValue的值,则会默认从0开始为每个case推导一个默认值。
enum Week: Int {
    case MON = 1
    case TUE
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}
  1. String类型
enum Week: String {
    case MON = "MON"
    case TUE = "TUE"
    case WED = "WED"
    case THU = "THU"
    case FRI = "FRI"
    case SAT = "SAT"
    case SUN = "SUN"
}

不绑定默认值: 如果默认rawValue是case后面的值,也可以简写成如下的形式。

enum Week: String {
    case MON
    case TUE
    case WED
    case THU
    case FRI
    case SAT
    case SUN
}
  1. 不设置rawValue
enum Week {
    case MON,TUE,WED,THU,FRI,SAT,SUN
}

没有设置rawValue,则无法调用rawValue

Week.TUE.rawValue   // ❌error

关联值 (associated values)

在Swift中,我们可以把其他类型的值 (变量或元组) 和枚举的成员值一起存储起来, 给每一个case"绑定"不同类型的值,这额外的信息称为关联值。\

enum QueueAction {
    case append(String)
    case insert(Int, String)
    case delete(Int)
    case replace(Int, String)
}
switch action {
case .append(value):
    print("append \(value)")
case .insert(index, value):
    print("index\(index), insert\(value)")
case .delete(index):
    print("delete, index\(index)")
case .replace(index, value):
    print("index\(index), replace\(value)")
default:
    print("")
}

在使用该枚举成员时,也可以修改这个关联值。

var var1 = "value1"
_ = QueueAction.append(var1)
_ = QueueAction.replace(1, var1)
var1 = "value2"

添加属性和函数

在Swift中,同structclass一样,我们也可以为enum添加属性与函数。

  1. 属性
    Swift中允许在枚举中包含计算属性和类属性,但不能包含存储属性
enum Week: Int {
    case MON,TUE,WED,THU,FRI,SAT,SUN
    
    var firstDay:Week { //计算属性
        return .MON
    }
    static let lastDay: Week = .SUN
}
  1. 函数
    Swift中允许在枚举中包含成员函数和类函数。
enum Week: Int {
    case MON,TUE,WED,THU,FRI,SAT,SUN
    
    mutating func nextDay() {
        if self == .SUN {
            self = .MON
        }else {
            self = Week.init(rawValue: self.rawValue+ 1) !
        }
    }
}

OC中使用Swift枚举

在OC中使用Swift枚举要求很严格:

  1. 使用@objc标记
  2. rawValue类型必须是Int
  3. 必须import Foundation

Struct

相对Objective-C, Swift使用结构体Struct的比例大大增加了,其中Int、Bool以及String、Array等底层全部使用Struct来定义。在Swift中,结构体不仅可以定义成员变量(属性),还可以定义成员方法;和类比较相似,都是具有定义和使用属性,方法以及初始化器等面向对象特性。但是结构体是不具有继承性,不具备运行时强制类型转换以及引用计数等能力。

创建及初始化

swift属性分为两大类 存储属性 (stored property) 和计算属性 (computed property)

  • 存储属性 (stored property):存储属性存储在对象内部,占用该对象内存。所以只有struct和class才有存储属性,枚举则不可以有存储属性,因为关联值和原始值都放在枚举类里面,而不是枚举实例化对象。
  • 计算属性 (computed property) :只用作计算,没有真正存储到实例化对象的内存中。

所以,一个struct对象占用内存的大小只计算 stored property(存储属性) ,而不计算computed property(计算属性)。

此外,对于一些struct经常会使用的“特殊值”,除了每次我们自己定义之外,还可以在struct中定义成type property。它们不是struct对象的一部分,因此,不会增加Point对象的大小。

struct Human{
    var name: String
    var sex: String
    var age: Int

    init(name:String = "lichangan",sex:String = "male",age:Int = 18) {
        self.name = name
        self.sex = sex
        self.age = age
    }
}

如果你不创建任何init方法,Swift编译器就会为你自动创建一个,让你可以逐个初始化struct中的每一个属性。

Human(name: "paff", sex: "male", age: 18)

属性观察器

属性观察器就是用来观察属性变化的,在属性值发生变化时,属性观察器可以获取到属性的新值和老值。

struct Human{
    var name: String
    var sex: String
    var age: Int {
        willSet {
            print("willSet \(newValue)")
        }
        didSet {
            print("didSet \(oldValue)")
        }
    }
}

tips:在init方法里面调用set方法,是不会触发属性观察器的。因为在init方法中,这时还没有真正的初始化完成,swift不允许这样不安全的操作,所以不会触发属性观察器。

而若理解struct的值语义,则会明白即便是字面上我们仅修改了struct变量的某个属性,但实际执行的逻辑则是我们重新给其赋值了一个新的struct对象。

var humanA = Human(name: "paff", sex: "male", age: 18)
humanA.age = humanA.age + 1

以上两句命令都会触发属性观察器willSetdidSet

Mutating

结构体和枚举都是值类型。一般情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择变异 (mutating) 这个方法,然后方法就可以从方法内部改变它的属性;并且,它做的任何改变在方法结束时还会保留在原始结构中。

struct Human{
    var name: String
    var sex: String
    var age: Int

    mutating func setAge(_ age: Int) {
        self.age = age
    }
}

Class

作为Swift中的引用类型,class表达的是一个具有明确生命周期的对象,我们需要关注这类内容的“生死存亡”,而值类型,我们更多关注的,就真的只是它的值而已。

初始化

struct不同,swift不会为class自动生成默认的init方法。如果我们不定义它,Swift编译器会报错。

  1. designated init
    obj-c中,init方法是非常不安全的,没人能够保证 init 只被调用一次,也没有人保证在初始化方法调用以后实例的各个变量都完成初始化;甚至如果在初始化里使用属性进行设置的的话,还可能会造成各种问题。
    在Swift中强化了designated初始化方法的地位。swift中不加修饰的init方法,都需要在方法中保证所有非Optional的实例变量被赋值初始化,而在子类中也强制(显示或隐式的)调用superdesignated init初始化。
    所以无论怎样被初始化的对象总是可以完成完整的初始化的。
class ClassB: ClassA {
    let numB: Int
    override init(num: Int) {
        numB = num + 1
        super.init(num: num)
    }
}

tips: 对于某些我们希望子类中一定实现的 designated 初始化方法,我们可以通过添加 required 关键字进行限制,强制子类对这个方法重写实现。这样做的最大的好处是可以保证依赖于某个 designated 初始化方法的 convenience 一直可以被使用。

  1. convenience init
    designated init初始化方法对应的是convenience init初始化方法。
    这类方法作为提供的便利方法,所有的convenience init初始化方法都必须调用类中的designated init初始化完成设置。
    另外,convenience init的初始化方法是不能被子类重写或者是从子类中以super 的方式被调用的。

初始化方法遵循以下两个原则:

  1. 初始化路径必须保证对象完全初始化,这可以通过调用本类型的designated init初始化方法得到保证;
  2. 子类的designated init方法必须调用父类的designated init方法,以保证父类也完成初始化。

初始化init的过程

派生类的初始化过程分成了两个阶段:
阶段一: 从派生类到基类,自下而上让类的每一个属性都有初始值。
阶段二: 所有属性都有初始值之后,从基类到派生类,自上而下对类的每个属性进行进一步加工。

继承init

  1. 继承init
    默认情况下,派生类不从基类继承任何init方法。基类的init方法只在有限条件下被派生类自动继承。

    • 如果派生类没有定义任何designated init,那么它将自动继承继承基类的designated init
    • 如果一个派生类继承了基类的designated init,那么它也将自动继承基类所有的convenience init
  2. 重载init方法
    在派生类中自定义designated init,表示我们要明确控制派生类对象的构建过程。
    tips: 派生类中自定义designated init后,便不会从基类继承任何init方法;但若是我们希望继承基类的convenience init方法,则我们需要在派生类中实现基类所有的designated init

classstruct的差异

  1. struct不同,swift并不会为class自动生成默认的init方法。如果我们不定义它,Swift编译器会报错。
  2. class是Swift中的引用类型,引用类型关注的是对象本身。

访问控制

Open > Public > Internal > Fileprivate > private

public

tips:module就是一个可以被分发的swift代码单位,一个import进来的程序库是一个module。

public的作用,就是开放一个类型或方法,对所有的module都可见。
可见的含义是只开放给外部使用。对于类型来说,不允许你派生;对于方法而言,也不允许你重写。

Open

public修饰的类型或方法不允许派生或重写,而当我们需要对外允许派生时,便需要更开放的权限:open

open的作用,就是开放一个类型或方法,对所有的module都可见,并且允许派生或重写。

internal

internal是Swift默认的访问权限,含义是仅针对同一module内开放。绝大部分时候,我们都在用这种类型的权限。

fileprivate

fileprivate是比internal更严格的限制,fileprivate的作用是将访问权限限制在文件内,让我们只可以在单个文件内访问被修饰的成员。
即使在相同module内,也仅在当前文件内使用被fileprivate修饰的类型或方法。

private

private是Swift中最严格的访问权限,被其约束的内容,只能在其定义的语法范围内访问。

对于一个类型来说,被private约束的内容,完全属于这个类型的内部实现,理解为绝不对外部可见的,也可以理解为是一个类型内部“不可改变”的属性。