方法(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 = https://www.cnblogs.com/funkyRay/p/[
[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")
}
}
如果子類里沒有重寫父類方法,那么類資訊里的方法串列會有父類的方法,以及自己新增的方法
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/275004.html
標籤:iOS
上一篇:細談Activity四種啟動模式
