当前位置: 首页>编程语言>正文

swift制作framework swift5.1教程

1.编译流程

swift制作framework swift5.1教程,swift制作framework swift5.1教程_编译器,第1张

swift制作framework swift5.1教程,swift制作framework swift5.1教程_swift_02,第2张

操作

swiftc -dump-ast main.swift // 生成语法树
swiftc -emit-sil main.swift  // 生成最简洁的SIL代码
swiftc -emit-ir main.swift -o main.ll  // 生成LLVM IR代码
swiftc -emit-assembly main.swift -o main.s  // 生成汇编代码
Contents/Developer/Toolchains/XcodeDefailt.xctoolchain/usr/bin

2.基础语法

2.1 基本运算

简单略

2.2 流程控制

简单略

2.3 函数

1、基本函数

/// 求和 【概述】
///
/// 求和 【详述】
///
/// - Parameters:
///   - v1: 参数1
///   - v2: 参数2
///
/// - Note: 传入两个Int类型的参数相加
///
func sum(v1: Int, v2: Int) -> Int {
    return v1 + v2
}

var result = sum(v1: 10, v2: 20)
print(result)

swift制作framework swift5.1教程,swift制作framework swift5.1教程_objective-c_03,第3张

2、参数标签

  • 修改参数标签
func goToSleep(at time: String){
    print("It's time to Sleep -> \(time)")
}

goToSleep(at: "23:00")
  • 可以使用_省略参数标签
func sub(_ v1: Int, _ v2: Int) -> Int {
    return v1 - v2
}

var res = sub(10, 20)
print(res)

3、可变参数

func sum(_ numbers: Int...) -> Int {
    var res = 0
    for number in numbers {
        res += number
    }
    return res
}

print(sum(10,20,30,40,50))

// A parameter following a variadic parameter requires a label
func testFunc(_ numbers: Int..., name1:String, name2:String) {}

testFunc(10,20,30,41,name1: "MKJ", name2: "HY")

一个函数只能最多有一个可变参数
紧跟在可变参数后面的参数不能省略标签,报错 A parameter following a variadic parameter requires a label

4、输入输出参数
函数参数默认是let,默认是常量

var number = 10
func test(_ num: inout Int){
    num += 1
}

test(&number)
print(number)

可变参数不能标记为inout inout参数不能有默认值
inout参数只能传入可以被多次赋值的 var inout参数的本质是地址传递(引用传递)

5、函数重载 (Function Overload)

  • 函数名相同
  • 参数个数不同 或 参数类型不同 或 参数标签不同
  • 返回值的类型与函数重载无关
  • 默认参数和函数重载一起使用产生二义性时,编译器并不会报错(C++ 就会报错)
func sum(v1: Int, v2: Int) -> Int {
    v1 + v2
}

func sum(v1: Int, v2: Int, v3: Int = 10) -> Int {
    v1 + v2 + v3
}
// 有歧义,会调用第一个,但是不会报错
print(sum(v1: 10, v2: 20))

6、内联函数(函数调用直接展开成函数体)

如果开启了编译器优化,编译器会将某些函数变成内联函数(Release模式下默认开启了优化)

swift制作framework swift5.1教程,swift制作framework swift5.1教程_编译器_04,第4张

swift制作framework swift5.1教程,swift制作framework swift5.1教程_objective-c_05,第5张

虽然断点了,但是控制台会直接打印输出,函数体直接内联在了main函数里面

除非加了@inline关键字

@inline(never) func test() {
    print("Mikejing")
}
test()

以上代码就永远不会被内联,即使开了编译优化,@inline(__always)该标识符就代表即使代码很长,也会被内联(动态派发或者递归调用除非),这里的动态派发可以理解为子类父类的多态

哪些不会被自动内联
1.函数体比较长
2.包含递归
3.包含动态派发

2.4 枚举

变量的取值就几种,就应该考虑用枚举

  • 原始值 不占用枚举变量的内存
enum Season : Int {
    case spring = 1, summber, autumn, winter
}
// s是枚举类型,不是Int 类型, 原始值,一一对应,存储关联
var s = Season.spring
s = .autumn
print(s)
print(s.rawValue)
print(Season.winter.rawValue)
  • 关联值 和枚举存储在一起,占用内存
enum Date {
    case digit(year: Int, month: Int, day: Int)
    case string(String)
}

var date = Date.digit(year: 2021, month: 11, day: 2)

date = .string("Ten Grade-12-IEG")

print(date)

