- 作者: 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环境内存布局
-
判断是否在堆空间
,查看汇编是否有调用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地址是栈空间,
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进行赋值操作
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))
-
方法、函数、全局变量、堆空间、局部变量(栈空间)
内存区域分布分析
Point.show: 0x100001740 (代码区)
show1: 0x100001290 (代码区)
全局变量 0x100007398 (全局区)
堆空间 0x10053e5d0 (堆空间)
局部变量(栈空间) 0x7ffeefbff3e8 (栈空间)
iOS Swift 语法
底层原理
与内存管理
分析 专题:【iOS Swift5语法】
网友评论