swift中有三种结构化存储类型:enum、struct、class,其中enum、struct为值类型,class是引用类型。
作为引用类型,class表达的是一个我们需要关注具体生命周期的对象;而对于值类型的enum和struct对象,我们更多的仅关注其存储的值。
枚举 enum
当我们面对需要“把一组有相关意义的值定义成一个独立的类型”这样的任务时,Swift为我们提供了一种类型安全的方式,便是enum。
Swift对枚举的能力做出了极大的扩展,有以下变化:
- 可以设置枚举的
rawValue,并且值类型不仅限于Int - 可以为枚举项绑定关联值 (associated values)
- 可以为枚举添加属性和函数
RawValue
与oc不同的是,Swift中,枚举的类型不仅限于整数类型。常见的,我们可以为枚举指定Int、String类型,或不指定rawValue类型。
Int类型
和常见的枚举使用方式一致。如果不设置rawValue的值,则会默认从0开始为每个case推导一个默认值。
enum Week: Int {
case MON = 1
case TUE
case WED
case THU
case FRI
case SAT
case SUN
}
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
}
- 不设置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中,同struct和class一样,我们也可以为enum添加属性与函数。
- 属性
Swift中允许在枚举中包含计算属性和类属性,但不能包含存储属性。
enum Week: Int {
case MON,TUE,WED,THU,FRI,SAT,SUN
var firstDay:Week { //计算属性
return .MON
}
static let lastDay: Week = .SUN
}
- 函数
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枚举要求很严格:
- 使用
@objc标记 rawValue类型必须是Int- 必须
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
以上两句命令都会触发属性观察器willSet和didSet。
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编译器会报错。
- designated init
在obj-c中,init方法是非常不安全的,没人能够保证 init 只被调用一次,也没有人保证在初始化方法调用以后实例的各个变量都完成初始化;甚至如果在初始化里使用属性进行设置的的话,还可能会造成各种问题。
在Swift中强化了designated初始化方法的地位。swift中不加修饰的init方法,都需要在方法中保证所有非Optional的实例变量被赋值初始化,而在子类中也强制(显示或隐式的)调用super的designated init初始化。
所以无论怎样被初始化的对象总是可以完成完整的初始化的。
class ClassB: ClassA {
let numB: Int
override init(num: Int) {
numB = num + 1
super.init(num: num)
}
}
tips: 对于某些我们希望子类中一定实现的 designated 初始化方法,我们可以通过添加 required 关键字进行限制,强制子类对这个方法重写实现。这样做的最大的好处是可以保证依赖于某个 designated 初始化方法的 convenience 一直可以被使用。
- convenience init
与designated init初始化方法对应的是convenience init初始化方法。
这类方法作为提供的便利方法,所有的convenience init初始化方法都必须调用类中的designated init初始化完成设置。
另外,convenience init的初始化方法是不能被子类重写或者是从子类中以super的方式被调用的。
初始化方法遵循以下两个原则:
- 初始化路径必须保证对象完全初始化,这可以通过调用本类型的
designated init初始化方法得到保证; - 子类的
designated init方法必须调用父类的designated init方法,以保证父类也完成初始化。
初始化init的过程
派生类的初始化过程分成了两个阶段:
阶段一: 从派生类到基类,自下而上让类的每一个属性都有初始值。
阶段二: 所有属性都有初始值之后,从基类到派生类,自上而下对类的每个属性进行进一步加工。
继承init
-
继承
init
默认情况下,派生类不从基类继承任何init方法。基类的init方法只在有限条件下被派生类自动继承。- 如果派生类没有定义任何
designated init,那么它将自动继承继承基类的designated init。 - 如果一个派生类继承了基类的
designated init,那么它也将自动继承基类所有的convenience init。
- 如果派生类没有定义任何
-
重载
init方法
在派生类中自定义designated init,表示我们要明确控制派生类对象的构建过程。
tips: 派生类中自定义designated init后,便不会从基类继承任何init方法;但若是我们希望继承基类的convenience init方法,则我们需要在派生类中实现基类所有的designated init。
class和struct的差异
- 同
struct不同,swift并不会为class自动生成默认的init方法。如果我们不定义它,Swift编译器会报错。 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约束的内容,完全属于这个类型的内部实现,理解为绝不对外部可见的,也可以理解为是一个类型内部“不可改变”的属性。