iOS-Swift-方法、下标、继承
一. 方法
枚举、结构体、类都可以定义实例方法、类型方法。
定义类方法:枚举、结构体使⽤static,类使⽤static或者class。
class Car {
static var count = 0
init() {
Car.count += 1
}
static func getCount() -> Int { count }
}
let c0 = Car()
let c1 = Car()
let c2 = Car()
print(Car.getCount()) // 3
- 在实例方法和类型方法中都有个self,在实例方法中self代表实例对象,在类型方法中self代表类型
- 在上面类型方法static func getCount中:count等价于self.count、Car.count、Car.self.count
1. mutating
枚举和结构体是值类型,默认情况下,值类型的属性不能被自身的实例方法修改,在func关键字前加mutating可以允许这种修改行为(类本来就可以改,就不⽤管了)
枚举:
enum StateSwitch {
case low, middle, high
mutating func next() {
switch self {
case .low:
self = .middle
case .middle:
self = .high
case .high:
self = .low
}
}
}
结构体:
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(deltaX: Double, deltaY: Double) {
x += deltaX
y += deltaY
// self = Point(x: x + deltaX, y: y + deltaY) 也是修改自己内存
}
}
2. @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)
@discardableResult
func get() -> Int {
return 10
}
get()
3. 将方法赋值给var、let
方法也可以像函数那样,赋值给var、let
//如果run类方法、run实例方法的参数和返回值是一样的,那么怎么知道拿到的是哪个方法呢?
struct Person {
var age: Int
func run(_ v: Int) { print("func run", age, v) }
static func run(_ v: Int) { print("static func run", v) }
}
let fn1 = Person.run //默认拿到的是类方法
fn1(10) //调用类方法,打印:static func run 10
let fn2: (Int) -> () = Person.run //也可以指定类型
fn2(20) //调用类方法,打印:static func run 20
let fn3: (Person) -> ((Int) -> ()) = Person.run //指定类型后
fn3(Person(age: 18))(30) //调用实例方法,打印:func run 18 30
如果fn3看不懂可以看下面一步一步的解释:
var fn1 = Person.run //fn1是(Person) -> ((Int) -> ())类型的。接收一个person实例,返回一个方法
var fn2 = fn1(Person(age: 18)) //fn2是(Int) -> ()类型的。fn2就是fn1接收一个实例返回的一个方法
fn2(30) //调用实例方法,打印:func run 18 30
二. 下标
使用subscript可以给任意类型(枚举、结构体、类)增加下标功能,有些地方也翻译为:下标脚本
subscript的语法类似于实例方法、计算属性,本质就是方法(函数)
1. 实例方法下标
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中定义的返回值类型决定了:
get方法的返回值类型
set方法中newValue的类型
(比如,上面subscript返回Double,那么subscript的get方法的返回值是Double,subscript的set方法的newValue也是Double) - subscript可以接受多个参数,并且类型任意
2. 类型方法下标
class Sum {
static subscript(v1: Int, v2: Int) -> Int {
return v1 + v2
}
}
print(Sum[10, 20]) // 30
3. 下标的细节
1. subscript可以没有set方法,但必须要有get方法,如果只有get方法,可以省略get(和计算属性的规定一样)
只有get:
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
}
}
}
省略get:
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
}
}
2. 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
3. 结构体、类作为返回值对比
结构体作为返回值:
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].y = 22
// Point(x: 11, y: 22)
print(pm[0])
// Point(x: 11, y: 22)
print(pm.point)
如果是结构体,想要给PointManager里面的结构体赋值,就必须实现set方法,如上,否则报错。
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 )
为什么结构体就必须实现set方法?因为结构体是值传递,PointManager里面的和传到PointManager外面的肯定不是同一个结构体,想要改PointManager里面的结构体肯定要有set方法。
类作为返回值:
class Point {
var x = 0, y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
get { point }
}
}
var pm = PointManager()
pm[0].x = 11
pm[0].y = 22
// Point(x: 11, y: 22)
print(pm[0])
// Point(x: 11, y: 22)
print(pm.point)
如果是类,对于pm[0].x = 11,pm[0]获取的就是point,是个指针,当然可以直接通过point访问x然后赋值11 (pm[0].x = 11相当于point.x = 11),所以如果是类不需要写set方法。
其实他们俩的区别就是值类型和引用类型的区别。
4. 接收多个参数的下标
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
print(grid.data)
// [0, 77, 2],
// [3, 4, 88],
// [99, 7, 8]
三. 继承
1. 继承简介
值类型(枚举、结构体)不支持继承,只有类支持继承
没有父类的类称为基类
Swift并没有像OC、Java那样的规定:任何类最终都要继承自某个基类
2. 继承的类内存结构分析
创建三个类,继承关系如下:
class Animal {
var age = 0
}
class Dog : Animal {
var weight = 0
}
class ErHa : Dog {
var iq = 0
}
Animal:
let a = Animal()
a.age = 10
// 系统实际分配32字节
print(Mems.size(ofRef: a))
/*
0x00000001000073e0 类型相关
0x0000000000000002 引用计数
0x000000000000000a 10
0x0000000000000000
*/
print(Mems.memStr(ofRef: a))
如上,a对象系统实际分配32字节,前8字节存放类型相关,后8字节存放引用计数,再后8字节存放10,(8 + 8 + 8 = 24字节,因为要是16倍数,所以实际分配32)。
Dog继承于Animal:
let d = Dog()
d.age = 10
d.weight = 20
// 系统实际分配32字节
print(Mems.size(ofRef: d))
/*
0x0000000100007490 类型相关
0x0000000000000002 引用计数
0x000000000000000a 10
0x0000000000000014 20
*/
print(Mems.memStr(ofRef: d))
如上,Dog继承于Animal,所以d对象也有age属性,d对象系统实际分配32字节,前8字节存放类型相关,后8字节存放引用计数,再后8字节存放10,最后8字节存放20。(8 + 8 + 8 + 8 = 32字节)
ErHa继承于Dog:
let e = ErHa()
e.age = 10
e.weight = 20
e.iq = 30
// 系统实际分配48字节
print(Mems.size(ofRef: e))
/*
0x0000000100007560 引用计数
0x0000000000000002 类型相关
0x000000000000000a 10
0x0000000000000014 20
0x000000000000001e 30
0x0000000000000000
*/
print(Mems.memStr(ofRef: e))
如上,ErHa继承于Dog,所以e对象也有weight、age属性,e对象系统实际分配48字节,前8字节存放类型相关,后8字节存放引用计数,再后8字节存放10,再后8字节存放20,再后8字节存放30,最后8字节存放0。(8 + 8 + 8 + 8 + 8 = 40字节,因为要是16倍数,所以实际分配48)
3. 重写
子类可以重写父类的方法、下标、属性(属性只能重写为计算属性),重写必须加上override关键字
① 重写方法、下标
- 重写实例方法、下标
父类:
class Animal {
func speak() {
print("Animal speak")
}
subscript(index: Int) -> Int {
return index
}
}
var anim: Animal
anim = Animal()
// Animal speak
anim.speak()
// 6
print(anim[6])
子类重写:
class Cat : Animal {
override func speak() {
super.speak()
print("Cat speak")
}
override subscript(index: Int) -> Int {
return super[index] + 1
}
}
anim = Cat() // 多态
// Animal speak
// Cat speak
anim.speak()
// 7
print(anim[6])
上面的anim = Cat()是父类指针指向子类对象,就是多态。
关于多态:anim.speak(),在编译的时候anim并不知道要调用的是父类还是子类的speak方法,运行的时候才会根据实际类型调用子类的speak方法。
- 重写类型方法、下标
被class修饰的类型方法、下标,允许被子类重写
被static修饰的类型方法、下标,不允许被子类重写
(比如,父类使用class修饰,子类重写然后用static修饰,那么子类的子类就不能再重写这个类型方法了)
父类:
class Animal {
class func speak() {
print("Animal speak")
}
class subscript(index: Int) -> Int {
return index
}
}
// Animal speak
Animal.speak()
// 6
print(Animal[6])
子类重写:
class Cat : Animal {
override class func speak() {
super.speak()
print("Cat speak")
}
override class subscript(index: Int) -> Int {
return super[index] + 1
}
}
// Animal speak
// Cat speak
Cat.speak()
// 7
print(Cat[6])
② 重写属性
-
子类可以将父类的var属性(存储、计算)重写为计算属性:
子类不可以将父类属性重写为存储属性
只能重写var属性,不能重写let属性
重写时,属性名、类型要一致 -
子类重写后的属性权限不能小于父类属性的权限:
如果父类属性是只读的,那么子类重写后的属性可以是只读的、也可以是可读写的
如果父类属性是可读写的,那么子类重写后的属性也必须是可读写的
- 重写实例属性
父类:
class Circle {
var radius: Int = 0
var diameter: Int {
set {
print("Circle setDiameter")
radius = newValue / 2
}
get {
print("Circle getDiameter")
return radius * 2
}
}
}
var circle: Circle
circle = Circle()
circle.radius = 6
// Circle getDiameter
// 12
print(circle.diameter)
// Circle setDiameter
circle.diameter = 20
// 10
print(circle.radius)
子类重写:
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 }
}
}
circle = SubCircle()
// SubCircle setRadius
circle.radius = 6
// SubCircle getDiameter
// Circle getDiameter
// SubCircle getRadius
// 12
print(circle.diameter)
// SubCircle setDiameter
// Circle setDiameter
// SubCircle setRadius
circle.diameter = 20
// SubCircle getRadius
// 10
print(circle.radius)
上面为什么要使用super.diameter?
如果不使用super,执行circle.diameter就会造成死循环,所以如果想要访问父类的属性请使用super。
- 重写类型属性
被class修饰的计算类型属性,可以被子类重写(class不能修饰存储类型属性)
被static修饰的类型属性(存储、计算),不可以被子类重写
父类:
class Circle {
static var radius: Int = 0
class var diameter: Int {
set {
print("Circle setDiameter")
radius = newValue / 2
}
get {
print("Circle getDiameter")
return radius * 2
}
}
}
Circle.radius = 6
// Circle getDiameter
// 12
print(Circle.diameter)
// Circle setDiameter
Circle.diameter = 20
// 10
print(Circle.radius)
子类重写:
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
}
}
}
SubCircle.radius = 6
// SubCircle getDiameter
// Circle getDiameter
// 12
print(SubCircle.diameter)
// SubCircle setDiameter
// Circle setDiameter
SubCircle.diameter = 20
// 10
print(SubCircle.radius)
4. 属性观察器
可以在子类中为父类属性(除了只读计算属性、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()
// SubCircle willSetRadius 10
// SubCircle didSetRadius 1 10
circle.radius = 10
- 添加属性观察器的存储实例属性
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()
// SubCircle willSetRadius 10
// Circle willSetRadius 10
// Circle didSetRadius 1 10
// SubCircle didSetRadius 1 10
circle.radius = 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 getRadius
// SubCircle willSetRadius 10
// Circle setRadius 10
// Circle getRadius
// SubCircle didSetRadius 20 20
circle.radius = 10
- 计算类型属性
class Circle {
class var radius: Int {
set {
print("Circle setRadius", newValue)
}
get {
print("Circle getRadius")
return 20
}
}
}
class SubCircle : Circle {
override static var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue)
}
didSet {
print("SubCircle didSetRadius", oldValue, radius)
}
}
}
// Circle getRadius
// SubCircle willSetRadius 10
// Circle setRadius 10
// Circle getRadius
// SubCircle didSetRadius 20 20
SubCircle.radius = 10
补充:final关键字
被final修饰的方法、下标、属性,禁止被重写
被final修饰的类,禁止被继承