Swift学习38:类

张建 lol

  • Swift 是构建代码的 一种 通用的 构造体

  • swift 也是一种 面向对象(OOP) 的编程语言

  • swift中 可以定义 属性(常量、变量)和方法

  • Swift 中 引用类型 的,存储在 堆区

类和结构体对比

Swift 中类和结构体有很多共同点:

  • 定义 属性 用于 存储值
  • 定义 方法 用于 提供功能
  • 定义 下标脚本 用于访问值
  • 定义 初始化器 用于 生成初始化值
  • 通过 扩展增加默认实现的功能
  • 符合 协议对某类提供标准功能

与结构体相对,类还有如下的 附加 功能:

  • class 定义 属性,必须赋值或包装成 Optionalstruct 定义属性不需要要赋值
  • class 是 引用类型,浅拷贝。struct 是 值类型,深拷贝。
  • class 可以继承。struct 不能继承
  • class 有 引用计数,可以 多次引用。struct 没有引用计数
  • class 有 类型转换,允许在 运行时检查和解释一个类实例的类型。struct 没有
  • class 有 反初始化器 允许一个 类实例释放任何其所被分配的资源
  • class 分配在 中,动态分配释放内存。struct 分配在 中,系统分配释放内存
  • struct 比 class 更轻量级, 只访问一次拿到数据, 访问两次拿到数据(第一次获取指针,第二次获取数据)

类的属性

  • 存储属性:存储实例的常量和变量

  • 计算属性:通过某种方式计算出来的属性 { set {} get{} }

  • 类型属性:与整个自身相关的属性,用 static 关键字修饰

类的定义

我们使用 class 关键字在 swift 中创建一个类。例如:

  • 没有父类
1
2
3
class 类名 {
// 定义属性和方法等
}
  • 有父类
1
2
3
class 类名 : SuperClass{
// 定义属性和方法等
}

总结:
定义的类,可以没有父类,也可以有父类
通常情况下,定义类时,继承自 _SwiftObject

定义存储属性

  • 存储属性 要么是 变量存储属性(由 var 关键字引入),要么是 常量存储属性(由 let 关键字引入)

  • 定义 属性 必须 赋值或包装成Optional

示例:

1
2
3
4
5
6
7
8
// 存储属性
class Person {
// 定义属性时,需要赋值或包装Optional,否则报错
// 赋值
let age: Int = 30
// 包装Optional
var name: String?
}

实例化类

1
2
3
4
5
6
// 实例化
let p = Person()
p.age = 32 // Cannot assign to property: 'age' is a 'let' constant
p.name = "ZJ"
print(p.age)
print(p.name!)

注:常量存储属性不能修改

修改后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person {
// 定义属性时,需要赋值或包装Optional,否则报错
let age: Int = 30
var name: String?
}
// 实例化
let p = Person()
//p.age = 32
p.name = "ZJ"
print(p.age)
print(p.name!)

======
30
ZJ

延迟存储属性

延迟属性,即,使用 lazy修饰 的存储属性。特点:

  • 第一次访问的时候才被赋值

  • 不能保证线程安全

  • lazy 修饰的属性,必须要初始化,不能包装Optiaonal

示例:

1
2
3
4
5
6
class Manager {
// lazy
lazy var name:String?
}
let m = Manager()
print(m.name)

编译运行,报错

修改成:

1
2
3
4
5
// lazy
lazy var name:String = "ZJ"

======
ZJ

计算属性

  • 除了 存储属性 还能定义 计算属性,而它实际 并不是存储值

  • 相反,它提供一个 读取器 和一个 可选的设置器 来间接得到和设置它的 属性和值。类似于 OCset/get 方法去设值和取值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 定义计算属性
