1.延迟存储属性
class Teacher {
lazy var age:Int = 10
}
1.用lazy修饰的存储属性
2.延迟存储属性必须有一个默认的初始值
我们不给age赋默认值会报错,惰性属性必须具有初始化程序,如果我们修改为可选 lazy var age:Int?,还是报同样的错误
3.延迟存储属性第一次访问时才会被赋值
let t = Teacher()
t.age = 30
print("end")
我们在第一次访问之前和之后访问t的内存情况
我们看一下,加lazy和不加lazy,Teacher的大小是不是相同
print(class_getInstanceSize(Teacher.self))
不加lazy的大小是24,加lazy的大小是32
为什么加了lazy之后会增加8个字节呢
看sil代码
swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil
我们看到lazy修饰的属性,是一个optional的变量
我们第一次getter方式时
我们第一次获取属性的值时,Optional的值时none,经过bb2赋值后变成case是some值为10
Optional的值多大呢
我们可以通过下面的命令打印看出
print(MemoryLayout<Optional<Int>>.size)
print(MemoryLayout<Optional<Int>>.stride)
size为9,stride为16,Optional<Int>的尺寸大小,在后面会有详细介绍。stride为16是因为字节对齐。
4.延迟存储属性并不能保证线程安全
这就导致age被初始化两次
5.延迟存储属性对实例对象大小的影响
如果存储属性时Int,不是延迟存储属性,则占8字节,如果是延迟存储属性则占9字节,9因为不是8的倍数,可能会出现内存对齐分配的内存可能会更大。
2.类型属性
class Teacher {
static var age:Int = 10
}
1.用static修饰的存储属性
2.类型属性必须有一个默认值
如果不给默认值
3.类型属性只会被初始化一次
我们还是通过sil看
类型属性是用单利属性初始化的
3.单利的正确写法
1.OC的单利写法
static OCClass*_ocObject = nil;
+ (instancetype)shareManager {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_ocObject = [[OCClass alloc] init];
});
return _ocObject;
}
2.swift的单利写法
class Teacher {
static let sharedInstance:Teacher = Teacher()
private init() {}
}
使用static let创建声明一个实例对象
给当前init添加访问控制权限private
4.结构体的初始化
1.结构体不需要自定义初始化方法
struct Teacher {
var age:Int
var name:String
}
class Student {
var age:Int
var name:String
}
在class中会报 Class 'Student' has no initializers,这是因为编译器在结构体中自动帮助我们合成初始化方法,也就意味我们可以直接这样调用
var teach = Teacher(age: 12, name: "Hello")
查看sil代码
struct Teacher {
@_hasStorage var age: Int { get set }
@_hasStorage var name: String { get set }
init(age: Int, name: String)
}
2.如果我们的属性有初始化值,系统会提供不同的默认初始化方法
struct Teacher {
var age:Int = 18
var name:String = "Hello"
}
3.如果我们自定义初始化方法,系统就不会帮我们生成初始化方法
struct Teacher {
var age:Int = 18
var name:String = "Hello"
init(age:Int,name:String) {
self.age = age;
self.name = name
}
}
5.结构体是值类型
1.什么是类型
func test() {
var age = 18
var age2 = age
age = 30
age2 = 45
print("age \(age) age2 \(age2)")
}
在age = 30打断点和print处打断点查看内存情况
直接修改地址内的值18(0x12)-->30(0x1e) 18(0x12)--->45(0x2d)
用withUnsafeMutablePointer(to: &age){print(&0)}查看指针地址
1.值类型,地址中存储的是值
2.值类型传递的过程是传递的副本
6.mutating和inout
mutating
如果我们定义一个stack,类型为struct,
struct MyStack {
var items = [Int]()
func push(_ item:Int) {
items.append(item)
}
}
报错 Cannot use mutating member on immutable value: 'self' is immutable
我们暂且修改代码
struct MyStack {
var items = [Int]()
func push(_ item:Int) {
print("end")
}
}
查看sil
在push中我们只所以能够访问 items,是因为push内有一个默认的参数self,但是我们现在看到self是let,因为MyStack是值类型,let修饰后就不能改变MyStack的值了,所以在上面的items.append(item)就会报错,我们怎么解决这个问题呢?
用mutating修饰
struct MyStack {
var items = [Int]()
mutating func push(_ item:Int) {
items.append(item)
}
}
查看sil经过mutabting修饰之后默认参数self就是var类型的
mutabting的实质是让函数的参数增加inout修饰
2. inout
func swapTwoNumber(a:Int,b: Int) {
let temp = a
a = b
b = temp
}
这种写法报错 Cannot assign to value: 'a' is a 'let' constant
函数中参数默认是let类型,我们无法进行修改
如果我们想修改默认传入的参数,我们使用inout进行修饰
func swapTwoNumber(a:inout Int,b: Int) {
}
我们看sil,看inout做了什么
经过inout修饰的是地址取值,并用var修饰。而没用inout修饰的,简单的复制并且是let类型
7.结构体方法调用
结构体中的方法调度是静态调度(编译,链接完成之后当前的函数地址就已经确定存放在了代码段)
1.查看当前mach文件的符号表
nm mach文件路径
2.还原符号
xcrun swift-demangle s13SwiftProperty9MyTeacherV7teacheryyF