特别备注
本系列文章总结自MJ老师在Swift编程从入门到精通-MJ大神精选,相关图片素材均取自课程中的课件。
类型(static)存储属性的本质
前面我们介绍static存储属性的时候,提到了它实际上是全局变量,现在来证明一下,首先我们看看普通的全局变量是怎么样的
var num1 = 10
var num2 = 11
var num3 = 12
运行至断点处,汇编如下
static存储属性如下
var num1 = 10
class Car {
static var num2 = 1
}
Car.num2 = 11
var num3 = 12
print(num1)
打开断点运行分析汇编
可以看到,num1 Car.num2 num2和实例一一样,都是全局数据段上一段连续空间分配的三个全局变量的地址 ,因此虽然num2
作为Car
的static
存储属性,但是从它在内存中的位置来看,跟普通的全局变量没有区别,因此可以说static存储属性的本质就是全局变量。
继续追踪汇编代码
var num1 = 10
class Car {
static var num2 = 1
}
//Car.num2 = 11 //
var num3 = 12
汇编里Car.num2
相关的代码就消失了,也就是说如果没有用到Car.num2
,那么它是不会被初始化的,因此我们说static
存储属性是默认lazy
(延迟)的。
var num1 = 10
class Car {
static var num2 = 1
}
Car.num2 = 11
var num3 = 12
看到在最后,调用了swift_once
函数,GCD里面我们知道有个dispatch_once
,我们进入这个函数
swift_once
函数里面确实是调用了GCD的dispatch_once_f
,GCD里面我们知道有个dispatch_once
,保证了方法只执行一次,那么dispatch_once
里面的block
是什么呢?应该就是Car.num2
的初始化代码,也就是这句代码static var num2 = 1
dispatch_once({
Car.num2 = 1
})
如何证明呢?从函数初始化出进去慢慢走一下流程,进入Car.num2.unsafeMutableAddressor
进入libswiftCore.dylib
swift_once:`继续看调用前的参数寄存器
在值初始化出加入断点
继续运行程序,可以看到进入到TestSwift
globalinit_33_A16532DE6A2F9802E7D47DD572722A30_func0:`
从初始值很明显看出这段内存就是num2
,并且跟我们在unsafeMutableAddressor
函数返回处记录的返回值相同,结果正如预期,证明完毕。方法
来对
num2
进行初始化的,因为使用了GCD
的dispatch_once
,因此我们说static
存储属性是线程安全的,并且只能被初始化一次。
方法
class Car {
static var count = 0
init() {
Car.count += 1
}
static func getCount() -> Int {
//以下几种访问count的方法是等价的
count += 1
self.count += 1
Car.self.count += 1
Car.count += 1
return count
}
}
let c0 = Car()
let c1 = Car()
let c2 = Car()
print(Car.getCount()) // 通过类名进行调用
枚举、结构体、类都可以定义实例方法、类型方法
-
实例方法(
Instance Method
):通过实例对象进行调用 -
类型方法(
Type Method
):通过类型调用,用static
或者class
关键字来定义
self
- 在实例方法中就代表实例对象
- 在类型方法中就代表类型
在类型方法static func getCount
中,以下几种写法等价
count
self.count
Car.count
Car.self.count
mutating
Swift语法规定,对于结构体和枚举这两种值类型,默认情况下,他们的属性是不能被自身的实例方法所修改的(对于类没有这个规定)
- 在
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
的语法类似于实例方法、计算属性,它的本质就是方法(函数)
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
为我们提供了通过[i]
的方式去访问成员变量,就像数组/字典那样去使用。下标与函数的表面区别,只是在定义的时候,用subscript
代替了func funcName
,在调用的时候通过[arg]
代替了funcName(arg)
。而subscript
的内部包含了get
和set
,很像计算属性。
注意点️
- subscript 中定义的返回值类型可以决定:
-
get
方法的返回值类型 -
set
方法中国呢newValue
的类型
-
-
subscript
可以接受多个参数,并且是任意类型
下标的细节
subscript
可以没有set
方法,但是必须要有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
方法,还可以省略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
}
}
- 可以设置参数标签
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]) // 如果有标签的话,在使用的时候,就一定要带上标签才行
上面我们看到的subscript
都是相当于实例方法(默认),下标也可以是类型方法
class Sum {
static subscript(v1: Int, v2: Int) -> Int {
return v1 + v2
}
}
print(Sum[10,20])
结构体、类作为返回值的对比
struct Point {
var x = 0
var y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
set { point = newValue } // 如果后面有堆point进行赋值,则必须要加上set方法。
get { point }
}
}
var pm = PointManager()
pm[0].x = 11
pm[0].y = 22
print(pm[0])
print(pm.point)
PointManager
这个类有一个下标,返回类型是结构体struct Point
,并且注意这个下标的特点,无论下标值传什么,它返回的都是结构体变量point
,我们需要注意的是,下标里面的set
的写法应该如下
set { point = newValue }
这样你可能会好奇,pm[0].x = 11
或者 pm[0].y = 22
时,在set方法里面我们怎么知道这个newValue
的值到底是给.x
还是给.y
的。其实你应该注意到,这里的newValue应该是struct Point
类型的,如果这样,其实设计者的思路就不难猜到
pm[0].x = 11
---> newValue = (11, pm[0].y)
---> set { point = newValue = (11, pm[0].y) }
pm[0].y = 22
---> newValue = (pm[0].x, 22)
---> set { point = newValue = (pm[0].x, 22) }
如果把strtct Point
换成 class Point
, 这个set
方法就可以不用写了
class Point {
var x = 0
var y = 0
}
class PointManager {
var point = Point()
subscript(index: Int) -> Point {
get { point }
}
}
var pm = PointManager()
pm[0].x = 11
pm[0].y = 22
print(pm[0])
print(pm.point)
因为我们通过pm[0]
拿到的是point
这个对象实例指针,那么pm[0].x
等价于point.x
,所以point.x = 11
是符合规范的
接收多个参数的下标
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]]
继承(Inheritance)
继承的内存结构
重写 override
子类可以重写父类的下标 , 方法 , 属性
,==必须加上override
关键字==.
重新写实例方法 , 下标
class Animal {
func speak() {
print("Animal speak")
}
subscript(index:Int) -> Int {
return index
}
}
class Cat: Animal {
//重写父类实例方法
override func speak() {
super.speak()
print("Cat Speak")
}
//重写父类下标
override subscript (index: Int)-> Int {
return super[index] + 1
}
}
var anim = Cat()
anim.speak()
print(anim[6])
run
Animal speak Cat Speak 7
重写类型方法 , 下标
- 被class修饰的类型方法、下标,允许被子类重写
- 被static修饰的类型方法、下标,不允许被子类重写
class Animal {
class func speak() {
print("Animal speak")
}
class subscript(index:Int) -> Int {
return index
}
}
class Cat: Animal {
override class func speak() {
super.speak()
print("Cat Speak")
}
override class subscript (index: Int)-> Int {
return super[index] + 1
}
}
Cat.speak()
print(Cat[6])
run
Animal speak Cat Speak 7
被static修饰的类型方法、下标,不允许被子类重写
父类使用class修饰的类型方法,子类可以使用static修饰
重写属性
重写实例属性
子类可以将父类的存储属性,计算属性重写为计算属性
.注意:只能重写为计算属性.
class Circle {
var radius: Int = 0
var diameter: Int {
set {
print("Circle setDiameter")
radius = newValue / 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 circle : Circle
run
SubCircle setRadius
print(circle.diameter)
run
SubCircle getDiameter Circle getDiameter SubCircle getRadius 12
circle.diameter = 20
run
SubCircle setDiameter Circle setDiameter SubCircle setRadius
print(circle.radius)
run
SubCircle getRadius 10
重写类型属性
- 被
static
修饰的属性不可以被子类重写 - 被
class
修饰的属性才可以被子类重写
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
}
}
}
class SubCircle : Circle {
override class var diameter: Int {
set {
print("SubCircle setDiameter")
super.diameter = newValue > 0 ? newValue : 0
}
get {
print("SubCircle getDiameter")
return super.diameter
}
}
}
- 被
static
修饰的属性不可以被子类重写
属性观察器
可以通过override
为父类属性添加属性观察器.但是不能为let属性 , 或者只读的计算属性
添加.因为
let属性和只读计算属性都不允许更改值,所以根本没必要添加属性观察器.
class Circle {
var radius: Int = 1
}
class SubCircle : Circle {
override var radius: Int {
willSet{
print("SubCircle willSetRadius",newValue)
}
didSet {
print("SubCircle didSetRadius",oldValue)
}
}
}
var circle = SubCircle()
circle.radius = 10
run
SubCircle willSetRadius 10 SubCircle didSetRadius 1 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() circle.radius = 10
Run
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 SetRadius",oldValue,radius)
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
run
Circle getRadius SubCircle willSetRadius 10 Circle setRadius 10 Circle getRadius SubCircle didSetRadius 20 20
final
如果我们不想让我们的类被外界继承;或者不想让我们写的方法,属性,下标被重写,可以加上final
关键字.
特别备注
本系列文章总结自MJ老师在Swift编程从入门到精通-MJ大神精选,相关图片素材均取自课程中的课件。