class Point {
var x = 0.0,y = 0.0
}
class Size {
var width = 0.0,height = 0.0
}
class Rect {
// 存储属性
var origin = Point()
var size = Size()

// 计算属性:可以通过 origin 和 size 计算出来它的 center
var center: Point {
get {
let centerX = origin.x + size.width/2.0
let centerY = origin.y + size.height/2.0
return Point(x:centerX, y:centerY)
}
set(newCenter) {
origin.x = newCenter.x - size.width/2.0
origin.y = newCenter.y - size.height/2.0
}
}
}
let rect = Rect(origin: Point(x: 0,y: 0),size: Size(width: 100,height: 100))
print(rect.center)

=============
Point(x: 50.0, y: 50.0)

简写 setter

  • 如果一个 计算属性设置器 没有为将要被设置的值定义一个名字,那么它将被默认命名为 newValue
1
2
3
4
set {
origin.x = newValue.x - size.width/2.0
origin.y = newValue.y - size.height/2.0
}

简写 getter

  • 如果整个 getter 的函数体是一个 单一的表达式,那么 getter 隐士返回这个表达式
1
2
3
4
// 简写 get
get {
CGPoint(x: p.x + s.w/2.0, y: p.y + s.h/2.0)
}

只读计算属性

  • 一个 有读取器 但是 没有设置器计算属性 就是所谓的 只读计算属性。只读计算属性返回一个值,也可以通过 点语法 访问,但是不能被修改为另一个值

  • 你必须用 var关键字 定义计算属性(包括只读计算属性)为变量属性 ,因为它的 值不是固定的

  • let关键字 只能用于 常量 属性,用于明确哪些值一旦作为实例初始化就不能更改

1
2
3
4
5
6
7
8
9
10
11
// 只读计算属性
class Rect {
var p = Point()
var s = Size()
// 计算属性
var center: CGPoint {
get {
return CGPoint(x: p.x + s.w/2.0, y: p.y + s.h/2.0)
}
}
}

属性观察者

  • willSet 会在 该值被存储之前被调用

  • didSet 会在 一个新值被存储之后被调用

  • 如果你实现了一个 willSet 观察者,新的属性值会以常量形式参数传递。你可以再你的 willSet 实现中为这个参数定义名字。如果你没有为它命名,那么它会使用默认值的名字 newValue

  • 如果你实现了一个 didSet 观察者,一个包含旧属性值的常量形式参数将会被传递。你可以为它命名,也可以使用默认值的形式参数名 oldValue。如果你在属性自己的 disSet 观察者里给自己赋值,你赋值的新值就会取代刚刚设置的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 属性观察器
class StepCount {
var totalStep: Int = 0 {
// 值被存储前调用
willSet(newValue) {
print(newValue)
}
// 值被存储后调用
didSet(oldValue) {
// 如果 存储后的值 > 存储前的值
if totalStep > oldValue {
print("步数增加: \(totalStep - oldValue)")
}
}
}
}
let step = StepCount()
step.totalStep = 100
step.totalStep = 200

============
100
步数增加: 100
200
步数增加: 100

全局和局部变量

  • 观察属性的能力同样对全局变量和局部变量有效全局变量 是定义在 任何函数、方法、闭包或者类之外的变量局部变量 是定义在 函数、方法或闭包环境之中的变量
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 全局变量
class Sum {
var count: Int = 0 {
willSet {
print("count : \(newValue)")
}
didSet {
print("did change count from \(oldValue) to \(count)")
}
}
}
let sum = Sum()
sum.count = 10 // 全局变量
print(sum.count)

==============
count : 10
did change count from 0 to 10

类型属性

  • 使用 static 关键字来定义 类型属性
1
2
3
4
5
6
7
8
9
// 类型属性
class SuperClass {
// 类型属性
static var name: String = "ZJ"
}
print(SuperClass.name)

=======
ZJ
  • 你也可以使用 class 关键字来定义 类型属性
1
2
3
4
5
6
7
8
9
10
11
12
13
// 重写父类的实现
class SubClass: SuperClass {
// 类型属性
class var newName: String {
get {
return "New ZJ"
}
}
}
print(SubClass.newName)