switch date{
case let .digit(year, month, day):
    print("\(year)-\(month)-\(day)")
case let .string(name):
    print("\(name)")
}
  • MemoryLayout 测试枚举内存
enum Password {
    case number(Int, Int, Int, Int)
    case other1
    case other2
}

var password = Password.number(1, 2, 3, 4)
print(MemoryLayout.size(ofValue: password)) // 实际用到的大小 33
print(MemoryLayout.stride(ofValue: password)) // 分配占用的空间大小 40
print(MemoryLayout.alignment(ofValue: password)) //  内存对齐 8

password = .other1

print(MemoryLayout.size(ofValue: password)) // 33
print(MemoryLayout.stride(ofValue: password)) // 40
print(MemoryLayout.alignment(ofValue: password)) // 8

由于关联值是和枚举存储在一起的,那么.number有四个Int,占用了32个字节,其他枚举都可以用一个字节来区分,因此只要33个字节,由于内存对齐是8个字节,因此,最终分配的内存是40个字节。

  • 特殊案例测试
enum Grade: String {
    case perfect = "A"
    case great = "B"
    case good = "C"
    case bad = "D"
}

// print(MemoryLayout<Grade>.size)  // 1
// print(MemoryLayout<Grade>.stride) // 1
// print(MemoryLayout<Grade>.alignment) // 1


enum Direction {
    case north, south, east, west
}

//print(MemoryLayout<Direction>.size)  // 1
//print(MemoryLayout<Direction>.stride) // 1
//print(MemoryLayout<Direction>.alignment) // 1



enum Score {
    case points(Int)
    case grade(Character)
    case other
}

// print(MemoryLayout<Score>.size)  // 17
// print(MemoryLayout<Score>.stride) // 24
// print(MemoryLayout<Score>.alignment) // 8




enum Date {
    case digit(year: Int, month: Int, day: Int)
    case string(String)
}

//print(MemoryLayout<Date>.size)  // 25
//print(MemoryLayout<Date>.stride) // 32
//print(MemoryLayout<Date>.alignment) // 8

enum TestEnum {
    case test0
    case test1
    case test2
    case test3(Int)
    case test4(Int, Int)
    case test5(Int, Int, Int, Bool)
}
//print(MemoryLayout<TestEnum>.size)  // 25
//print(MemoryLayout<TestEnum>.stride) // 32
//print(MemoryLayout<TestEnum>.alignment) // 8

根据上面的例子,其中原始值类型,占用都是一个字节。枚举Score类型,Charater占用了16个字节,再多一个字节,就能区分其他三个类型,因此只要17个字节,Date类型也一样。再来看最后一种类型TestEnum,个人理解是,当.test5类型下,最少需要25个字节,那么最后一个字节,就可以用来区分作为用其他枚举类型的字节位,如果用来表示.test5,只要有特定值即可,然后其他枚举有对应的特定值即可。

上述的内存分布可以往后查看

2.5 可选类型(optional)

swift制作framework swift5.1教程,swift制作framework swift5.1教程_swift_06,第6张

  • 可选项是对其他类型的一层包装,可以将它理解为一个盒子
  • 如果为nil,那么它是个空盒子
  • 如果不为nil,那么盒子里装的是:被包装类型的数据
  • 如果要从可选项中取出被包装的数据(将盒子里装的东西取出来),需要使用感叹号! 进行强制解包
  • 如果对值为nil的可选项(空盒子)进行强制解包,将会产生运行时错误
    Fatal error: Unexpectedly found nil while unwrapping an Optional value
可选项绑定

可以使用可选项绑定来判断可选项是否包含值
如果包含就自动解包,把值赋给一个临时的常量(let)或者变量(var),并返回true,否则返回false

示例一:

if let number = Int("123") { 
	print("字符串转换整数成功:\(number)") // number是强制解包之后的Int值
	// number作用域仅限于这个大括号
} else { 
	print("字符串转换整数失败")
}
// 字符串转换整数成功:123

示例二:条件语句中用到可选绑定,就需要,隔开,不能用&隔开

// 遍历数组,将遇到的正数都加起来,如果遇到负数或者非数字,停止遍历 
var strs = ["10", "20", "abc", "-20", "30"]
var index = 0
var sum = 0
while let num = Int(strs[index]), num > 0 {
	sum += num
	index += 1 
}
print(sum)
空合并运算符
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?
public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
  • a ?? b
  • a 必须是可选项
  • b 是可选项 或者 不是可选项
  • b 跟 a 的存储类型必须相同
  • 如果 a 不为nil,就返回 a
  • 如果 a 为nil,就返回 b
  • 如果 b 不是可选项,返回 a 时会自动解包

