Swift学习笔记(1)-- 类与结构体(上)

192 阅读4分钟

类与结构体

类与结构体的相同点和区别

相同点:

  • 定义存储属性;
  • 定义方法;
  • 定义初始化器;
  • 定义下标以使用下标语法提供对其值的访问;
  • 通过extension来扩展功能;
  • 遵循协议来提供某种功能;

区别:

  • 类是引用类型,结构体是值类型;
  • 类有类型转换,可以在运行时检查和解释类实例的类型;
  • 类可以继承,结构体不可以;
  • 引用计数允许对一个实例有多个引用;
  • 类有析构函数,用来释放其分配的资源;

调试过程常用命令:

//打印变量的内存地址
(lldb) po withUnsafePointer(to: &t){print($0)}
0x00007ff7b75c5ec8
0 elements
 
 //查看内存地址的类型,需要配置一个脚本 (链接: https://pan.baidu.com/s/1tZU8_KtK9VtS9pTmgwQQIQ  提取码: murc )
 (lldb) cat address 0x00007ff7b951ce90
address:0x00007ff7b951ce90, stack address (SP: 0x7ff7b951ce50 FP: 0x7ff7b951cec0) SwiftDemo.ViewController.test() -> ()

//输出对应的值
 (lldb) po t
 <LZTeacher: 0x6000012b08d0>

//输出对应的值,并返回值的类型以及命令结果的引用名
 (lldb) p t
 (SwiftDemo.LZTeacher) $R1 = 0x00006000012b08d0 (age = 19, name = "jim")
 
 //读取内存中的值(8g:8字节格式输出)
 (lldb) x/8g 0x00007ff7b951ce90
0x7ff7b951ce90: 0x0000000000000013 0x0000000000007373
0x7ff7b951cea0: 0xe200000000000000 0x0000600001f429a0
0x7ff7b951ceb0: 0x00007f848bf0e3b0 0x00007f848bf0e3b0
0x7ff7b951cec0: 0x00007ff7b951cef0 0x00000001069e1b54
 
 //查看变量的内存地址
 (lldb) frame variable -L t
 scalar: (SwiftDemo.LZTeacher) t = 0x00006000012b08d0 {
 0x00006000012b08e0:   age = 19
 0x00006000012b08e8:   name = "jim"
 }

存储属性

let 和 var的区别: 从汇编角度: 从SIL的角度:

类的初始化器

当前的类编译器默认不会自动提供成员初始化器,如果自己不定义初始化器的话会报错;

结构体的话,编译器会提供默认的初始化器(前提是我们自己没有指定初始化器)

class Person{
    var age = 19
    var name = "dd"
    //初始化器,也就是构造函数
    //指定初始化器,默认只有一个
    init(_ age: Int, _ name: String) {
        self.age = age
        self.name = name
    }
    
    //其他的初始化器都定义为便捷初始化器
    //便捷初始化器,必须从相同类里面调用另一个初始化器(安全考虑,保证所有的成员都已经被初始化),否则会报错
    convenience init() {
        self.init(19, "")//这个初始化器必须先调用,否则会报错
        self.age = 16
        self.name = "ff"
    }
}
struct Person{
    var age = 19
    var name = "dd"
}

从Person类中派生出一个子类Teacher,并指定一个初始化器,如下所示:

class Teacher: Person{
    var sex: String
    
    init(_ sex: String){
        self.sex = sex //指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成
        super.init(19, "jj") //指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖
    }
}

必要初始化器

在类的初始化器前添加 required 修饰符来表明所有该类的子类都必须实现该初始器。

class Person{
    var age = 19
    var name = "dd"
    
    required init(_ age: Int, _ name: String) {
        self.age = age
        self.name = name
    }
...
}

class Teacher: Person{
    var sex: String
    required init(_ age: Int, _ name: String) {
        fatalError("init(_:_:) has not been implemented")
    }
}

类生命周期

iOS 开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的,如图所示:

image.png

Swift的编译过程:

image.png

如图所示的编译流程,对应的命令如下:

// 分析输出AST 
swiftc main.swift -dump-parse

// 分析并且检查类型输出AST 
swiftc main.swift -dump-ast

// 生成中间体语言(SIL),未优化
swiftc main.swift -emit-silgen

 // 生成中间体语言(SIL),优化后的
swiftc main.swift -emit-sil

// 生成LLVM中间体语言 (.ll文件)
swiftc main.swift -emit-ir

// 生成LLVM中间体语言 (.bc文件)
swiftc main.swift -emit-bc

// 生成汇编
swiftc main.swift -emit-assembly 

// 编译生成可执行.out文件
swiftc -o main.o main.swift

Swift对象内存分配

底层调用过程: __allocating_init -----> swift_allocObject -----> _swift_allocObject_ -----> swift_slowAlloc -----> Malloc

Swift 对象的内存结构HeapObject(OC objc_object),有两个属性:一个是Metadata,一个是RefCount,默认占用16字节大小。

struct HeapObject {
    HeapMetadata const *metadata;
}

Swift类的数据结构(看源码得出的结论):

image.png

生成sil中间文件的脚本,可以在xcode中配置,配置之后运行可以生成sil文件:

swiftc -emit-sil ${SRCROOT}/LGSwiftTest/main.swift > ./main.sil && open main.sil

SIL文件中一些语法解释:

@main : 入口函数,@作为当前的标识符

%0:寄存器,相当于常量

使用命令: xcrun swift-demangle xxx  可以翻译下面的符号值
@$s4main10HeapObjectV8metadataSVvs

sil的文档: github.com/apple/swift…

类的内存结构的探索

swift源码:github.com/apple/swift