1.类与结构体
1.1 类
class PersonClass {
//1.类中可以定义存储值的属性
var age : Int = 18
var name : String = "小明"
// 2.类中可以定义方法
func test() {
print("%@多少%@岁", self.name, self.age);
}
// 3.类中可以定义初始化器
// 指定初始化器 默认只有一个
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
// 便捷初始化器 必须从相同的类里调用另一个初始化器
// 安全性能原则:self及其属性已经初始化了
convenience init(_ age: Int) {
self.init(18, "小明")
self.age = age
self.name = "小王"
}
// 可失败初始化
init?(age: Int, name: String) {
if age < 18 {return nil}
self.age = age
self.name = name
}
// 必要初始化器 所有该类的子类都必须 实现该初始化器
required init(age1: Int, name1:String) {
self.age = age1
self.name = name1
}
// 类有析构函数, 结构体没有
deinit {
}
}
class SubPersonClass: PersonClass {
var subName: String
// 子类的所有属性必须在父类初始化之前初始化完成
// 子类初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖
// 确保成员变量访问安全
init(_ subName : String) {
self.subName = subName;
super.init(18, "小明")
}
}
1.2 结构体
// 结构体
struct PersonStruct {
//1.结构体中可以定义存储值的属性
var age : Int = 18
var name : String = "小明"
// 2.结构体中可以定义方法
func test() {
print("%@多少%@岁", self.name, self.age);
}
// 3.结构体中可以定义初始化器
// 与类不同,编译器会提供默认的初始化方法(前提是我们自己没有指定初始化器)
init(age : Int, name : String) {
self.age = age
self.name = name
}
}
1.3 类和结构体的相同点
- 定义存储值的属性
- 定义方法
- 定义下标以使用语法提供对其值的访问
- 定义初始化器
- 使用
extension
来拓展功能 - 遵循协议来提供某种功能
1.4 类和结构体的不同点
- 类有继承的特性,而结构体没有
- 类型转换使您能在运行时检查和解释class的实例对象的类型
- 类有析构函数用来释放其占用的资源, 而结构体没有
- 类有引用计数,允许对一个类实例有多个引用
- 类是引用类型,结构体是值类型
2. 引用类型和值类型
- 引用类型的变量并不直接存储具体的实例对象,而是对当前存储具体实例内存地址的引用。
- 值类型的变量存储的就是具体的实例(或者说具体的值)
- 引用类型存储在
堆
上 - 值类型存储在
栈
上
借助几个LLDB
指令来查看当前变量的内存结构
- po 只会输出对应的值
- p 则会输出返回值的类型以及命令结果
- x/8g: 读取内存中的值(8g: 8字节格式输出)
- frame varibale -L xxx:查看各实例内存信息
2.1 引用类型
引用赋值给var
、let
或者给函数传参,是将内存地址拷贝一份。类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件。属于浅拷贝
p
和p1
都引用了同一个PersonClass
的实例地址
p
和p1
在内存中的地址相差8个字节(存储着当前实例对象的内存地址)
2.2 值类型
值类型赋值给 var
、let
或者给函数传参,是直接将所有内容拷贝一份。类似于对文件进行复制粘贴操作,产生了全新的文件副本。属于深拷贝。
ps
和ps1
是内存当中存储的值,并且更改ps1
当中的age
或name
对ps
没有影响,因为ps
和ps1
存储的是两个不同的值
3.类的生命周期
iOS开发的语言不管是OC
还是Swift
后端都是通过LLVM
进行编译的,如下图所示:
OC
通过clang
编译器,编译成IR
,然后再生成可执行文件 .o
(这里也就是我们的机器码)而
Swift
则是通过Swift
编译器编辑成IR
,然后再生成可执行文件,具体编译过程如下:
swift code
经过-dump-parse
的命令来进行语法分析,解析成我们的AST
(也就是抽象语法树);- 通过
-dump-ast
语义分析来检查我们的语义,(例如类型检查是否准确安全);- 完成之后
Swift
代码将会降级为SIL
,也就是Swift
中间语言/代码(Swift intermediate language
);SIL
分为Raw SIL
(原生的,没有开启优化选项) 和SILOpt Canonical SIL
(经过优化的);- 最后通过
LLVM
降级为IR
,然后通过后端代码编译为不同架构的机器码
我们也可以通过swiftc
命令去生成对应的文件
// 分析输出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
SIL
文件中的重要标识可参考:SIL官方文档
接下来开始分析类的初始化
class Person {
var age : Int = 18
var name : String = "小明"
}
var p = Person()
通过swiftc main.swift -emit-sil > ./main.sil
生成.sil
文件,在.sil
文件中 注意到__allocating_init()
创建实例对象的函数
// Person.__allocating_init()
sil hidden [exact_self_class] @$s4main6PersonCACycfC : $@convention(method) (@thick Person.Type) -> @owned Person {
// %0 "$metatype"
bb0(%0 : $@thick Person.Type):
%1 = alloc_ref $Person // user: %3
// function_ref Person.init()
%2 = function_ref @$s4main6PersonCACycfc : $@convention(method) (@owned Person) -> @owned Person // user: %3
%3 = apply %2(%1) : $@convention(method) (@owned Person) -> @owned Person // user: %4
return %3 : $Person // id: %4
} // end sil function '$s4main6PersonCACycfC'
其中Person.Type
元类型的作用类似于 OC 里的isa
alloc_ref
根据官方文档中的解释
Allocates an object of reference type T. The object will be initialized with retain count 1; its state will be otherwise uninitialized. The optional objc attribute indicates that the object should be allocated using Objective-C's allocation methods (+allocWithZone:)(可以理解为去堆区申请内存空间)
进入汇编我们可以看到__allocating_init
的调用
进入到
__allocating_init
内部可以看到
__allocating_init
内部调用了一个函数swift_allocObject
通过 Swift源码 中的
HeapObject.cpp
文件可以发现
HeapObject *swift::swift_allocObject(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
CALL_IMPL(swift_allocObject, (metadata, requiredSize, requiredAlignmentMask));
}
SWIFT_RUNTIME_EXPORT
HeapObject *(*SWIFT_RT_DECLARE_ENTRY _swift_allocObject)(
HeapMetadata const *metadata, size_t requiredSize,
size_t requiredAlignmentMask) = _swift_allocObject_;
swift_allocObject
走的是内部函数_swift_allocObject_
,进入到_swift_allocObject_
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
assert(isAlignmentMask(requiredAlignmentMask));
auto object = reinterpret_cast<HeapObject *>(
swift_slowAlloc(requiredSize, requiredAlignmentMask));
// NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
// check on the placement new allocator which we have observed on Windows,
// Linux, and macOS.
new (object) HeapObject(metadata);
// If leak tracking is enabled, start tracking this object.
SWIFT_LEAKS_START_TRACKING_OBJECT(object);
SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);
return object;
}
跳转至swift_slowAlloc
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
void *p;
// This check also forces "default" alignment to use AlignedAlloc.
if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__) && SWIFT_STDLIB_HAS_DARWIN_LIBMALLOC
p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
p = malloc(size);
#endif
} else {
size_t alignment = (alignMask == ~(size_t(0)))
? _swift_MinAllocationAlignment
: alignMask + 1;
p = AlignedAlloc(size, alignment);
}
if (!p) swift::crash("Could not allocate memory.");
return p;
}
因此我们可以看出Swift
对象的内存分配流程如下
__allocating_init
--->swift_allocObject
---> _ swift_allocObject
---> swift_slowAlloc
--->Malloc
在_swift_allocObject_
函数里我们可以看到返回的是HeapObject
类型,因此Swift对象内存结构为HeapObject
进入到HeapObject
/// The Swift heap-object header.
/// This must match RefCountedStructTy in IRGen.
struct HeapObject {
/// This is always a valid pointer to a metadata object.
HeapMetadata const *__ptrauth_objc_isa_pointer metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
#ifndef __swift__
HeapObject() = default;
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
// Initialize a HeapObject header for an immortal object
constexpr HeapObject(HeapMetadata const *newMetadata,
InlineRefCounts::Immortal_t immortal)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Immortal)
{ }
#ifndef NDEBUG
void dump() const SWIFT_USED;
#endif
#endif // __swift__
};
我们可以看到HeapObject
有两个属性,默认占用 16 字节大小:
- 一个是
HeapMatadata
类型的metadata
,8字节,- 一个是
refCount
,8字节
通过源码最后可以得出
//HeapMataData的结构如下
struct HeapMataData {
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}