?? 返回的类型取决于最右边的类型

let a: Int? = 1
let b: Int = 2
let c = a ?? b // c Int, 1


let a: Int? = nil
let b: Int = 2
let c = a ?? b // c Int, 2

??if let配合使用

let a:Int? = nil
let b: Int? = 2
if let c = a ?? b{
	print(c)
}
// 等价于 if a != nil || b != nil

字符串插值消除警告

var name: String? = "Mk"
print("\(name)")
// String interpolation produces a debug description for an optional value; did you mean to make this explicit?

print("\(name!)")

print("\(String(describing: name))")

print("\(name ?? "")")
多重可选项
var num1: Int? = nil
var num2: Int?? = num1
var num3: Int?? = nil

(num2 ?? 1) ?? 2 // 2
(num3 ?? 1) ?? 2 // 1

swift制作framework swift5.1教程,swift制作framework swift5.1教程_编译器_07,第7张

也可以用lldb指令 frame variable -R 或者 fr v -R 查看区别

(lldb) fr v -R num1
(Swift.Optional<Swift.Int>) num1 = none {
  some = {
    _value = 0
  }
}

(lldb) fr v -R num2
(Swift.Optional<Swift.Optional<Swift.Int>>) num2 = some {
  some = none {
    some = {
      _value = 0
    }
  }
}

(lldb) fr v -R num3
(Swift.Optional<Swift.Optional<Swift.Int>>) num3 = none {
  some = some {
    some = {
      _value = 0
    }
  }
}

因此,上面的(num2 ?? 1) ?? 2第一个空合运算符,代表num2是有值的,只是一个有值的空盒子包装,第二个运算符就是nil,取2即可。

2.6 枚举内存布局

示例一:
可以看到该枚举实际占用了25个字节,内存对齐8个字节,因此实际分配了32个字节
可以看到前 24个字节用来存储,第25个字节用来区分具体哪个case,可以看出,这里其实有四种case,分别对应第25个字节的 01,02,03,00,如果是test0,test1,test2这三个case,都是03类型,然后通过第一个字节进行值区分

enum TestEnum {
    case test0
    case test1
    case test2
    case test3(Int)
    case test4(Int, Int)
    case test5(Int, Int, Int)
}

print(MemoryLayout<TestEnum>.size)  // 25
print(MemoryLayout<TestEnum>.stride) // 32
print(MemoryLayout<TestEnum>.alignment) // 8


/*
01 00 00 00 00 00 00 00
02 00 00 00 00 00 00 00
03 00 00 00 00 00 00 00
02
00 00 00 00 00 00 00
*/
var test1 = TestEnum.test5(1, 2, 3)
withUnsafePointer(to: &test1) { ptr in
    print("address-\(ptr)")
}
print("开始")


/*
04 00 00 00 00 00 00 00
05 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
01
00 00 00 00 00 00 00
*/
test1 = .test4(4, 5)
/*
 06 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00
 00 00 00 00 00 00 00
 */
test1 = .test3(6)
/*
 02 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 03
 00 00 00 00 00 00 00
 */
test1 = .test2;
/*
 01 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 03
 00 00 00 00 00 00 00
 */
test1 = .test1;
/*
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 03
 00 00 00 00 00 00 00
 */
test1 = .test0;

示例二:
该示例如果按上面的套路,应该是24 + 1 + 1 = 26个字节,但是实际只需要25个字节,可以看到这边的Bool类型这个字节可以用来做区分不同case,因此只需要25个字节即可,编译器不至于那么傻

enum TestEnum {
    case test0
    case test1
    case test2
    case test3(Int)
    case test4(Int, Int)
    case test5(Int, Int, Int,Bool)
}

print(MemoryLayout<TestEnum>.size)  // 25
print(MemoryLayout<TestEnum>.stride) // 32
print(MemoryLayout<TestEnum>.alignment) // 8


/*
 01 00 00 00 00 00 00 00
 02 00 00 00 00 00 00 00
 03 00 00 00 00 00 00 00
 81
 00 00 00 00 00 00 00
*/
var test1 = TestEnum.test5(1, 2, 3, true)
withUnsafePointer(to: &test1) { ptr in
    print("address-\(ptr)")
}
print("开始")


/*
 04 00 00 00 00 00 00 00
 05 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 40
 00 00 00 00 00 00 00
*/
test1 = .test4(4, 5)
/*
 06 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00
 00 00 00 00 00 00 00
 */
test1 = .test3(6)
/*
 02 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 C0
 00 00 00 00 00 00 00
 */
