- 作者: Liwx
- 郵箱: 1032282633@qq.com
- 源碼: 需要
源碼
的同學, 可以在評論區
留下您的郵箱
iOS Swift 語法
底層原理
與內存管理
分析 專題:【iOS Swift5語法】00 - 匯編
01 - 基礎語法
02 - 流程控制
03 - 函數
04 - 枚舉
05 - 可選項
06 - 結構體和類
07 - 閉包
08 - 屬性
09 - 方法
10 - 下標
11 - 繼承
12 - 初始化器init
13 - 可選項
目錄
- 01-結構體
- 02-結構體的初始化器
- 03-思考下面代碼能通過么?
- 04-自定義初始化器
- 05-窺探初始化器的本質
- 06-結構體內存結構
- 07-類
- 08-類的初始化器
- 09-結構體與類的本質區別
- 10-值類型
- 11-值類型的賦值操作
- 12-引用類型
- 13-對象的堆空間申請過程
- 14-引用類型的賦值操作
- 15-值類型、引用類型的let
- 16-嵌套類型
- 17-枚舉、結構體、類都可以定義方法
01-結構體
- 在Swift標準庫中,
絕大多數
的公開類型都是結構體
,而枚舉
和類
只占很小一部分
- 比如
Bool、Int、Double、String、Array、Dictionary
等常見類型都是結構體
- 所有的結構體都有一個編譯器
自動生成的初始化器
(initializer, 初始化方法,構造器,構造方法) - 調用構造方法時,可以傳入
所有成員值
,用以初始化所有成員(存儲屬性
, Stored Property)
struct Date {
var year: Int
var month: Int
var day: Int
}
var date = Date(year: 2019, month: 6, day: 1)
02-結構體的初始化器
編譯器會根據情況,可能會為
結構體生成多個初始化器
,宗旨是:保證所有成員都有初始值
示例1
// 結構體所有存儲屬性都沒有設置初始值
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 10)
//var p2 = Point(y: 10) // 報錯: missing argument for parameter 'x' in call
//var p3 = Point(x: 10) // 報錯: missing argument for parameter 'y' in call
//var p4 = Point() // 報錯: missing arguments for parameters 'x', 'y' in call
- 示例2
// 結構體部分存儲屬性沒有設置初始值
struct Point {
var x: Int = 0
var y: Int
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
//var p3 = Point(x: 10) // 報錯: missing argument for parameter 'y' in call
//var p4 = Point() // 報錯: missing argument for parameter 'y' in call
- 示例3
// 結構體部分存儲屬性沒有設置初始值
struct Point {
var x: Int
var y: Int = 0
}
var p1 = Point(x: 10, y: 10)
//var p2 = Point(y: 10) // 報錯: missing argument for parameter 'x' in call
var p3 = Point(x: 10)
//var p4 = Point() // 報錯: missing argument for parameter 'x' in call
- 示例4
// 結構體所有存儲屬性都有設置初始值
struct Point {
var x: Int = 0
var y: Int = 0
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()
03-思考下面代碼能通過么?
-
可選項
都有個默認值nil
- 因此可以編譯通過
struct Point {
var x: Int?
var y: Int?
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()
04-自定義初始化器
- 一旦在定義結構體時
自定義了初始化器
,編譯器就不會
再幫它自動生成其他初始化器
struct Point {
var x: Int = 0
var y: Int = 0
init(x: Int, y: Int) {
self.x = x
self.y = y
}
}
var p1 = Point(x: 10, y: 10)
//var p2 = Point(y: 10) // error: missing argument for parameter 'x' in call
//var p3 = Point(x: 10) // error: missing argument for parameter 'y' in call
//var p4 = Point() // error: missing arguments for parameters 'x', 'y' in call
05-窺探初始化器的本質
- 以下2段代碼完全等效
struct Point {
var x: Int = 0
var y: Int = 0
}
var p = Point()
struct Point {
var x: Int
var y: Int
init() {
x = 0
y = 0
}
}
var p = Point()
- 通過匯編代碼對比,上面兩段代碼的
匯編代碼一模一樣
06-結構體和類`Point.init():
-> 0x100000bf0 <+0>: pushq %rbp
0x100000bf1 <+1>: movq %rsp, %rbp
0x100000bf4 <+4>: xorps %xmm0, %xmm0
0x100000bf7 <+7>: movaps %xmm0, -0x10(%rbp)
0x100000bfb <+11>: movq $0x0, -0x10(%rbp)
0x100000c03 <+19>: movq $0x0, -0x8(%rbp)
0x100000c0b <+27>: xorl %eax, %eax
0x100000c0d <+29>: movl %eax, %ecx
0x100000c0f <+31>: movq %rcx, %rax
0x100000c12 <+34>: movq %rcx, %rdx
0x100000c15 <+37>: popq %rbp
0x100000c16 <+38>: retq
06-結構體內存結構
struct Point {
var x: Int = 0
var y: Int = 0
var origin: Bool = false
}
print(MemoryLayout<Point>.size) // 17
print(MemoryLayout<Point>.stride) // 24
print(MemoryLayout<Point>.alignment)// 8
var p1 = Point(x: 10, y: 20, origin: true)
print(Mems.ptr(ofVal: &p1)) // 查看地址0x000000010000a6e8
// 0A 00 00 00 00 00 00 00
// 14 00 00 00 00 00 00 00
// 01 00 00 00 00 00 00 00
print(Mems.memStr(ofVal: &p1)) // 查看地址里面的內容 0x000000000000000a 0x0000000000000014 0x0000000000000001
image.png
07-類
- 類的定義和結構體類似,但編譯器并沒有為類
自動生成
可以傳入成員值的初始化器
// 結構體
struct Point {
var x: Int = 0
var y: Int = 0
}
let p1 = Point()
let p2 = Point(x: 10, y: 10)
let p3 = Point(y: 10)
let p4 = Point(x: 10)
// 類
class Point {
var x: Int = 0
var y: Int = 0
}
let p1 = Point()
//let p2 = Point(x: 10, y: 10) // error: argument passed to call that takes no arguments
//let p3 = Point(y: 10) // error: argument passed to call that takes no arguments
//let p4 = Point(x: 10) // error: argument passed to call that takes no arguments
- 如果類成員
未指定初始值
,編譯器不會自動生成無參的初始化器
class Point { // error: class 'Point' has no initializers
var x: Int
var y: Int
}
let p1 = Point() // error: 'Point' cannot be constructed because it has no accessible initializers
08-類的初始化器
- 如果類的
所有成員
都在定義的時候
指定了初始值
,編譯器會為類生成無參的初始化器
- 成員的
初始化
是在這個初始化器
中完成的
- 成員的
- 以下2段代碼完全等效
class Point {
var x: Int = 10
var y: Int = 20
}
let p1 = Point()
class Point {
var x: Int
var y: Int
init() {
x = 10
y = 20
}
}
let p1 = Point()
據匯編觀察, 2段代碼的匯編代碼一模一樣
06-結構體和類`Point.init():
0x1000019e0 <+0>: pushq %rbp
0x1000019e1 <+1>: movq %rsp, %rbp
0x1000019e4 <+4>: subq $0x60, %rsp
0x1000019e8 <+8>: movq $0x0, -0x8(%rbp)
0x1000019f0 <+16>: movq %r13, -0x8(%rbp)
-> 0x1000019f4 <+20>: movq %r13, %rax
0x1000019f7 <+23>: addq $0x10, %rax
0x1000019fb <+27>: xorl %ecx, %ecx
0x1000019fd <+29>: movl %ecx, %edx
0x1000019ff <+31>: leaq -0x20(%rbp), %rsi
0x100001a03 <+35>: movl $0x21, %edi
0x100001a08 <+40>: movq %rdi, -0x40(%rbp)
0x100001a0c <+44>: movq %rax, %rdi
0x100001a0f <+47>: movq %rsi, -0x48(%rbp)
0x100001a13 <+51>: movq -0x40(%rbp), %rax
0x100001a17 <+55>: movq %rdx, -0x50(%rbp)
0x100001a1b <+59>: movq %rax, %rdx
0x100001a1e <+62>: movq -0x50(%rbp), %rcx
0x100001a22 <+66>: movq %r13, -0x58(%rbp)
0x100001a26 <+70>: callq 0x100005436 ; symbol stub for: swift_beginAccess
0x100001a2b <+75>: movq -0x58(%rbp), %rax
0x100001a2f <+79>: movq $0xa, 0x10(%rax) ; 屬性x 賦值 10
0x100001a37 <+87>: movq -0x48(%rbp), %rdi
0x100001a3b <+91>: callq 0x100005454 ; symbol stub for: swift_endAccess
0x100001a40 <+96>: movq -0x58(%rbp), %rax
0x100001a44 <+100>: addq $0x18, %rax
0x100001a48 <+104>: leaq -0x38(%rbp), %rcx
0x100001a4c <+108>: movq %rax, %rdi
0x100001a4f <+111>: movq %rcx, %rsi
0x100001a52 <+114>: movq -0x40(%rbp), %rdx
0x100001a56 <+118>: movq -0x50(%rbp), %rax
0x100001a5a <+122>: movq %rcx, -0x60(%rbp)
0x100001a5e <+126>: movq %rax, %rcx
0x100001a61 <+129>: callq 0x100005436 ; symbol stub for: swift_beginAccess
0x100001a66 <+134>: movq -0x58(%rbp), %rax
0x100001a6a <+138>: movq $0x14, 0x18(%rax) ; 屬性y 賦值 20
0x100001a72 <+146>: movq -0x60(%rbp), %rdi
0x100001a76 <+150>: callq 0x100005454 ; symbol stub for: swift_endAccess
0x100001a7b <+155>: movq -0x58(%rbp), %rax
0x100001a7f <+159>: addq $0x60, %rsp
0x100001a83 <+163>: popq %rbp
0x100001a84 <+164>: retq
09-結構體與類的本質區別
-
結構體
是值類型
(枚舉也是值類型),類
是引用類型
(指針類型)
class Size {
var width = 1
var height = 2
}
struct Point {
var x = 3
var y = 4
}
func test() {
var size = Size() // size是指針變量,占用棧空間8個字節
var point = Point() // point是結構體變量,占用棧空間16個字節
}
- 下圖為64bit環境內存布局
QQ20200422-093229.png
-
判斷是否在堆空間
,查看匯編是否有調用alloc
或者malloc
class Size {
var width = 1
var height = 2
}
struct Point {
var x = 3
var y = 4
}
var size = Size() // __allocating_init, swift_allocObject, swift_slowAlloc, malloc
var point = Point() // 查看匯編代碼未調用alloc或malloc
print(Mems.size(ofRef: size)) // 32, Mems.size(value)引用類型變量value占用的堆空間大小
print("MemoryLayout<Size>.stride", MemoryLayout<Size>.stride) // 8, 在64bit環境引用類型占用棧空間內存始終是8
print("MemoryLayout<Point>.stride", MemoryLayout<Point>.stride) // 16
// size和point地址是??臻g,
print("size變量的地址", Mems.ptr(ofVal: &size)) // size變量的地址 0x00007ffeefbff040
print("size變量的內容", Mems.memStr(ofVal: &size)) // size變量的內容 0x000000010076ee50
print("size所指向內存的地址", Mems.ptr(ofRef: size)) // size所指向內存的地址 0x000000010076ee50
print("size所指向內存的內容", Mems.memStr(ofRef: size))// size所指向內存的內容 0x000000010000a500 0x0000000200000002 0x0000000000000001 0x0000000000000002
print("point變量的地址", Mems.ptr(ofVal: &point)) // point變量的地址 0x00007ffeefbff030
print("point變量的內存", Mems.memStr(ofVal: &point)) // point變量的內存 0x0000000000000003 0x0000000000000004
- 在Mac、iOS中的
malloc
函數分配的內存大小總是16的倍數
import Foundation
var ptr1 = malloc(16) // malloc函數在Foundation框架中,需import Foundation
print(malloc_size(ptr1)) // 16
var ptr2 = malloc(1)
print(malloc_size(ptr2)) // 16
var ptr3 = malloc(17)
print(malloc_size(ptr3)) // 32
10-值類型
- 值類型賦值給var、let或者給函數傳參,是直接將所有內容
拷貝
一份- 類似于對文件進行copy、paste操作, 產生了全新的
文件副本
.屬于深拷貝(deep copy)
- 類似于對文件進行copy、paste操作, 產生了全新的
func testValueType() {
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
var p2 = p1
p2.x = 11
p2.y = 22
print(p2.x , p2.y)
}
testValueType()
-
值類型深拷貝
QQ20200422-101724.png 匯編分析, p1先拷貝1份給p2,之后再對p2進行賦值操作
image.png
11-值類型的賦值操作
-
Swift標準庫中
, 為了能提升性能,String、Array、Dictionary、Set
采取了Copy On Write
的技術- 比如僅當有
"寫"操作
時,才會真正執行拷貝操作
- 對于標準庫值類型的賦值操作, Swift能確保最佳性能,所以沒必要為了保證最佳性能來避免賦值
- 比如僅當有
-
建議
: 不需要修改的,盡量定義成let
- 字符串
var s1 = "Jack"
var s2 = s1
s2.append("_Rose")
print(s1) // Jack
print(s2) // Jack_Rose
- 數組
var a1 = [1, 2, 3]
var a2 = a1
a2.append(4)
a1[0] = 2
print(a1) // [2, 2, 3]
print(a2) // [1, 2, 3, 4]
- 字典
var d1 = ["max": 10, "min": 2]
var d2 = d1
d1["other"] = 7
d2["max"] = 12
print(d1) // ["other": 7, "max": 10, "min": 2]
print(d2) // ["max": 12, "min": 2]
- 結構體賦值內存分析
struct Point {
var x: Int
var y: Int
}
var p1 = Point(x: 10, y: 20)
print(Mems.ptr(ofVal: &p1)) // 0x0000000107647780
print(Mems.memStr(ofVal: &p1)) // 0x000000000000000a 0x0000000000000014
p1 = Point(x: 11, y: 22)
print(Mems.ptr(ofVal: &p1)) // 0x0000000107647780
print(Mems.memStr(ofVal: &p1)) // 0x000000000000000b 0x0000000000000016
image.png
12-引用類型
-
引用賦值
給var、let或者給函數傳參,是將內存地址拷貝一份
- 類似于指針一個文件的
替身(快捷方式、鏈接)
, 指向的是同一個文件,屬于淺拷貝(shallow copy)
- 類似于指針一個文件的
func testReferenceType() {
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
var s1 = Size(width: 10, height: 20)
var s2 = s1
s2.width = 11
s2.height = 22
print(s1.width, s1.height) // 11 22
print(s2.width, s2.height) // 11 22
}
testReferenceType()
image.png
QQ20200422-111917.png
13-對象的堆空間申請過程
- 在Swift中,
創建類的實例對象
,要向堆空間申請內存,大概流程
如下- Class.__allocating_init()
- libswiftCore.dylib: swift_allocObject
- libswiftCode.dylib: swift_slowAlloc
- libsystem_malloc.dylib: malloc
- 在Mac、iOS中的malloc函數分配的內存大小總是
16的倍數
- 通過
class_getInstanceSize
可以得知: 類的對象至少需要占用多少內存
import Foundation
class Point {
// 指向類型信息 8
// 引用計數 8
var x = 11 // 8
var test = true // 1
var y = 22 // 8
} // 33, 40, 48
var p = Point() // malloc函數分配的內存大小總是16的倍數, 所以堆空間分配48個字節
print(class_getInstanceSize(type(of: p))) // 40
print(class_getInstanceSize(Point.self)) // 40 Point.self 相當于 [Point class] [p class]
print(Mems.size(ofRef: p)) // 48, 堆空間分配48個字節
14-引用類型的賦值操作
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
var s1 = Size(width: 10, height: 20)
print(Mems.ptr(ofVal: &s1)) // 0x0000000111fdca40
print(Mems.ptr(ofRef: s1)) // 0x000060000047d140
print(Mems.memStr(ofRef: s1)) // 0x0000000111fdc568 0x0000000200000002 0x000000000000000a 0x0000000000000014
s1 = Size(width:11, height: 22)
print(Mems.ptr(ofVal: &s1)) // 0x0000000111fdca40
print(Mems.ptr(ofRef: s1)) // 0x0000600000422e00
print(Mems.memStr(ofRef: s1)) // 0x0000000111fdc568 0x0000000400000002 0x000000000000000b 0x0000000000000016
image.png
15-值類型、引用類型的let
-
值類型
定義為let
的實例,不能修改
值類型實例的成員屬性
-
引用類型
定義為let
的實例,可以修改
引用類型實例的成員屬性
struct Point {
var x: Int
var y: Int
}
class Size {
var width: Int
var height: Int
init(width: Int, height: Int) {
self.width = width
self.height = height
}
}
let p = Point(x: 10, y: 20) // let代表p變量的內存不可修改, p結構體變量占用16個字節
//p = Point(x: 11, y: 22) // error: cannot assign to value: 'p' is a 'let' constant
//p.x = 33 // error: cannot assign to property: 'p' is a 'let' constant
//p.y = 44 // error: cannot assign to property: 'p' is a 'let' constant
let s = Size(width: 10, height: 20) // let代表s變量的內存不可修改 s指針變量占用8個字節
//s = Size(width: 11, height: 22) // error: cannot assign to value: 's' is a 'let' constant
s.width = 33
s.height = 44
-
let
修飾的字符串
,不能
對數組進行增刪改
操作
// let 修飾的字符串 不允許使用append等賦值操作
let str = "Jack"
// str.append("_Rose") // error: cannot use mutating member on immutable value
print(str)
-
let
修飾的數組
,不能
對數組進行增刪改
操作
// let修飾的數組,不能對數組進行增刪改操作
let arr = [1, 2, 3]
// arr[0] = 1 // error: cannot assign through subscript: 'arr' is a 'let' constant
// arr.append(4) // error: cannot use mutating member on immutable value
print(arr)
16-嵌套類型
- 嵌套類型的簡單使用
struct Poker {
enum Suit : Character {
case spades = "??"
case hearts = "??"
case diamonds = "??"
case clubs = "??"
}
enum Rank : Int {
case two = 2, three, four, five, six, seven, eight, nine, TernaryPrecedence
case jack, queen, king, ace
}
}
// 獲取嵌套類型的原始值
print(Poker.Suit.hearts.rawValue) // ??
var suit = Poker.Suit.spades
suit = .diamonds
var rank = Poker.Rank.five
rank = .king
17-枚舉、結構體、類都可以定義方法
- 一般把定義在
枚舉、結構體、類內部的函數
,叫做方法
- 方法占用對象的內存嗎?
不占用
- 方法的
本質
就是函數
- 方法、函數都存放在
代碼段
- 類定義方法
class Size {
var width = 10
var height = 10
func show() {
print("width = \(width), height = \(height)")
}
}
let s = Size()
s.show() // width = 10, height = 10
- 結構體定義方法
struct Point {
var x = 10
var y = 10
func show() {
print("x = \(x), y = \(y)")
}
}
let p = Point()
p.show() // x = 10, y = 10
- 枚舉定義方法
enum Poker : Character {
case spades = "??"
case hearts = "??"
case diamonds = "??"
case clubs = "??"
func show() {
print("face is \(rawValue)")
}
}
let pf = Poker.hearts
pf.show() // face is ??
- 匯編分析
方法、函數、全局變量、堆空間、局部變量(棧空間)
內存分布
func show1() {
print("show1")
}
class Point {
var x = 11
var y = 22
func show() {
var a = 10
print("局部變量(棧空間)", Mems.ptr(ofVal: &a))
print(x, y)
}
}
var p = Point()
p.show()
show1()
print("全局變量", Mems.ptr(ofVal: &p))
print("堆空間", Mems.ptr(ofRef: p))
-
方法、函數、全局變量、堆空間、局部變量(??臻g)
內存區域分布分析
Point.show: 0x100001740 (代碼區)
show1: 0x100001290 (代碼區)
全局變量 0x100007398 (全局區)
堆空間 0x10053e5d0 (堆空間)
局部變量(??臻g) 0x7ffeefbff3e8 (棧空間)
iOS Swift 語法
底層原理
與內存管理
分析 專題:【iOS Swift5語法】