Swift 枚举
枚举简单的说也是一种数据类型,只不过是这种数据类型只包含自定义的特定数据,它是一组有共同特性的数据的集合。
Swift 的枚举类似于 Objective C 和 C 的结构,枚举的功能为:
1.它声明在类中,可以通过实例化类来访问它的值。
2.枚举也可以定义构造函数(initializers)来提供一个初始成员值;可以在原始的实现基础上扩展它们的功能。
3.可以遵守协议(protocols)来提供标准的功能。
语法
Swift 中使用 enum 关键词来创建枚举并且把它们的整个定义放在一对大括号内:
enum enumname {
// 枚举定义放在这里
}
// 定义枚举
enum DaysofaWeek {
case Sunday
case Monday
case TUESDAY
case WEDNESDAY
case THURSDAY
case FRIDAY
case Saturday
}
var weekDay = DaysofaWeek.THURSDAY
weekDay = .THURSDAY
switch weekDay
{
case .Sunday:
print("星期天")
case .Monday:
print("星期一")
case .TUESDAY:
print("星期二")
case .WEDNESDAY:
print("星期三")
case .THURSDAY:
print("星期四")
case .FRIDAY:
print("星期五")
case .Saturday:
print("星期六")
}
以上程序执行输出结果为:
星期四
枚举中定义的值(如 Sunday,Monday,……和Saturday)是这个枚举的成员值(或成员)。case关键词表示一行新的成员值将被定义。
注意: 和 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。在上面的DaysofaWeek例子中,Sunday,Monday,……和Saturday不会隐式地赋值为0,1,……和6。相反,这些枚举成员本身就有完备的值,这些值是已经明确定义好的DaysofaWeek类型。
var weekDay = DaysofaWeek.THURSDAY
weekDay的类型可以在它被DaysofaWeek的一个可能值初始化时推断出来。一旦weekDay被声明为一个DaysofaWeek,你可以使用一个缩写语法(.)将其设置为另一个DaysofaWeek的值:
var weekDay = .THURSDAY
当weekDay的类型已知时,再次为其赋值可以省略枚举名。使用显式类型的枚举值可以让代码具有更好的可读性。
枚举可分为相关值与原始值。
相关值与原始值的区别
相关值
以下实例中我们定义一个名为 Student 的枚举类型,它可以是 Name 的一个字符串(String),或者是 Mark 的一个相关值(Int,Int,Int)。
enum Student{
case Name(String)
case Mark(Int,Int,Int)
}
var studDetails = Student.Name("Runoob")
var studMarks = Student.Mark(98,97,95)
switch studMarks {
case .Name(let studName):
print("学生的名字是: \(studName)。")
case .Mark(let Mark1, let Mark2, let Mark3):
print("学生的成绩是: \(Mark1),\(Mark2),\(Mark3)。")
}
以上程序执行输出结果为:
学生的成绩是: 98,97,95。
原始值
原始值可以是字符串,字符,或者任何整型值或浮点型值。每个原始值在它的枚举声明中必须是唯一的。
在原始值为整数的枚举时,不需要显式的为每一个成员赋值,Swift会自动为你赋值。
例如,当使用整数作为原始值时,隐式赋值的值依次递增1。如果第一个值没有被赋初值,将会被自动置为0。
enum Month: Int {
case January = 1, February, March, April, May, June, July, August, September, October, November, December
}
let yearMonth = Month.May.rawValue
print("数字月份为: \(yearMonth)。")
以上程序执行输出结果为:
数字月份为: 5。
Swift 结构体
Swift 结构体是构建代码所用的一种通用且灵活的构造体。
我们可以为结构体定义属性(常量、变量)和添加方法,从而扩展结构体的功能。
与 C 和 Objective C 不同的是:
结构体不需要包含实现文件和接口。
结构体允许我们创建一个单一文件,且系统会自动生成面向其它代码的外部接口。
结构体总是通过被复制的方式在代码中传递,因此它的值是不可修改的。
语法
我们通过关键字 struct 来定义结构体:
struct nameStruct {
Definition 1
Definition 2
……
Definition N
}
实例
我们定义一个名为 MarkStruct 的结构体 ,结构体的属性为学生三个科目的分数,数据类型为 Int:
struct MarkStruct{
var mark1: Int
var mark2: Int
var mark3: Int
}
我们可以通过结构体名来访问结构体成员。
结构体实例化使用 let 关键字:
import Cocoa
struct studentMarks {
var mark1 = 100
var mark2 = 78
var mark3 = 98
}
let marks = studentMarks()
print("Mark1 是 \(marks.mark1)")
print("Mark2 是 \(marks.mark2)")
print("Mark3 是 \(marks.mark3)")
以上程序执行输出结果为:
Mark1 是 100
Mark2 是 78
Mark3 是 98
实例中,我们通过结构体名 'studentMarks' 访问学生的成绩。结构体成员初始化为mark1, mark2, mark3,数据类型为整型。
然后我们通过使用 let 关键字将结构体 studentMarks() 实例化并传递给 marks。
最后我们就通过 . 号来访问结构体成员的值。
以下实例化通过结构体实例化时传值并克隆一个结构体:
import Cocoa
struct MarksStruct {
var mark: Int
init(mark: Int) {
self.mark = mark
}
}
var aStruct = MarksStruct(mark: 98)
var bStruct = aStruct // aStruct 和 bStruct 是使用相同值的结构体!
bStruct.mark = 97
print(aStruct.mark) // 98
print(bStruct.mark) // 97
以上程序执行输出结果为:
98
97
结构体应用
在你的代码中,你可以使用结构体来定义你的自定义数据类型。
结构体实例总是通过值传递来定义你的自定义数据类型。
按照通用的准则,当符合一条或多条以下条件时,请考虑构建结构体:
结构体的主要目的是用来封装少量相关简单数据值。
有理由预计一个结构体实例在赋值或传递时,封装的数据将会被拷贝而不是被引用。
任何在结构体中储存的值类型属性,也将会被拷贝,而不是被引用。
结构体不需要去继承另一个已存在类型的属性或者行为。
举例来说,以下情境中适合使用结构体:
几何形状的大小,封装一个width属性和height属性,两者均为Double类型。
一定范围内的路径,封装一个start属性和length属性,两者均为Int类型。
三维坐标系内一点,封装x,y和z属性,三者均为Double类型。
结构体实例是通过值传递而不是通过引用传递。
import Cocoa
struct markStruct{
var mark1: Int
var mark2: Int
var mark3: Int
init(mark1: Int, mark2: Int, mark3: Int){
self.mark1 = mark1
self.mark2 = mark2
self.mark3 = mark3
}
}
print("优异成绩:")
var marks = markStruct(mark1: 98, mark2: 96, mark3:100)
print(marks.mark1)
print(marks.mark2)
print(marks.mark3)
print("糟糕成绩:")
var fail = markStruct(mark1: 34, mark2: 42, mark3: 13)
print(fail.mark1)
print(fail.mark2)
print(fail.mark3)
以上程序执行输出结果为:
优异成绩:
98
96
100
糟糕成绩:
34
42
13
以上实例中我们定义了结构体 markStruct,三个成员属性:mark1, mark2 和 mark3。结构体内使用成员属性使用 self 关键字。
从实例中我们可以很好的理解到结构体实例是通过值传递的。
Swift 类
Swift 类是构建代码所用的一种通用且灵活的构造体。
我们可以为类定义属性(常量、变量)和方法。
与其他编程语言所不同的是,Swift 并不要求你为自定义类去创建独立的接口和实现文件。你所要做的是在一个单一文件中定义一个类,系统会自动生成面向其它代码的外部接口。
类和结构体对比
Swift 中类和结构体有很多共同点。共同处在于:
- 定义属性用于存储值
- 定义方法用于提供功能
- 定义附属脚本用于访问值
- 定义构造器用于生成初始化值
- 通过扩展以增加默认实现的功能
- 符合协议以对某类提供标准功能
与结构体相比,类还有如下的附加功能:
- 继承允许一个类继承另一个类的特征
- 类型转换允许在运行时检查和解释一个类实例的类型
- 解构器允许一个类实例释放任何其所被分配的资源
- 引用计数允许对一个类的多次引用
语法:
class classname {
Definition 1
Definition 2
……
Definition N
}
类定义
class student{
var studname: String
var mark: Int
var mark2: Int
}
实例化类:
let studrecord = student()
实例
import Cocoa
class MarksStruct {
var mark: Int
init(mark: Int) {
self.mark = mark
}
}
class studentMarks {
var mark = 300
}
let marks = studentMarks()
print("成绩为 \(marks.mark)")
以上程序执行输出结果为:
成绩为 300
以上程序执行输出结果为:
成绩为 300
作为引用类型访问类属性
类的属性可以通过 . 来访问。格式为:实例化类名.属性名:
import Cocoa
class MarksStruct {
var mark: Int
init(mark: Int) {
self.mark = mark
}
}
class studentMarks {
var mark1 = 300
var mark2 = 400
var mark3 = 900
}
let marks = studentMarks()
print("Mark1 is \(marks.mark1)")
print("Mark2 is \(marks.mark2)")
print("Mark3 is \(marks.mark3)")
以上程序执行输出结果为:
Mark1 is 300
Mark2 is 400
Mark3 is 900
恒等运算符
因为类是引用类型,有可能有多个常量和变量在后台同时引用某一个类实例。
为了能够判定两个常量或者变量是否引用同一个类实例,Swift 内建了两个恒等运算符:
| 恒等运算符 | 不恒等运算符 |
| 运算符为:=== | 运算符为:!== |
| 如果两个常量或者变量引用同一个类实例则返回 true | 如果两个常量或者变量引用不同一个类实例则返回 true |
实例
import Cocoa
class SampleClass: Equatable {
let myProperty: String
init(s: String) {
myProperty = s
}
}
func ==(lhs: SampleClass, rhs: SampleClass) -> Bool {
return lhs.myProperty == rhs.myProperty
}
let spClass1 = SampleClass(s: "Hello")
let spClass2 = SampleClass(s: "Hello")
if spClass1 === spClass2 {// false
print("引用相同的类实例 \(spClass1)")
}
if spClass1 !== spClass2 {// true
print("引用不相同的类实例 \(spClass2)")
}
以上程序执行输出结果为:
引用不相同的类实例 SampleClass
Swift 属性
Swift 属性将值跟特定的类、结构或枚举关联。
属性可分为存储属性和计算属性:
存储属性 计算属性
存储常量或变量作为实例的一部分 计算(而不是存储)一个值
用于类和结构体 用于类、结构体和枚举
存储属性和计算属性通常用于特定类型的实例。
属性也可以直接用于类型本身,这种属性称为类型属性。
另外,还可以定义属性观察器来监控属性值的变化,以此来触发一个自定义的操作。属性观察器可以添加到自己写的存储属性上,也可以添加到从父类继承的属性上。
存储属性
简单来说,一个存储属性就是存储在特定类或结构体的实例里的一个常量或变量。
存储属性可以是变量存储属性(用关键字var定义),也可以是常量存储属性(用关键字let定义)。
可以在定义存储属性的时候指定默认值
也可以在构造过程中设置或修改存储属性的值,甚至修改常量存储属性的值
import Cocoa
struct Number
{
var digits: Int
let pi = 3.1415
}
var n = Number(digits: 12345)
n.digits = 67
print("\(n.digits)")
print("\(n.pi)")
以上程序执行输出结果为:
67
3.1415
考虑以下代码:
let pi = 3.1415
代码中 pi 在定义存储属性的时候指定默认值(pi = 3.1415),所以不管你什么时候实例化结构体,它都不会改变。
如果你定义的是一个常量存储属性,如果尝试修改它就会报错,如下所示:
import Cocoa
struct Number
{
var digits: Int
let numbers = 3.1415
}
var n = Number(digits: 12345)
n.digits = 67
print("\(n.digits)")
print("\(n.numbers)")
n.numbers = 8.7
以上程序,执行会报错,错误如下所示:
error: cannot assign to property: 'numbers' is a 'let' constant
n.numbers = 8.7
意思为 'numbers' 是一个常量,你不能修改它。
延迟存储属性
延迟存储属性是指当第一次被调用的时候才会计算其初始值的属性。
在属性声明前使用 lazy 来标示一个延迟存储属性。
注意:
必须将延迟存储属性声明成变量(使用var关键字),因为属性的值在实例构造完成之前可能无法得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延迟属性。
延迟存储属性一般用于:
延迟对象的创建。
当属性的值依赖于其他未知类
import Cocoa
class sample {
lazy var no = number() // `var` 关键字是必须的
}
class number {
var name = "Runoob Swift 教程"
}
var firstsample = sample()
print(firstsample.no.name)
以上程序执行输出结果为:
Runoob Swift 教程
实例化变量
如果您有过 Objective-C 经验,应该知道Objective-C 为类实例存储值和引用提供两种方法。对于属性来说,也可以使用实例变量作为属性值的后端存储。
Swift 编程语言中把这些理论统一用属性来实现。Swift 中的属性没有对应的实例变量,属性的后端存储也无法直接访问。这就避免了不同场景下访问方式的困扰,同时也将属性的定义简化成一个语句。
一个类型中属性的全部信息——包括命名、类型和内存管理特征——都在唯一一个地方(类型定义中)定义。
计算属性
除存储属性外,类、结构体和枚举可以定义计算属性,计算属性不直接存储值,而是提供一个 getter 来获取值,一个可选的 setter 来间接设置其他属性或变量的值。
import Cocoa
class sample {
var no1 = 0.0, no2 = 0.0
var length = 300.0, breadth = 150.0
var middle: (Double, Double) {
get{
return (length / 2, breadth / 2)
}
set(axis){
no1 = axis.0 - (length / 2)
no2 = axis.1 - (breadth / 2)
}
}
}
var result = sample()
print(result.middle)
result.middle = (0.0, 10.0)
print(result.no1)
print(result.no2)
以上程序执行输出结果为:
(150.0, 75.0)
-150.0
-65.0
如果计算属性的 setter 没有定义表示新值的参数名,则可以使用默认名称 newValue。
只读计算属性
只有 getter 没有 setter 的计算属性就是只读计算属性。
只读计算属性总是返回一个值,可以通过点(.)运算符访问,但不能设置新的值。
import Cocoa
class film {
var head = ""
var duration = 0.0
var metaInfo: [String:String] {
return [
"head": self.head,
"duration":"\(self.duration)"
]
}
}
var movie = film()
movie.head = "Swift 属性"
movie.duration = 3.09
print(movie.metaInfo["head"]!)
print(movie.metaInfo["duration"]!)
以上程序执行输出结果为:
Swift 属性
3.09
注意:
必须使用var关键字定义计算属性,包括只读计算属性,因为它们的值不是固定的。let关键字只用来声明常量属性,表示初始化后再也无法修改的值。
属性观察器
属性观察器监控和响应属性值的变化,每次属性被设置值的时候都会调用属性观察器,甚至新的值和现在的值相同的时候也不例外。
可以为除了延迟存储属性之外的其他存储属性添加属性观察器,也可以通过重载属性的方式为继承的属性(包括存储属性和计算属性)添加属性观察器。
注意:
不需要为无法重载的计算属性添加属性观察器,因为可以通过 setter 直接监控和响应值的变化。
可以为属性添加如下的一个或全部观察器:
willSet在设置新的值之前调用
didSet在新的值被设置之后立即调用
willSet和didSet观察器在属性初始化过程中不会被调用
import Cocoa
class Samplepgm {
var counter: Int = 0{
willSet(newTotal){
print("计数器: \(newTotal)")
}
didSet{
if counter > oldValue {
print("新增数 \(counter - oldValue)")
}
}
}
}
let NewCounter = Samplepgm()
NewCounter.counter = 100
NewCounter.counter = 800
以上程序执行输出结果为:
计数器: 100
新增数 100
计数器: 800
新增数 700
全局变量和局部变量
计算属性和属性观察器所描述的模式也可以用于全局变量和局部变量。
局部变量 全局变量
在函数、方法或闭包内部定义的变量。 函数、方法、闭包或任何类型之外定义的变量。
用于存储和检索值。 用于存储和检索值。
存储属性用于获取和设置值。 存储属性用于获取和设置值。
也用于计算属性。 也用于计算属性。
类型属性
类型属性是作为类型定义的一部分写在类型最外层的花括号({})内。
使用关键字 static 来定义值类型的类型属性,关键字 class 来为类定义类型属性。
struct Structname {
static var storedTypeProperty = " "
static var computedTypeProperty: Int {
// 这里返回一个 Int 值
}
}
enum Enumname {
static var storedTypeProperty = " "
static var computedTypeProperty: Int {
// 这里返回一个 Int 值
}
}
class Classname {
class var computedTypeProperty: Int {
// 这里返回一个 Int 值
}
}
注意:
例子中的计算型类型属性是只读的,但也可以定义可读可写的计算型类型属性,跟实例计算属性的语法类似。
获取和设置类型属性的值
类似于实例的属性,类型属性的访问也是通过点运算符(.)来进行。但是,类型属性是通过类型本身来获取和设置,而不是通过实例。实例如下:
import Cocoa
struct StudMarks {
static let markCount = 97
static var totalCount = 0
var InternalMarks: Int = 0 {
didSet {
if InternalMarks > StudMarks.markCount {
InternalMarks = StudMarks.markCount
}
if InternalMarks > StudMarks.totalCount {
StudMarks.totalCount = InternalMarks
}
}
}
}
var stud1Mark1 = StudMarks()
var stud1Mark2 = StudMarks()
stud1Mark1.InternalMarks = 98
print(stud1Mark1.InternalMarks)
stud1Mark2.InternalMarks = 87
print(stud1Mark2.InternalMarks)
以上程序执行输出结果为:
97
87
Swift 方法
Swift 方法是与某些特定类型相关联的函数
在 Objective-C 中,类是唯一能定义方法的类型。但在 Swift 中,你不仅能选择是否要定义一个类/结构体/枚举,还能灵活的在你创建的类型(类/结构体/枚举)上定义方法。
实例方法
在 Swift 语言中,实例方法是属于某个特定类、结构体或者枚举类型实例的方法。
实例方法提供以下方法:
可以访问和修改实例属性
提供与实例目的相关的功能
实例方法要写在它所属的类型的前后大括号({})之间。
实例方法能够隐式访问它所属类型的所有的其他实例方法和属性。
实例方法只能被它所属的类的某个特定实例调用。
实例方法不能脱离于现存的实例而被调用。
语法
func funcname(Parameters) -> returntype
{
Statement1
Statement2
……
Statement N
return parameters
}
实例
import Cocoa
class Counter {
var count = 0
func increment() {
count += 1
}
func incrementBy(amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
// 初始计数值是0
let counter = Counter()
// 计数值现在是1
counter.increment()
// 计数值现在是6
counter.incrementBy(amount: 5)
print(counter.count)
// 计数值现在是0
counter.reset()
print(counter.count)
以上程序执行输出结果为:
6
0
Counter类定义了三个实例方法:
increment 让计数器按 1 递增;
incrementBy(amount: Int) 让计数器按一个指定的整数值递增;
reset 将计数器重置为0。
Counter 这个类还声明了一个可变属性 count,用它来保持对当前计数器值的追踪。
方法的局部参数名称和外部参数名称
Swift 函数参数可以同时有一个局部名称(在函数体内部使用)和一个外部名称(在调用函数时使用
Swift 中的方法和 Objective-C 中的方法极其相似。像在 Objective-C 中一样,Swift 中方法的名称通常用一个介词指向方法的第一个参数,比如:with,for,by等等。
Swift 默认仅给方法的第一个参数名称一个局部参数名称;默认同时给第二个和后续的参数名称为全局参数名称。
以下实例中 'no1' 在swift中声明为局部参数名称。'no2' 用于全局的声明并通过外部程序访问。
import Cocoa
class division {
var count: Int = 0
func incrementBy(no1: Int, no2: Int) {
count = no1 / no2
print(count)
}
}
let counter = division()
counter.incrementBy(no1: 1800, no2: 3)
counter.incrementBy(no1: 1600, no2: 5)
counter.incrementBy(no1: 11000, no2: 3)
以上程序执行输出结果为:
600
320
3666
是否提供外部名称设置
我们强制在第一个参数添加外部名称把这个局部名称当作外部名称使用(Swift 2.0前是使用 # 号)。
相反,我们呢也可以使用下划线(_)设置第二个及后续的参数不提供一个外部名称。
import Cocoa
class multiplication {
var count: Int = 0
func incrementBy(first no1: Int, no2: Int) {
count = no1 * no2
print(count)
}
}
let counter = multiplication()
counter.incrementBy(first: 800, no2: 3)
counter.incrementBy(first: 100, no2: 5)
counter.incrementBy(first: 15000, no2: 3)
以上程序执行输出结果为:
2400
500
45000
self 属性
类型的每一个实例都有一个隐含属性叫做self,self 完全等同于该实例本身。
你可以在一个实例的实例方法中使用这个隐含的self属性来引用当前实例。
import Cocoa
class calculations {
let a: Int
let b: Int
let res: Int
init(a: Int, b: Int) {
self.a = a
self.b = b
res = a + b
print("Self 内: \(res)")
}
func tot(c: Int) -> Int {
return res - c
}
func result() {
print("结果为: \(tot(c: 20))")
print("结果为: \(tot(c: 50))")
}
}
let pri = calculations(a: 600, b: 300)
let sum = calculations(a: 1200, b: 300)
pri.result()
sum.result()
以上程序执行输出结果为:
Self 内: 900
Self 内: 1500
结果为: 880
结果为: 850
结果为: 1480
结果为: 1450
在实例方法中修改值类型
Swift 语言中结构体和枚举是值类型。一般情况下,值类型的属性不能在它的实例方法中被修改。
但是,如果你确实需要在某个具体的方法中修改结构体或者枚举的属性,你可以选择变异(mutating)这个方法,然后方法就可以从方法内部改变它的属性;并且它做的任何改变在方法结束时还会保留在原始结构中。
方法还可以给它隐含的self属性赋值一个全新的实例,这个新实例在方法结束后将替换原来的实例。
import Cocoa
struct area {
var length = 1
var breadth = 1
func area() -> Int {
return length * breadth
}
mutating func scaleBy(res: Int) {
length *= res
breadth *= res
print(length)
print(breadth)
}
}
var val = area(length: 3, breadth: 5)
val.scaleBy(res: 3)
val.scaleBy(res: 30)
val.scaleBy(res: 300)
以上程序执行输出结果为:
9
15
270
450
81000
135000
在可变方法中给 self 赋值
可变方法能够赋给隐含属性 self 一个全新的实例。
import Cocoa
struct area {
var length = 1
var breadth = 1
func area() -> Int {
return length * breadth
}
mutating func scaleBy(res: Int) {
self.length *= res
self.breadth *= res
print(length)
print(breadth)
}
}
var val = area(length: 3, breadth: 5)
val.scaleBy(res: 13)
以上程序执行输出结果为:
39
65
类型方法
实例方法是被类型的某个实例调用的方法,你也可以定义类型本身调用的方法,这种方法就叫做类型方法。
声明结构体和枚举的类型方法,在方法的func关键字之前加上关键字static。类可能会用关键字class来允许子类重写父类的实现方法。
类型方法和实例方法一样用点号(.)语法调用。
import Cocoa
class Math
{
class func abs(number: Int) -> Int
{
if number < 0
{
return (-number)
}
else
{
return number
}
}
}
struct absno
{
static func abs(number: Int) -> Int
{
if number < 0
{
return (-number)
}
else
{
return number
}
}
}
let no = Math.abs(number: -35)
let num = absno.abs(number: -5)
print(no)
print(num)
以上程序执行输出结果为:
35
5
Swift 继承
继承我们可以理解为一个类获取了另外一个类的方法和属性。
当一个类继承其它类时,继承类叫子类,被继承类叫超类(或父类)
在 Swift 中,类可以调用和访问超类的方法,属性和下标脚本,并且可以重写它们。
我们也可以为类中继承来的属性添加属性观察器。
基类
没有继承其它类的类,称之为基类(Base Class)。
以下实例中我们定义了基类 StudDetails ,描述了学生(stname)及其各科成绩的分数(mark1、mark2、mark3):
class StudDetails {
var stname: String!
var mark1: Int!
var mark2: Int!
var mark3: Int!
init(stname: String, mark1: Int, mark2: Int, mark3: Int) {
self.stname = stname
self.mark1 = mark1
self.mark2 = mark2
self.mark3 = mark3
}
}
let stname = "swift"
let mark1 = 98
let mark2 = 89
let mark3 = 76
let sds = StudDetails(stname:stname, mark1:mark1, mark2:mark2, mark3:mark3);
print(sds.stname)
print(sds.mark1)
print(sds.mark2)
print(sds.mark3)
以上程序执行输出结果为:
swift
98
89
76
子类
子类指的是在一个已有类的基础上创建一个新的类。
为了指明某个类的超类,将超类名写在子类名的后面,用冒号(:)分隔,语法格式如下
class SomeClass: SomeSuperclass {
// 类的定义
}
实例
以下实例中我们定义了超类 StudDetails,然后使用子类 Tom 继承它:
class StudDetails
{
var mark1: Int;
var mark2: Int;
init(stm1:Int, results stm2:Int)
{
mark1 = stm1;
mark2 = stm2;
}
func show()
{
print("Mark1:\(self.mark1), Mark2:\(self.mark2)")
}
}
class Tom : StudDetails
{
init()
{
super.init(stm1: 93, results: 89)
}
}
let tom = Tom()
tom.show()
以上程序执行输出结果为:
Mark1:93, Mark2:89
重写(Overriding)
子类可以通过继承来的实例方法,类方法,实例属性,或下标脚本来实现自己的定制功能,我们把这种行为叫重写(overriding)。
我们可以使用 override 关键字来实现重写。
访问超类的方法、属性及下标脚本
你可以通过使用super前缀来访问超类的方法,属性或下标脚本。
重写 访问方法,属性,下标脚本
方法 super.somemethod()
属性 super.someProperty()
下标脚本 super[someIndex]
重写方法和属性
重写方法
在我们的子类中我们可以使用 override 关键字来重写超类的方法。
以下实例中我们重写了 show() 方法:
class SuperClass {
func show() {
print("这是超类 SuperClass")
}
}
class SubClass: SuperClass {
override func show() {
print("这是子类 SubClass")
}
}
let superClass = SuperClass()
superClass.show()
let subClass = SubClass()
subClass.show()
以上程序执行输出结果为:
这是超类 SuperClass
这是子类 SubClass
重写属性
你可以提供定制的 getter(或 setter)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。
子类并不知道继承来的属性是存储型的还是计算型的,它只知道继承来的属性会有一个名字和类型。所以你在重写一个属性时,必需将它的名字和类型都写出来。
注意点:
如果你在重写属性中提供了 setter,那么你也一定要提供 getter。
如果你不想在重写版本中的 getter 里修改继承来的属性值,你可以直接通过super.someProperty来返回继承来的值,其中someProperty是你要重写的属性的名字。
以下实例我们定义了超类 Circle 及子类 Rectangle, 在 Rectangle 类中我们重写属性 area:
class Circle {
var radius = 12.5
var area: String {
return "矩形半径 \(radius) "
}
}
// 继承超类 Circle
class Rectangle: Circle {
var print = 7
override var area: String {
return super.area + " ,但现在被重写为 \(print)"
}
}
let rect = Rectangle()
rect.radius = 25.0
rect.print = 3
print("Radius \(rect.area)")
以上程序执行输出结果为:
Radius 矩形半径 25.0 ,但现在被重写为 3
重写属性观察器
你可以在属性重写中为一个继承来的属性添加属性观察器。这样一来,当继承来的属性值发生改变时,你就会监测到。
注意:你不可以为继承来的常量存储型属性或继承来的只读计算型属性添加属性观察器。
class Circle {
var radius = 12.5
var area: String {
return "矩形半径为 \(radius) "
}
}
class Rectangle: Circle {
var print = 7
override var area: String {
return super.area + " ,但现在被重写为 \(print)"
}
}
let rect = Rectangle()
rect.radius = 25.0
rect.print = 3
print("半径: \(rect.area)")
class Square: Rectangle {
override var radius: Double {
didSet {
print = Int(radius/5.0)+1
}
}
}
let sq = Square()
sq.radius = 100.0
print("半径: \(sq.area)")
半径: 矩形半径为 25.0 ,但现在被重写为 3
半径: 矩形半径为 100.0 ,但现在被重写为 21
防止重写
我们可以使用 final 关键字防止它们被重写。
如果你重写了final方法,属性或下标脚本,在编译时会报错。
你可以通过在关键字class前添加final特性(final class)来将整个类标记为 final 的,这样的类是不可被继承的,否则会报编译错误。
final class Circle {
final var radius = 12.5
var area: String {
return "矩形半径为 \(radius) "
}
}
class Rectangle: Circle {
var print = 7
override var area: String {
return super.area + " ,但现在被重写为 \(print)"
}
}
let rect = Rectangle()
rect.radius = 25.0
rect.print = 3
print("半径: \(rect.area)")
class Square: Rectangle {
override var radius: Double {
didSet {
print = Int(radius/5.0)+1
}
}
}
let sq = Square()
sq.radius = 100.0
print("半径: \(sq.area)")
由于以上实例使用了 final 关键字不允许重写,所以执行会报错:
error: var overrides a 'final' var
override var area: String {
^
note: overridden declaration is here
var area: String {
^
error: var overrides a 'final' var
override var radius: Double {
^
note: overridden declaration is here
final var radius = 12.5
^
error: inheritance from a final class 'Circle'
class Rectangle: Circle {
^
Swift 构造过程
构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个属性设置初始值和为其执行必要的准备和初始化任务。
Swift 构造函数使用 init() 方法。
与 Objective-C 中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
类实例也可以通过定义析构器(deinitializer)在类实例释放之前执行清理内存的工作。
存储型属性的初始赋值
类和结构体在实例创建时,必须为所有存储型属性设置合适的初始值。
存储属性在构造器中赋值时,它们的值是被直接设置的,不会触发任何属性观测器。
存储属性在构造器中赋值流程:
创建初始值。
在属性定义中指定默认属性值。
初始化实例,并调用 init() 方法。
构造器
构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字init命名。
语法
init()
{
// 实例化后执行的代码
}
实例
以下结构体定义了一个不带参数的构造器 init,并在里面将存储型属性 length 和 breadth 的值初始化为 6 和 12:
struct rectangle {
var length: Double
var breadth: Double
init() {
length = 6
breadth = 12
}
}
var area = rectangle()
print("矩形面积为 (area.length*area.breadth)")
以上程序执行输出结果为:
矩形面积为 72.0
默认属性值
我们可以在构造器中为存储型属性设置初始值;同样,也可以在属性声明时为其设置默认值。
使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型。
以下实例我们在属性声明时为其设置默认值:
struct rectangle {
// 设置默认值
var length = 6
var breadth = 12
}
var area = rectangle()
print("矩形的面积为 \(area.length*area.breadth)")
以上程序执行输出结果为:
矩形面积为 72
构造参数
你可以在定义构造器 init() 时提供构造参数,如下所示:
struct Rectangle {
var length: Double
var breadth: Double
var area: Double
init(fromLength length: Double, fromBreadth breadth: Double) {
self.length = length
self.breadth = breadth
area = length * breadth
}
init(fromLeng leng: Double, fromBread bread: Double) {
self.length = leng
self.breadth = bread
area = leng * bread
}
}
let ar = Rectangle(fromLength: 6, fromBreadth: 12)
print("面积为: \(ar.area)")
let are = Rectangle(fromLeng: 36, fromBread: 12)
print("面积为: \(are.area)")
以上程序执行输出结果为:
面积为: 72.0
面积为: 432.0
内部和外部参数名
跟函数和方法参数相同,构造参数也存在一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。
然而,构造器并不像函数和方法那样在括号前有一个可辨别的名字。所以在调用构造器时,主要通过构造器中的参数名和类型来确定需要调用的构造器。
如果你在定义构造器时没有提供参数的外部名字,Swift 会为每个构造器的参数自动生成一个跟内部名字相同的外部名。
struct Color {
let red, green, blue: Double
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
init(white: Double) {
red = white
green = white
blue = white
}
}
// 创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
print("red 值为: \(magenta.red)")
print("green 值为: \(magenta.green)")
print("blue 值为: \(magenta.blue)")
// 创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器
let halfGray = Color(white: 0.5)
print("red 值为: \(halfGray.red)")
print("green 值为: \(halfGray.green)")
print("blue 值为: \(halfGray.blue)")
以上程序执行输出结果为:
red 值为: 1.0
green 值为: 0.0
blue 值为: 1.0
red 值为: 0.5
green 值为: 0.5
blue 值为: 0.5
没有外部名称参数
如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线_来显示描述它的外部名。
struct Rectangle {
var length: Double
init(frombreadth breadth: Double) {
length = breadth * 10
}
init(frombre bre: Double) {
length = bre * 30
}
//不提供外部名字
init(_ area: Double) {
length = area
}
}
// 调用不提供外部名字
let rectarea = Rectangle(180.0)
print("面积为: \(rectarea.length)")
// 调用不提供外部名字
let rearea = Rectangle(370.0)
print("面积为: \(rearea.length)")
// 调用不提供外部名字
let recarea = Rectangle(110.0)
print("面积为: \(recarea.length)")
以上程序执行输出结果为:
面积为: 180.0
面积为: 370.0
面积为: 110.0
可选属性类型
如果你定制的类型包含一个逻辑上允许取值为空的存储型属性,你都需要将它定义为可选类型optional type(可选属性类型)。
当存储属性声明为可选时,将自动初始化为空 nil。
struct Rectangle {
var length: Double?
init(frombreadth breadth: Double) {
length = breadth * 10
}
init(frombre bre: Double) {
length = bre * 30
}
init(_ area: Double) {
length = area
}
}
let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")
let rearea = Rectangle(370.0)
print("面积为:\(rearea.length)")
let recarea = Rectangle(110.0)
print("面积为:\(recarea.length)")
以上程序执行输出结果为:
面积为:Optional(180.0)
面积为:Optional(370.0)
面积为:Optional(110.0)
构造过程中修改常量属性
只要在构造过程结束前常量的值能确定,你可以在构造过程中的任意时间点修改常量属性的值。
对某个类实例来说,它的常量属性只能在定义它的类的构造过程中修改;不能在子类中修改。
尽管 length 属性现在是常量,我们仍然可以在其类的构造器中设置它的值:
struct Rectangle {
let length: Double?
init(frombreadth breadth: Double) {
length = breadth * 10
}
init(frombre bre: Double) {
length = bre * 30
}
init(_ area: Double) {
length = area
}
}
let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")
let rearea = Rectangle(370.0)
print("面积为:\(rearea.length)")
let recarea = Rectangle(110.0)
print("面积为:\(recarea.length)")
以上程序执行输出结果为:
面积为:Optional(180.0)
面积为:Optional(370.0)
面积为:Optional(110.0)
默认构造器
默认构造器将简单的创建一个所有属性值都设置为默认值的实例:
以下实例中,ShoppingListItem类中的所有属性都有默认值,且它是没有父类的基类,它将自动获得一个可以为所有属性设置默认值的默认构造器
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
print("名字为: \(item.name)")
print("数理为: \(item.quantity)")
print("是否付款: \(item.purchased)")
以上程序执行输出结果为:
名字为: nil
数理为: 1
是否付款: false
结构体的逐一成员构造器
如果结构体对所有存储型属性提供了默认值且自身没有提供定制的构造器,它们能自动获得一个逐一成员构造器。
我们在调用逐一成员构造器时,通过与成员属性名相同的参数名进行传值来完成对成员属性的初始赋值。
下面例子中定义了一个结构体 Rectangle,它包含两个属性 length 和 breadth。Swift 可以根据这两个属性的初始赋值100.0 、200.0自动推导出它们的类型Double。
struct Rectangle {
var length = 100.0, breadth = 200.0
}
let area = Rectangle(length: 24.0, breadth: 32.0)
print("矩形的面积: \(area.length)")
print("矩形的面积: \(area.breadth)")
由于这两个存储型属性都有默认值,结构体 Rectangle 自动获得了一个逐一成员构造器 init(width:height:)。 你可以用它来为 Rectangle 创建新的实例。
以上程序执行输出结果为:
矩形的面积: 24.0
矩形的面积: 32.0
值类型的构造器代理
构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。
以下实例中,Rect 结构体调用了 Size 和 Point 的构造过程:
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
init() {}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
// origin和size属性都使用定义时的默认值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0):
let basicRect = Rect()
print("Size 结构体初始值: \(basicRect.size.width, basicRect.size.height) ")
print("Rect 结构体初始值: \(basicRect.origin.x, basicRect.origin.y) ")
// 将origin和size的参数值赋给对应的存储型属性
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
size: Size(width: 5.0, height: 5.0))
print("Size 结构体初始值: \(originRect.size.width, originRect.size.height) ")
print("Rect 结构体初始值: \(originRect.origin.x, originRect.origin.y) ")
//先通过center和size的值计算出origin的坐标。
//然后再调用(或代理给)init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
size: Size(width: 3.0, height: 3.0))
print("Size 结构体初始值: \(centerRect.size.width, centerRect.size.height) ")
print("Rect 结构体初始值: \(centerRect.origin.x, centerRect.origin.y) ")
以上程序执行输出结果为:
Size 结构体初始值: (0.0, 0.0)
Rect 结构体初始值: (0.0, 0.0)
Size 结构体初始值: (5.0, 5.0)
Rect 结构体初始值: (2.0, 2.0)
Size 结构体初始值: (3.0, 3.0)
Rect 结构体初始值: (2.5, 2.5)
构造器代理规则
值类型 类类型
不支持继承,所以构造器代理的过程相对简单,因为它们只能代理给本身提供的其它构造器。 你可以使用self.init在自定义的构造器中引用其它的属于相同值类型的构造器。 它可以继承自其它类,这意味着类有责任保证其所有继承的存储型属性在构造时也能正确的初始化。
类的继承和构造过程
Swift 提供了两种类型的类构造器来确保所有类实例中存储型属性都能获得初始值,它们分别是指定构造器和便利构造器。
指定构造器 便利构造器
类中最主要的构造器 类中比较次要的、辅助型的构造器
初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。 可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入的实例。
每一个类都必须拥有至少一个指定构造器 只在必要的时候为类提供便利构造器
Init(parameters) {
statements
}
convenience init(parameters) {
statements
}
指定构造器实例
class mainClass {
var no1 : Int // 局部存储变量
init(no1 : Int) {
self.no1 = no1 // 初始化
}
}
class subClass : mainClass {
var no2 : Int // 新的子类存储变量
init(no1 : Int, no2 : Int) {
self.no2 = no2 // 初始化
super.init(no1:no1) // 初始化超类
}
}
let res = mainClass(no1: 10)
let res2 = subClass(no1: 10, no2: 20)
print("res 为: \(res.no1)")
print("res2 为: \(res2.no1)")
print("res2 为: \(res2.no2)")
以上程序执行输出结果为:
res 为: 10
res 为: 10
res 为: 20
便利构造器实例
class mainClass {
var no1 : Int // 局部存储变量
init(no1 : Int) {
self.no1 = no1 // 初始化
}
}
class subClass : mainClass {
var no2 : Int
init(no1 : Int, no2 : Int) {
self.no2 = no2
super.init(no1:no1)
}
// 便利方法只需要一个参数
override convenience init(no1: Int) {
self.init(no1:no1, no2:0)
}
}
let res = mainClass(no1: 20)
let res2 = subClass(no1: 30, no2: 50)
print("res 为: \(res.no1)")
print("res2 为: \(res2.no1)")
print("res2 为: \(res2.no2)")
以上程序执行输出结果为:
res 为: 20
res2 为: 30
res2 为: 50
构造器的继承和重载
Swift 中的子类不会默认继承父类的构造器。
父类的构造器仅在确定和安全的情况下被继承。
当你重写一个父类指定构造器时,你需要写override修饰符。
class SuperClass {
var corners = 4
var description: String {
return "\(corners) 边"
}
}
let rectangle = SuperClass()
print("矩形: \(rectangle.description)")
class SubClass: SuperClass {
override init() { //重载构造器
super.init()
corners = 5
}
}
let subClass = SubClass()
print("五角型: \(subClass.description)")
以上程序执行输出结果为:
矩形: 4 边
五角型: 5 边
指定构造器和便利构造器实例
接下来的例子将在操作中展示指定构造器、便利构造器和自动构造器的继承。
它定义了包含两个个类MainClass、SubClass的类层次结构,并将演示它们的构造器是如何相互作用的。
class MainClass {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[匿名]")
}
}
let main = MainClass(name: "Runoob")
print("MainClass 名字为: \(main.name)")
let main2 = MainClass()
print("没有对应名字: \(main2.name)")
class SubClass: MainClass {
var count: Int
init(name: String, count: Int) {
self.count = count
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, count: 1)
}
}
let sub = SubClass(name: "Runoob")
print("MainClass 名字为: \(sub.name)")
let sub2 = SubClass(name: "Runoob", count: 3)
print("count 变量: \(sub2.count)")
以上程序执行输出结果为:
MainClass 名字为: Runoob
没有对应名字: [匿名]
MainClass 名字为: Runoob
count 变量: 3
类的可失败构造器
如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器。
变量初始化失败可能的原因有:
传入无效的参数值。
缺少某种所需的外部资源。
没有满足特定条件。
为了妥善处理这种构造过程中可能会失败的情况。
你可以在一个类,结构体或是枚举类型的定义中,添加一个或多个可失败构造器。其语法为在init关键字后面加添问号(init?)。
实例
下例中,定义了一个名为Animal的结构体,其中有一个名为species的,String类型的常量属性。
同时该结构体还定义了一个,带一个String类型参数species的,可失败构造器。这个可失败构造器,被用来检查传入的参数是否为一个空字符串,如果为空字符串,则该可失败构造器,构建对象失败,否则成功。
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { return nil }
self.species = species
}
}
//通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功
// someCreature 的类型是 Animal? 而不是 Animal
let someCreature = Animal(species: "长颈鹿")
// 打印 "动物初始化为长颈鹿"
if let giraffe = someCreature {
print("动物初始化为\(giraffe.species)")
}
以上程序执行输出结果为:
动物初始化为长颈鹿
枚举类型的可失败构造器
你可以通过构造一个带一个或多个参数的可失败构造器来获取枚举类型中特定的枚举成员。
实例
下例中,定义了一个名为TemperatureUnit的枚举类型。其中包含了三个可能的枚举成员(Kelvin,Celsius,和 Fahrenheit)和一个被用来找到Character值所对应的枚举成员的可失败构造器:
enum TemperatureUnit {
// 开尔文,摄氏,华氏
case Kelvin, Celsius, Fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .Kelvin
case "C":
self = .Celsius
case "F":
self = .Fahrenheit
default:
return nil
}
}
}
let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
print("这是一个已定义的温度单位,所以初始化成功。")
}
let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
print("这不是一个已定义的温度单位,所以初始化失败。")
}
以上程序执行输出结果为:
这是一个已定义的温度单位,所以初始化成功。
这不是一个已定义的温度单位,所以初始化失败。
类的可失败构造器
值类型(如结构体或枚举类型)的可失败构造器,对何时何地触发构造失败这个行为没有任何的限制。
但是,类的可失败构造器只能在所有的类属性被初始化后和所有类之间的构造器之间的代理调用发生完后触发失败行为。
实例
下例子中,定义了一个名为 StudRecord 的类,因为 studname 属性是一个常量,所以一旦 StudRecord 类构造成功,studname 属性肯定有一个非nil的值。
class StudRecord {
let studname: String!
init?(studname: String) {
self.studname = studname
if studname.isEmpty { return nil }
}
}
if let stname = StudRecord(studname: "失败构造器") {
print("模块为 \(stname.studname)")
}
以上程序执行输出结果为:
模块为 失败构造器
覆盖一个可失败构造器
就如同其它构造器一样,你也可以用子类的可失败构造器覆盖基类的可失败构造器。
者你也可以用子类的非可失败构造器覆盖一个基类的可失败构造器。
你可以用一个非可失败构造器覆盖一个可失败构造器,但反过来却行不通。
一个非可失败的构造器永远也不能代理调用一个可失败构造器。
实例
以下实例描述了可失败与非可失败构造器:
class Planet {
var name: String
init(name: String) {
self.name = name
}
convenience init() {
self.init(name: "[No Planets]")
}
}
let plName = Planet(name: "Mercury")
print("行星的名字是: \(plName.name)")
let noplName = Planet()
print("没有这个名字的行星: \(noplName.name)")
class planets: Planet {
var count: Int
init(name: String, count: Int) {
self.count = count
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, count: 1)
}
}
以上程序执行输出结果为:
行星的名字是: Mercury
没有这个名字的行星: [No Planets]
可失败构造器 init!
通常来说我们通过在init关键字后添加问号的方式(init?)来定义一个可失败构造器,但你也可以使用通过在init后面添加惊叹号的方式来定义一个可失败构造器(init!)。实例如下:
struct StudRecord {
let stname: String
init!(stname: String) {
if stname.isEmpty {return nil }
self.stname = stname
}
}
let stmark = StudRecord(stname: "Runoob")
if let name = stmark {
print("指定了学生名")
}
let blankname = StudRecord(stname: "")
if blankname == nil {
print("学生名为空")
}
以上程序执行输出结果为:
指定了学生名
学生名为空
Swift 析构过程
在一个类的实例被释放之前,析构函数被立即调用。用关键字deinit来标示析构函数,类似于初始化函数用init来标示。析构函数只适用于类类型。
析构过程原理
Swift 会自动释放不再需要的实例以释放资源。
Swift 通过自动引用计数(ARC)处理实例的内存管理。
通常当你的实例被释放时不需要手动地去清理。但是,当使用自己的资源时,你可能需要进行一些额外的清理。
例如,如果创建了一个自定义的类来打开一个文件,并写入一些数据,你可能需要在类实例被释放之前关闭该文件。
语法
在类的定义中,每个类最多只能有一个析构函数。析构函数不带任何参数,在写法上不带括号:
deinit {
// 执行析构过程
}
实例
var counter = 0; // 引用计数器
class BaseClass {
init() {
counter += 1;
}
deinit {
counter -= 1;
}
}
var show: BaseClass? = BaseClass()
print(counter)
show = nil
print(counter)
以上程序执行输出结果为:
1
0
当 show = nil 语句执行后,计算器减去 1,show 占用的内存就会释放。
var counter = 0; // 引用计数器
class BaseClass {
init() {
counter += 1;
}
deinit {
counter -= 1;
}
}
var show: BaseClass? = BaseClass()
print(counter)
print(counter)
以上程序执行输出结果为:
1
1
Swift 可选链
可选链(Optional Chaining)是一种可以请求和调用属性、方法和子脚本的过程,用于请求或调用的目标可能为nil。
可选链返回两个值:
如果目标有值,调用就会成功,返回该值
如果目标为nil,调用将返回nil
多次请求或调用可以被链接成一个链,如果任意一个节点为nil将导致整条链失效。
可选链可替代强制解析
通过在属性、方法、或下标脚本的可选值后面放一个问号(?),即可定义一个可选链。
可选链 '?' 感叹号(!)强制展开方法,属性,下标脚本可选链
? 放置于可选值后来调用方法,属性,下标脚本 ! 放置于可选值后来调用方法,属性,下标脚本来强制展开值
当可选为 nil 输出比较友好的错误信息 当可选为 nil 时强制展开执行错误
使用感叹号(!)可选链实例
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
//将导致运行时错误
let roomCount = john.residence!.numberOfRooms
以上程序执行输出结果为:
fatal error: unexpectedly found nil while unwrapping an Optional value
想使用感叹号(!)强制解析获得这个人residence属性numberOfRooms属性值,将会引发运行时错误,因为这时没有可以供解析的residence值。
使用问号(?)可选链实例
class Person {
var residence: Residence?
}
class Residence {
var numberOfRooms = 1
}
let john = Person()
// 链接可选residence?属性,如果residence存在则取回numberOfRooms的值
if let roomCount = john.residence?.numberOfRooms {
print("John 的房间号为 \(roomCount)。")
} else {
print("不能查看房间号")
}
以上程序执行输出结果为:
不能查看房间号
因为这种尝试获得numberOfRooms的操作有可能失败,可选链会返回Int?类型值,或者称作"可选Int"。当residence是空的时候(上例),选择Int将会为空,因此会出现无法访问numberOfRooms的情况。
要注意的是,即使numberOfRooms是非可选Int(Int?)时这一点也成立。只要是通过可选链的请求就意味着最后numberOfRooms总是返回一个Int?而不是Int。
为可选链定义模型类
你可以使用可选链来多层调用属性,方法,和下标脚本。这让你可以利用它们之间的复杂模型来获取更底层的属性,并检查是否可以成功获取此类底层属性。
实例
定义了四个模型类,其中包括多层可选链:
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
通过可选链调用方法
你可以使用可选链的来调用可选值的方法并检查方法调用是否成功。即使这个方法没有返回值,你依然可以使用可选链来达成这一目的。
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if ((john.residence?.printNumberOfRooms()) != nil) {
print("输出房间号")
} else {
print("无法输出房间号")
}
以上程序执行输出结果为:
无法输出房间号
使用if语句来检查是否能成功调用printNumberOfRooms方法:如果方法通过可选链调用成功,printNumberOfRooms的隐式返回值将会是Void,如果没有成功,将返回nil。
使用可选链调用下标脚本
你可以使用可选链来尝试从下标脚本获取值并检查下标脚本的调用是否成功,然而,你不能通过可选链来设置下标脚本。
实例1
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if let firstRoomName = john.residence?[0].name {
print("第一个房间名 \(firstRoomName).")
} else {
print("无法检索到房间")
}
以上程序执行输出结果为:
无法检索到房间
在下标脚本调用中可选链的问号直接跟在 john.residence 的后面,在下标脚本括号的前面,因为 john.residence 是可选链试图获得的可选值。
实例2
实例中创建一个 Residence 实例给 john.residence,且在他的 rooms 数组中有一个或多个 Room 实例,那么你可以使用可选链通过 Residence 下标脚本来获取在 rooms 数组中的实例了:
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse
let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence!.address = johnsAddress
if let johnsStreet = john.residence?.address?.street {
print("John 所在的街道是 \(johnsStreet)。")
} else {
print("无法检索到地址。 ")
}
以上程序执行输出结果为:
John 所在的街道是 Laurel Street。
通过可选链接调用来访问下标
通过可选链接调用,我们可以用下标来对可选值进行读取或写入,并且判断下标调用是否成功。
实例
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
print("第一个房间名为\(firstRoomName)")
} else {
print("无法检索到房间")
}
以上程序执行输出结果为:
第一个房间名为客厅
访问可选类型的下标
如果下标返回可空类型值,比如Swift中Dictionary的key下标。可以在下标的闭合括号后面放一个问号来链接下标的可空返回值:
var testScores = ["Dave": [86, 82, 84], "Bev": [79, 94, 81]]
testScores["Dave"]?[0] = 91
testScores["Bev"]?[0]++
testScores["Brian"]?[0] = 72
// the "Dave" array is now [91, 82, 84] and the "Bev" array is now [80, 94, 81]
上面的例子中定义了一个testScores数组,包含了两个键值对, 把String类型的key映射到一个整形数组。
这个例子用可选链接调用把"Dave"数组中第一个元素设为91,把"Bev"数组的第一个元素+1,然后尝试把"Brian"数组中的第一个元素设为72。
前两个调用是成功的,因为这两个key存在。但是key"Brian"在字典中不存在,所以第三个调用失败。
连接多层链接
你可以将多层可选链连接在一起,可以掘取模型内更下层的属性方法和下标脚本。然而多层可选链不能再添加比已经返回的可选值更多的层。
如果你试图通过可选链获得Int值,不论使用了多少层链接返回的总是Int?。 相似的,如果你试图通过可选链获得Int?值,不论使用了多少层链接返回的总是Int?。
实例1
下面的例子试图获取john的residence属性里的address的street属性。这里使用了两层可选链来联系residence和address属性,它们两者都是可选类型:
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if let johnsStreet = john.residence?.address?.street {
print("John 的地址为 \(johnsStreet).")
} else {
print("不能检索地址")
}
以上程序执行输出结果为:
不能检索地址
实例2
如果你为Address设定一个实例来作为john.residence.address的值,并为address的street属性设定一个实际值,你可以通过多层可选链来得到这个属性值。
class Person {
var residence: Residence?
}
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
get{
return rooms[i]
}
set {
rooms[i] = newValue
}
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
class Room {
let name: String
init(name: String) { self.name = name }
}
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
john.residence?[0] = Room(name: "浴室")
let johnsHouse = Residence()
johnsHouse.rooms.append(Room(name: "客厅"))
johnsHouse.rooms.append(Room(name: "厨房"))
john.residence = johnsHouse
if let firstRoomName = john.residence?[0].name {
print("第一个房间是\(firstRoomName)")
} else {
print("无法检索房间")
}
以上实例输出结果为:
第一个房间是客厅
对返回可选值的函数进行链接
我们还可以通过可选链接来调用返回可空值的方法,并且可以继续对可选值进行链接。
实例
class Person {
var residence: Residence?
}
// 定义了一个变量 rooms,它被初始化为一个Room[]类型的空数组
class Residence {
var rooms = [Room]()
var numberOfRooms: Int {
return rooms.count
}
subscript(i: Int) -> Room {
return rooms[i]
}
func printNumberOfRooms() {
print("房间号为 \(numberOfRooms)")
}
var address: Address?
}
// Room 定义一个name属性和一个设定room名的初始化器
class Room {
let name: String
init(name: String) { self.name = name }
}
// 模型中的最终类叫做Address
class Address {
var buildingName: String?
var buildingNumber: String?
var street: String?
func buildingIdentifier() -> String? {
if (buildingName != nil) {
return buildingName
} else if (buildingNumber != nil) {
return buildingNumber
} else {
return nil
}
}
}
let john = Person()
if john.residence?.printNumberOfRooms() != nil {
print("指定了房间号)")
} else {
print("未指定房间号")
}
以上程序执行输出结果为:
未指定房间号
Swift 自动引用计数(ARC)
Swift 使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存
通常情况下我们不需要去手动释放内存,因为 ARC 会在类的实例不再被使用时,自动释放其占用的内存。
但在有些时候我们还是需要在代码中实现内存管理。
ARC 功能
当每次使用 init() 方法创建一个类的新的实例的时候,ARC 会分配一大块内存用来储存实例的信息。
内存中会包含实例的类型信息,以及这个实例所有相关属性的值。
当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。
为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。
实例赋值给属性、常量或变量,它们都会创建此实例的强引用,只要强引用还在,实例是不允许被销毁的。
ARC 实例
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) 开始初始化")
}
deinit {
print("\(name) 被析构")
}
}
// 值会被自动初始化为nil,目前还不会引用到Person类的实例
var reference1: Person?
var reference2: Person?
var reference3: Person?
// 创建Person类的新实例
reference1 = Person(name: "Runoob")
//赋值给其他两个变量,该实例又会多出两个强引用
reference2 = reference1
reference3 = reference1
//断开第一个强引用
reference1 = nil
//断开第二个强引用
reference2 = nil
//断开第三个强引用,并调用析构函数
reference3 = nil
以上程序执行输出结果为:
Runoob 开始初始化
Runoob 被析构
类实例之间的循环强引用
在上面的例子中,ARC 会跟踪你所新创建的 Person 实例的引用数量,并且会在 Person 实例不再被需要时销毁它。
然而,我们可能会写出这样的代码,一个类永远不会有0个强引用。这种情况发生在两个类实例互相保持对方的强引用,并让对方不被销毁。这就是所谓的循环强引用。
实例
下面展示了一个不经意产生循环强引用的例子。例子定义了两个类:Person和Apartment,用来建模公寓和它其中的居民:
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) 被析构") }
}
class Apartment {
let number: Int
init(number: Int) { self.number = number }
var tenant: Person?
deinit { print("Apartment #\(number) 被析构") }
}
// 两个变量都被初始化为nil
var runoob: Person?
var number73: Apartment?
// 赋值
runoob = Person(name: "Runoob")
number73 = Apartment(number: 73)
// 意感叹号是用来展开和访问可选变量 runoob 和 number73 中的实例
// 循环强引用被创建
runoob!.apartment = number73
number73!.tenant = runoob
// 断开 runoob 和 number73 变量所持有的强引用时,引用计数并不会降为 0,实例也不会被 ARC 销毁
// 注意,当你把这两个变量设为nil时,没有任何一个析构函数被调用。
// 强引用循环阻止了Person和Apartment类实例的销毁,并在你的应用程序中造成了内存泄漏
runoob = nil
number73 = nil
解决实例之间的循环强引用
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:
弱引用
无主引用
弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
对于生命周期中会变为nil的实例使用弱引用。相反的,对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。
弱引用实例
class Module {
let name: String
init(name: String) { self.name = name }
var sub: SubModule?
deinit { print("\(name) 主模块") }
}
class SubModule {
let number: Int
init(number: Int) { self.number = number }
weak var topic: Module?
deinit { print("子模块 topic 数为 \(number)") }
}
var toc: Module?
var list: SubModule?
toc = Module(name: "ARC")
list = SubModule(number: 4)
toc!.sub = list
list!.topic = toc
toc = nil
list = nil
以上程序执行输出结果为:
ARC 主模块
子模块 topic 数为 4
无主引用实例
class Student {
let name: String
var section: Marks?
init(name: String) {
self.name = name
}
deinit { print("\(name)") }
}
class Marks {
let marks: Int
unowned let stname: Student
init(marks: Int, stname: Student) {
self.marks = marks
self.stname = stname
}
deinit { print("学生的分数为 \(marks)") }
}
var module: Student?
module = Student(name: "ARC")
module!.section = Marks(marks: 98, stname: module!)
module = nil
以上程序执行输出结果为:
ARC
学生的分数为 98
闭包引起的循环强引用
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了实例。这个闭包体中可能访问了实例的某个属性,例如self.someProperty,或者闭包中调用了实例的某个方法,例如self.someMethod。这两种情况都导致了闭包 "捕获" self,从而产生了循环强引用。
实例
下面的例子为你展示了当一个闭包引用了self后是如何产生一个循环强引用的。例子中定义了一个叫HTMLElement的类,用一种简单的模型表示 HTML 中的一个单独的元素:
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
if let text = self.text {
return "<\(self.name)>\(text)\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
// 创建实例并打印信息
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
HTMLElement 类产生了类实例和 asHTML 默认值的闭包之间的循环强引用。
实例的 asHTML 属性持有闭包的强引用。但是,闭包在其闭包体内使用了self(引用了self.name和self.text),因此闭包捕获了self,这意味着闭包又反过来持有了HTMLElement实例的强引用。这样两个对象就产生了循环强引用。
解决闭包引起的循环强引用:在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。
弱引用和无主引用
当闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。
相反的,当捕获引用有时可能会是nil时,将闭包内的捕获定义为弱引用。
如果捕获的引用绝对不会置为nil,应该用无主引用,而不是弱引用。
实例
前面的HTMLElement例子中,无主引用是正确的解决循环强引用的方法。这样编写HTMLElement类来避免循环强引用:
class HTMLElement {
let name: String
let text: String?
lazy var asHTML: () -> String = {
[unowned self] in
if let text = self.text {
return "<\(self.name)>\(text)\(self.name)>"
} else {
return "<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) 被析构")
}
}
//创建并打印HTMLElement实例
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
// HTMLElement实例将会被销毁,并能看到它的析构函数打印出的消息
paragraph = nil
以上程序执行输出结果为:
hello, world
p 被析构