方法(Method)
基本概念
枚举、结构体、类都可以定义实例方法、类型方法
- 实例方法(Instance Method):通过实例对象调用
- 类型方法(Type Method):通过类型调用
实例方法调用
class Car {
var count = 0
func getCount() -> Int {
count
}
}
let car = Car()
car.getCount()
类型方法用static
或者class
关键字定义
class Car {
static var count = 0
static func getCount() -> Int {
count
}
}
Car.getCount()
类型方法中不能调用实例属性,反之实例方法中也不能调用类型属性
不管是类型方法还是实例方法,都会含有隐藏参数self
self
在实例方法中代表实例对象
self
在类型方法中代表类型
// count等于self.count、Car.self.count、Car.count
static func getCount() -> Int {
self.count
}
mutating
结构体和枚举是值类型,默认情况下,值类型的属性不能被自身的实例方法修改
在func
关键字前面加上mutating
可以允许这种修改行为
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
}
}
enum StateSwitch {
case low, middle, high
mutating func next() {
switch self {
case .low:
self = .middle
case .middle:
self = .high
case .high:
self = .low
}
}
}
@discardableResult
在func
前面加上@discardableResult
,可以消除函数调用后返回值未被使用的警告
struct Point {
var x = 0.0, y = 0.0
@discardableResult mutating func moveX(deltaX: Double) -> Double {
x += deltaX
return x
}
}
var p = Point()
p.moveX(deltaX: 10)
下标(subscript)
基本概念
使用subscript
可以给任意类型(枚举、结构体、类)增加下标功能
有些地方也翻译成:下标脚本
subscript
的语法类似于实例方法、计算属性,本质就是方法(函数)
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
set {
if index == 0 {
x = newValue
} else if index == 1 {
y = newValue
}
}
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
var p = Point()
p[0] = 11.1
p[1] = 22.2
print(p.x) // 11.1
print(p.y) // 22.2
print(p[0]) // 11.1
print(p[1]) // 22.2
subscript
中定义的返回值类型决定了getter
中返回值类型和setter
中newValue
的类型
subscript
可以接收多个参数,并且类型任意
class Grid {
var data = [
[0, 1 ,2],
[3, 4, 5],
[6, 7, 8]
]
subscript(row: Int, column: Int) -> Int {
set {
guard row >= 0 && row < 3 && column >= 0 && column < 3 else { return }
data[row][column] = newValue
}
get {
guard row >= 0 && row < 3 && column >= 0 && column < 3 else { return 0 }
return data[row][column]
}
}
}
var grid = Grid()
grid[0, 1] = 77
grid[1, 2] = 88
grid[2, 0] = 99
subscript
可以没有setter
,但必须要有getter
,同计算属性
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
get {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
}
subscript
如果只有getter
,可以省略getter
class Point {
var x = 0.0, y = 0.0
subscript(index: Int) -> Double {
if index == 0 {
return x
} else if index == 1 {
return y
}
return 0
}
}
subscript
可以设置参数标签
只有设置了自定义标签的调用才需要写上参数标签
class Point {
var x = 0.0, y = 0.0
subscript(index i: Int) -> Double {
if i == 0 {
return x
} else if i == 1 {
return y
}
return 0
}
}
var p = Point()
p.y = 22.2
print(p[index: 1]) // 22.2
subscript
可以是类型方法
class Sum {
static subscript(v1: Int, v2: Int) -> Int {
v1 + v2
}
}
print(Sum[10, 20]) // 30
通过反汇编来分析
看下面的示例代码,我们将断点打到图上的位置,然后观察反汇编
看到其内部是会调用setter
来进行计算
然后再将断点打到这里来看
看到其内部是会调用getter
来进行计算
经上述分析就可以证明subscript
本质就是方法调用
结构体和类作为返回值对比
看下面的示例代码
struct Point {
var x = 0, y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
set { point = newValue }
get { point }
}
}
var pm = PointManager()
pm[0].x = 11 // 等价于pm[0] = Point(x: 11, y: pm[0].y)
pm[0].y = 22 // 等价于pm[0] = Point(x: pm[0].x, y: 22)
如果我们注释掉setter
,那么调用会报错
但是我们将结构体换成类,就不会报错了
原因还是在于结构体是值类型,通过getter
得到的Point
结构体只是临时的值(可以想成计算属性),并不是真正的存储属性point,所以会报错
通过打印也可以看出来要修改的并不是同一个地址值的point
但换成了类,那么通过getter
得到的Point
类是一个指针变量,而修改的是指向堆空间中的Point
的属性,所以不会报错
继承(Inheritance)
基本概念
值类型(结构体、枚举)不支持继承,只有类支持继承
没有父类的类,叫做基类
Swift
并没有像OC、Java
那样的规定,任何类最终都要继承自某个基类
子类可以重新父类的下标、方法、属性,重写必须加上override
class Car {
func run() {
print("run")
}
}
class Truck: Car {
override func run() {
}
}
内存结构
看下面几个类的内存占用是多少
class Animal {
var age = 0
}
class Dog: Animal {
var weight = 0
}
class ErHa: Dog {
var iq = 0
}
let a = Animal()
a.age = 10
print(Mems.size(ofRef: a)) // 32
print(Mems.memStr(ofRef: a))
//0x000000010000c3c8
//0x0000000000000003
//0x000000000000000a
//0x000000000000005f
let d = Dog()
d.age = 10
d.weight = 20
print(Mems.size(ofRef: d)) // 32
print(Mems.memStr(ofRef: d))
//0x000000010000c478
//0x0000000000000003
//0x000000000000000a
//0x0000000000000014
let e = ErHa()
e.age = 10
e.weight = 20
e.iq = 30
print(Mems.size(ofRef: e)) // 48
print(Mems.memStr(ofRef: e))
//0x000000010000c548
//0x0000000000000003
//0x000000000000000a
//0x0000000000000014
//0x000000000000001e
//0x0000000000000000
首先类内部会有16个字节存储类信息和引用计数,然后才是属性,又由于堆空间分配内存的原则是16的倍数,所以内存空间占用分别为32、32、48
子类会继承自父类的属性,所以内存会算上父类的属性存储空间
重写实例方法、下标
class Animal {
func speak() {
print("Animal speak")
}
subscript(index: Int) -> Int {
index
}
}
var ani: Animal
ani = Animal()
ani.speak()
print(ani[6])
class Cat: Animal {
override func speak() {
super.speak()
print("Cat speak")
}
override subscript(index: Int) -> Int {
super[index] + 1
}
}
ani = Cat()
ani.speak()
print(ani[7])
被class
修饰的类型方法、下标,允许被子类重写
class Animal {
class func speak() {
print("Animal speak")
}
class subscript(index: Int) -> Int {
index
}
}
Animal.speak()
print(Animal[6])
class Cat: Animal {
override class func speak() {
super.speak()
print("Cat speak")
}
override class subscript(index: Int) -> Int {
super[index] + 1
}
}
Cat.speak()
print(Cat[7])
被static
修饰的类型方法、下标,不允许被子类重写
但是被class
修饰的类型方法、下标,子类重写时允许使用static
修饰
class Animal {
class func speak() {
print("Animal speak")
}
class subscript(index: Int) -> Int {
index
}
}
Animal.speak()
print(Animal[6])
class Cat: Animal {
override static func speak() {
super.speak()
print("Cat speak")
}
override static subscript(index: Int) -> Int {
super[index] + 1
}
}
Cat.speak()
print(Cat[7])
但再后面的子类就不被允许了
重写属性
子类可以将父类的属性(存储、计算)重写为计算属性
class Animal {
var age = 0
}
class Dog: Animal {
override var age: Int {
set {
}
get {
10
}
}
var weight = 0
}
但子类不可以将父类的属性重写为存储属性
只能重写var
属性,不能重写let
属性
重写时,属性名、类型要一致
子类重写后的属性权限不能小于父类的属性权限
- 如果父类属性是只读的,那么子类重写后的属性也是只读的,也可以是可读可写的
- 如果父类属性是可读可写的,那么子类重写后的属性也必须是可读可写的
重写实例属性
class Circle {
// 存储属性
var radius: Int = 0
// 计算属性
var diameter: Int {
set(newDiameter) {
print("Circle setDiameter")
radius = newDiameter / 2
}
get {
print("Circle getDiameter")
return radius * 2
}
}
}
class SubCircle: Circle {
override var radius: Int {
set {
print("SubCircle setRadius")
super.radius = newValue > 0 ? newValue : 0
}
get {
print("SubCircle getRadius")
return super.radius
}
}
override var diameter: Int {
set {
print("SubCircle setDiameter")
super.diameter = newValue > 0 ? newValue : 0
}
get {
print("SubCircle getDiameter")
return super.diameter
}
}
}
var c = SubCircle()
c.radius = 6
print(c.diameter)
c.diameter = 20
print(c.radius)
//SubCircle setRadius
//SubCircle getDiameter
//Circle getDiameter
//SubCircle getRadius
//12
//SubCircle setDiameter
//Circle setDiameter
//SubCircle setRadius
//SubCircle getRadius
//10
从父类继承过来的存储属性,都会分配内存空间,不管之后会不会被重写为计算属性
如果重写的方法里的setter
和getter
不写super
,那么就会死循环
class SubCircle: Circle {
override var radius: Int {
set {
print("SubCircle setRadius")
radius = newValue > 0 ? newValue : 0
}
get {
print("SubCircle getRadius")
return radius
}
}
}
重写类型属性
被class
修饰的计算类型属性,可以被子类重写
class Circle {
// 存储属性
static var radius: Int = 0
// 计算属性
class var diameter: Int {
set(newDiameter) {
print("Circle setDiameter")
radius = newDiameter / 2
}
get {
print("Circle getDiameter")
return radius * 2
}
}
}
class SubCircle: Circle {
override static var diameter: Int {
set {
print("SubCircle setDiameter")
super.diameter = newValue > 0 ? newValue : 0
}
get {
print("SubCircle getDiameter")
return super.diameter
}
}
}
Circle.radius = 6
print(Circle.diameter)
Circle.diameter = 20
print(Circle.radius)
SubCircle.radius = 6
print(SubCircle.diameter)
SubCircle.diameter = 20
print(SubCircle.radius)
//Circle getDiameter
//12
//Circle setDiameter
//10
//SubCircle getDiameter
//Circle getDiameter
//12
//SubCircle setDiameter
//Circle setDiameter
//10
被static
修饰的类型属性(计算、存储),不可以被子类重写
属性观察器
可以在子类中为父类属性(除了只读计算属性、let属性)增加属性观察器
重写后还是存储属性,不是变成了计算属性
class Circle {
var radius: Int = 1
}
class SubCircle: Circle {
override var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue)
}
didSet {
print("SubCircle didSetRadius", oldValue, radius)
}
}
}
var circle = SubCircle()
circle.radius = 10
//SubCircle willSetRadius 10
//SubCircle didSetRadius 1 10
如果父类里也有属性观察器,那么子类赋值时,会先调用自己的属性观察器willSet
,然后调用父类的属性观察器willSet
;并且在父类里面才是真正的进行赋值,然后先父类的didSet
,最后再调用自己的didSet
class Circle {
var radius: Int = 1 {
willSet {
print("Circle willSetRadius", newValue)
}
didSet {
print("Circle didSetRadius", oldValue, radius)
}
}
}
class SubCircle: Circle {
override var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue)
}
didSet {
print("SubCircle didSetRadius", oldValue, radius)
}
}
}
var circle = SubCircle()
circle.radius = 10
//SubCircle willSetRadius 10
//Circle willSetRadius 10
//Circle didSetRadius 1 10
//SubCircle didSetRadius 1 10
可以给父类的计算属性
增加属性观察器
class Circle {
var radius: Int {
set {
print("Circle setRadius", newValue)
}
get {
print("Circle getRadius")
return 20
}
}
}
class SubCircle: Circle {
override var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue)
}
didSet {
print("SubCircle didSetRadius", oldValue, radius)
}
}
}
var circle = SubCircle()
circle.radius = 10
//Circle getRadius
//SubCircle willSetRadius 10
//Circle setRadius 10
//Circle getRadius
//SubCircle didSetRadius 20 20
上面打印会先调用一次Circle getRadius
是因为在设置值之前会先拿到它的oldValue
,所以需要调用getter
一次
为了测试,我们将oldValue
的获取去掉后,再打印发现就没有第一次的getter
的调用了
final
被final
修饰的方法、下标、属性,禁止被重写
被final
修饰的类,禁止被继承
方法调用的本质
我们先看下面的示例代码,分析结构体和类的调用方法区别是什么
struct Animal {
func speak() {
print("Animal speak")
}
func eat() {
print("Animal eat")
}
func sleep() {
print("Animal sleep")
}
}
var ani = Animal()
ani.speak()
ani.eat()
ani.sleep()
反汇编之后,发现结构体的方法调用就是直接找到方法所在地址直接调用
结构体的方法地址都是固定的
下面我们在看换成类之后反汇编的实现是怎样的
class Animal {
func speak() {
print("Animal speak")
}
func eat() {
print("Animal eat")
}
func sleep() {
print("Animal sleep")
}
}
var ani = Animal()
ani.speak()
ani.eat()
ani.sleep()
反汇编之后,会发现需要调用的方法地址是不确定的,所以凡是调用固定地址的都不会是类的方法的调用
而且上述的几个调用的方法地址都是从rcx
往高地址偏移8个字节来调用的,也就说明几个方法地址都是连续的
我们再来分析下方法调用前做了什么
通过反汇编我们可以看到,会从全局变量的指针找到其指向的堆内存中的类的存储空间,然后再根据类的前8个字节里的类信息知道需要调用的方法地址,从类信息的地址进行偏移找到方法地址,然后调用
然后我们将示例代码修改一下,再观察其本质是什么
class Animal {
func speak() {
print("Animal speak")
}
func eat() {
print("Animal eat")
}
func sleep() {
print("Animal sleep")
}
}
class Dog: Animal {
override func speak() {
print("Dog speak")
}
override func eat() {
print("Dog eat")
}
func run() {
print("Dog run")
}
}
var ani = Animal()
ani.speak()
ani.eat()
ani.sleep()
ani = Dog()
ani.speak()
ani.eat()
ani.sleep()
增加了子类后,Dog的类信息里的方法列表会存有重写后的父类方法,以及自己新增的方法
class Dog: Animal {
func run() {
print("Dog run")
}
}
如果子类里没有重写父类方法,那么类信息里的方法列表会有父类的方法,以及自己新增的方法