test1 = .test2;
/*
 01 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 C0
 00 00 00 00 00 00 00
 */
test1 = .test1;
/*
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 C0
 00 00 00 00 00 00 00
 */
test1 = .test0;

示例三:
该示例Bool类型不是最后一位,因此内存对齐的原因,实际都需要32位来支撑,但是不需要额外的一个字节来区分不同case,Bool类型占用的那8个字节,低地址最后一个就可以用来区分不同case,高地址01就可以用来表示Bool值了

enum TestEnum {
    case test0
    case test1
    case test2
    case test3(Int)
    case test4(Int, Int)
    case test5(Int, Int, Bool, Int)
}

print(MemoryLayout<TestEnum>.size)  // 32
print(MemoryLayout<TestEnum>.stride) // 32
print(MemoryLayout<TestEnum>.alignment) // 8


/*
 01 00 00 00 00 00 00 00
 02 00 00 00 00 00 00 00
 01 00 00 00 00 00 00 80
 03 00 00 00 00 00 00 00
*/
var test1 = TestEnum.test5(1, 2, true, 3)
withUnsafePointer(to: &test1) { ptr in
    print("address-\(ptr)")
}
print("开始")


/*
 04 00 00 00 00 00 00 00
 05 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 40
 00 00 00 00 00 00 00 00
*/
test1 = .test4(4, 5)
/*
 06 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 */
test1 = .test3(6)
/*
 02 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 C0
 00 00 00 00 00 00 00 00
 */
test1 = .test2;
/*
 01 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 C0
 00 00 00 00 00 00 00 00
 */
test1 = .test1;
/*
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 C0
 00 00 00 00 00 00 00 00
 */
test1 = .test0;

总结:
1.一个字节存储成员值(多个case的情况,如果有Bool的情况下,可以共用这8个字节或者这1个字节)
2.N个字节存储关联值(N取占用内存最大的关联值),任何一个case的关联值/原始值都共用这N个字节

3.AT&T汇编和lldb指令

3.1寄存器和内存

通常,CPU会先将内存中的数据存储到寄存器中,然后再对寄存器中的数据进行运算
假设内存中有块红色内存空间的值是3,现在想把它的值加1,并将结果存储到蓝色内存空间

  • 1.CPU首先会将红色内存空间的值放到rax寄存器中: movq 红色内存空间, %rax
  • 2.然后让rax寄存器与1相加:addq movq %rax, 蓝色内存空间x1, %rax
  • 3.最后/z将值赋值给内存空间:
  • x86和x64汇编根据编译器的不同,有两种书写格式
  • swift制作framework swift5.1教程,swift制作framework swift5.1教程_objective-c_08,第8张

  • Intel:Window派系
  • AT&T汇编 — iOS模拟器
  • AT&T:Unix派系
    咱们iOS开发,最主要的汇编语言是:
  • ARM汇编 — iOS真机