======
New ZJ

定义实例方法

  • 实例方法 是属于 特定类实例、结构体实例或枚举实例的函数,提供 访问和修改实例属性的方法

  • 每一个类的实例都隐含一个叫做 self 的属性,它完全与实例本身相等。你可以用 self 属性在当前实例中调用自身的方法

  • 实际上,你不需要经常在代码中写 self。如果你没有显示地写出 selfswift 会在你方法中使用已知属性或方法的时候假定你调用了当前实例中的属性或者方法

  • 例外就是当一个实例方法的形式参数名与实例中某个属性拥有相同的名字的时候。在这种情况下,形式参数名具有优先权,并且调用属性的时候使用更加严谨的方式就很有必要了。你可以使用 self 属性来 区分形式参数名和属性名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 定义方法
class PrintInfo {
var x = 0.0, y = 0.0
// 实例方法
func printInfo() {
print("x is \(x),y is \(y)")
}
}
// 初始化一个实例 printInfo
var printInfo = PrintInfo()
// 赋值存储属性
printInfo.x = 10
printInfo.y = 20
// 实例调用 实例方法
printInfo.printInfo()

=======
x is 10.0,y is 20.0

实例方法 self

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// self
class SomePoint {
var x = 0.0
// 实例方法 self
func isToTheRightOf(x: Double) -> Bool {
// self 访问 属性
return self.x > x
}
}
// 初始化一个实例 somePoint
let somePoint = SomePoint()
// 赋值存储属性
somePoint.x = 4
// 如果 4 > 1
if somePoint.isToTheRightOf(x: 1.0) {
// 打印 4
print("the point is x \(somePoint.x)")
}

=======
the point is x 4.0

在实例方法中修改属性

引用类型的,允许修改属性

1
2
3
4
5
6
7
8
9
10
11
12
13
// 修改属性
class ChangeName {
var name: String = "ZZ"
func change() {
name = "ZJ"
print(name)
}
}
let name = ChangeName()
name.change()

===========
ZJ

类型方法

  • 通过在 func 关键字前用 class 关键字来明确一个 类型方法
1
2
3
4
5
6
7
8
9
10
11
12
13
// 类型方法
class PointClass {
// 类型方法
static func pringInfo(newX: Double) {
print("x is \(newX)")
}
}
var pc = PointClass()
// 类型方法用 类 调用
PointClass.pringInfo(newX: 10)

=======
x is 10

定义初始化器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 定义初始化器
class Name {
// 存储属性
var name: String

// 初始化器
init(name: String) {
// 初始化值
self.name = "my name is \(name)"
}
}
let name = Name(name: "ZJ")
print(name.name)

=======
my name is ZJ

定义下标脚本

  • 类、结构体和枚举 可以定义 下标,它可以作为访问集合、列表或序列号成员元素的快捷方式。你可以使用下标通过索引值来设置或检索值而不需要为设置和检索分别使用实例方法

  • 你可以为一个类型 定义多个下标,并且下标会基于传入的索引值的类型选择合适的下标重载使用。下标没有限制单个维度,你可以使用多个输入形参来定义下标以满足自定义类型的需求

  • 下标脚本 允许你通过在实例名后面的方括号内写一个或多个值对该类的实例进行查询。它的语法类似于实例方法和计算属性。使用关键字 subscript 来定义下标,并且指定一个或多个输入形式参数和返回类型,与实例方法一样。与实例方法不同的是,下标可以是读写也可以是只读的

  • 下标脚本的访问用 []

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定义下标脚本
class Grade {
subscript(num: Int) ->String {
if num < 60 {
return "不及格"
}else {
return "及格"
}
}
}
let grade = Grade()
print(grade[99])

=======
及格
  • Post title:Swift学习38:类
  • Post author:张建
  • Create time:2023-03-05 17:04:46
  • Post link:https://redefine.ohevan.com/2023/03/05/Swift课程/Swift学习38:类/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.