以下是常见汇编指令

  • rax,rbx,rcx,rdx,rsi,rdi,rbp,rsp
  • 项目

    AT&T

    Inter

    说明

    寄存器命名

    %rax

    rax

    操作数顺序

    movq %rax, %rdx

    mov rdx, rax

    将rax的值赋值给rdx

    常数、立即数

    movq $3, %rax

    movq $0x10 %rax

    mov rax, 3

    mov rax, 0x10

    将3赋值给rax,将0x10赋值给rax

    内存赋值

    movq $0xa, 0x1ff7(%rip)

    mov qword ptr [rip+0x1ff7], 0xa

    将0xa赋值给地址为rip+0x1ff7的内存空间

    取内存地址

    leaq -0x18(%rbp), %rax

    lea rax, [rbp-0x18]

    将rbp-0x18这个地址赋值给rax

    jmp指令

    jmp *%rdx

    jmp 0x4001002

    jmp *(%rax)

    jmp rdx

    jmp 0x4001002

    jmp [rax]

    call 和 jmp写法类似

    操作数长度

    movl %eax, %edx

    movb $0x10 %al

    leaw 0x10(%dx), %ax

    mov edx eax

    mov al 0x10

    lea ax, [dx + 0x10]

    b=byte (8-bit)

    s = short(16-bit integer or 32-bit floating point)

    w = word (16-bit)

    l = long (32-bit integer or 64-bit floating point)

    q = quad (64 bit)

    t = ten bytes (80 -bit floating point)

    16个常用寄存器

    • r8,r9,r10,r11,r12,r13,r14,r15
    • rax、rdx常作为函数返回值使用

    寄存器具体用途

    • rdi、rsi、rdx、rcx、r8、r9等常用于存放函数参数
    • rsp和rbp用于栈操作
    • rip作为指令指针(存储着CPU下一条要执行的指令的地址,一旦CPU读取一个指令,rip会自动指向下一个指令)
    • 3.2 lldb常用指令

    enum TestEnum { case test0 case test1 case test2 case test3(Int) case test4(Int, Int) case test5(Int, Int, Int) } print(MemoryLayout<TestEnum>.size) // 25 print(MemoryLayout<TestEnum>.stride) // 32 print(MemoryLayout<TestEnum>.alignment) // 8 var test1 = TestEnum.test5(10, 20, 30) // 断点到此处,开启汇编Debug模式
    • 读取寄存器的值
      register read/格式 、 register read/x
    • 修改寄存器的值
      register write 寄存器名称 数值 、 register write rax 0
    • 读取内存中的值
      x/数量-格式-字节大小 内存地址
      x/3xw 0x00000100
    • 格式
      x是16进制 f是浮点 d是十进制
    • 字节大小
      b - byte 1字节
      h -half word 2字节
      w - word 4字节
      g - giant word 8字节
    • 修改内存中的值
      memory write 内存地址 数值
      memory write 0x000001010
    • thread step-over、next、n
      单步运⾏行行,把子函数当做整体⼀一步执⾏行行(源码级别)
    • thread step-in、step、s
      单步运⾏行行,遇到子函数会进⼊入子函数(源码级别)
    • thread step-inst-over、nexti、ni
      单步运⾏行行,把子函数当做整体⼀一步执⾏行行(汇编级别)
    • thread step-inst、stepi、si
      单步运⾏行行,遇到子函数会进⼊入子函数(汇编级别)
    • thread step-out、finish
      直接执⾏行行完当前函数的所有代码,返回到上一个函数(遇到断点会卡住)

    看完上述基本的汇编指令和lldb指令,我们从汇编的角度看下枚举的汇编代码

    0x100001b5d <+2845>: movq   ripxa, 0x3b48(%rip)        ; Swift01.date : Swift01.Date + 28
        0x100001b68 <+2856>: leaq   0x3b41(%rip), %rax        ; Swift01.test1 : Swift01.TestEnum
        0x100001b6f <+2863>: movq   CPUx14, 0x3b3e(%rip)       ; Swift01.test1 : Swift01.TestEnum + 4
        0x100001b7a <+2874>: movq   six1e, 0x3b3b(%rip)       ; Swift01.test1 : Swift01.TestEnum + 12
        0x100001b85 <+2885>: movb   0x100001b5d <+2845>: movq   xa, 0x3b48(%rip)  
    可以看到 rip的地址就是下一条的地址  0x100001b68
    0x3b48 + 0x100001b68 = 0x1000056B0  把0xa 存入  0x1000056B0 q占用八个字节 以下依次类推
    0x100001b68 <+2856>: leaq   0x3b41(%rip), %rax  0x100001b6f + 0x3b41 = 0x1000056B0 地址放入 rax
    0x100001b6f <+2863>: movq   x14, 0x3b3e(%rip)  0x1000056B8 存 20
    0x100001b7a <+2874>: movq   x1e, 0x3b3b(%rip)  0x1000056C0 存 30
    0x100001b85 <+2885>: movb   x2, 0x3b3c(%rip)   0x1000056C8 
    存 2 类型关联值  b 一个字节x2, 0x3b3c(%rip)        ; Swift01.test1 : Swift01.TestEnum + 23

    可以看到如下汇编

    记住如下两条原则:

    • 存储的是指令的地址
    • 要执行的下一条指令地址就存储在rip中
    • 汇编单步调试,进入函数

    分析如下

    看到对应的地址下存储的值

    swift制作framework swift5.1教程,swift制作framework swift5.1教程_swift_09,第9张

    规律知识点
    1.内存地址格式为 0x4bdc(%rip),一般是全局变量,全局区(数据段)
    2.内存地址格式为 -0x78(%rbp),一般是局部变量,栈空间
    3.内存地址格式为 0x10(%rax),一般是堆空间
    4. 小括号里面放的都是内存地址
    5. r开头: 64bit 8个字节
    e开头: 32bit 4个字节
    ax,bx,cx:16bit, 2个字节
    ah,al:1个字节
    bh,bl
    movq 0xa %rax
    0xa 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0

    第一天结束,明天继续!!!!!!



    https://www.xamrdz.com/lan/5xc1962634.html

    相关文章: