一、存储属性
存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性 (由 var 关键字引入)要么是常量存储属性(由 let 关键字引入)。
class LGTeacher{
var age: Int
var name: String
}
比如这里的 age 和 name 就是我们所说的存储属性,这里我们需要加以区分的是 let 和 var 两者的区别:从定义上:
let 用来声明常量,常量的值一旦设置好便不能再被更改; var 用来声明变量,变量的值可以在将来设置为不同的值。
结构体
class
let 和 var 的区别:
从汇编的角度看
var age = 19
let x = 20
从sil角度
let和var两个全局的变成了存储属性, let只有get方法,没有set方法,本质上let和var也是一种语法糖
private(set) var age = 19
sil代码如下
@_hasStorage @_hasInitialValue private(set) var age: Int { get set }
不改变存储属性,只是私有了set方法,相当于对外是只读的,在声明的范围是不影响的,set get都可以用
二、计算属性
错误
存储的属性是最常见的,除了存储属性,类、结构体和枚举也能够定义计算属性,计算属性并不 存储值,他们提供 getter 和 setter 来修改和获取值。对于存储属性来说可以是常量或变量,但计算属性必须定义为变量。于此同时我们书写计算属性时候必须包含类型,因为编译器需 要知道期望返回值是什么。
正确
class NBBankClass {
var age = 18
let name = ""
var address:String {
get{
return "ningbo"
}
set {
self.name = newValue
}
}
}
获取对象的内存(证明计算属性不占用内存)
# 40_iOS干货27_查看OC对象占用至少多少字节的方法
class NBCBBank{
var age :Int = 18
var name:Int = 20
var address:String {
set{
self.age = 20
}
get{
return "NINGBO"
}
}
let tex = ""
func test() {
print("准备调用")
var bank = NBCBBank()
//对象实际大小
print(class_getInstanceSize(NBCBBank.self))
//开辟的内存大小
print(malloc_size(Unmanaged.passUnretained(bank).toOpaque()))
}
}
对bank的memory操作只是得到栈区指针,不是堆区对象的大小,对象的对齐是16字节
sil中间代码
class NBBankClass {
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue final let name: String { get }
var address: String { get set } 计算属性没有@_hasStorage的标识
@objc deinit
init()
}
结构体中证明计算属性不占内存
存储属性占用内存
2个存储属性 16字节
在增加计算属性 不会变化
三、属性观察者
原类中的属性观察者
class NBBankClass {
var age = 18 {
willSet{
print("age will set value \(newValue)")
}
didSet {
print("age has been changed \(oldValue)")
}
}
func test(){
self.age = 20
}
}
属性观察者会观察用来观察属性值的变化,一个 willSet 当属性将被改变调用,即使这个值与 原有的值相同,而 didSet 在属性已经改变之后调用。它们的语法类似于 getter 和 setter。
查看sil代码
可以看到原来 都是在 setter 方法中先调用willset 再赋值 最后调用 didset 。
这里我们在使用属性观察器的时候,需要注意的一点是在初始化期间设置属性时不会调用 willSet 和 didSet 观察者;只有在为完全初始化的实例分配新值时才会调用它们。运行下面这 段代码,你会发现当前并不会有任何的输出。
class NBBankClass {
var age = 18 {
willSet{
print("age will set value \(newValue)")
}
didSet {
print("age has been changed \(oldValue)")
}
}
init(_ age:Int){
self.age = age
}
}
上面的属性观察者只是对存储属性起作用,如果我们想对计算属性起作用怎么办?很简单,只需 将相关代码添加到属性的 setter
var age = 18 {
willSet{
print("age will set value \(newValue)")
}
didSet {
print("age has been changed \(oldValue)")
}
}
var address:String {
set{
//我即将修改xx
age = 10
//我修改完了
}
get{
return "ningbo"
}
}
子类中的属性观察者
顺序是 子类中属性的willset -> 父类中属性的willset -> 父类中属性的didset -> 子类中属性的didset
四、延迟存储属性
- 用关键字 lazy 来标识一个延迟存储属性,必须有初始值。
class NBBankClass {
lazy var age :Int = 18
}
-
延迟存储属性的初始值在其第一次使用时才进行计算。
@_hasStorage @_hasInitialValue final var __lazy_storage__age:
Int?{ get set } 可选项
// NBBankClass.age.getter
sil hidden [lazy_getter] [noinline] @$s8NBCBBank11NBBankClassC3ageSivg : $@convention(method) (@guaranteed NBBankClass) -> Int {
// %0 "self" // users: %14, %2, %1
bb0(%0 : $NBBankClass):
debug_value %0 : $NBBankClass, let, name "self", argno 1 // id: %1
%2 = ref_element_addr %0 : $NBBankClass, #NBBankClass.$__lazy_storage_$_age // user: %3
%3 = begin_access [read] [dynamic] %2 : $*Optional<Int> // users: %4, %5
%4 = load %3 : $*Optional<Int> // user: %6
end_access %3 : $*Optional<Int> // id: %5
switch_enum %4 : $Optional<Int>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 // id: %6
// %7 // users: %9, %8
bb1(%7 : $Int): // Preds: bb0
debug_value %7 : $Int, let, name "tmp1" // id: %8
br bb3(%7 : $Int) // id: %9
bb2: // Preds: bb0
%10 = integer_literal $Builtin.Int64, 18 // user: %11
%11 = struct $Int (%10 : $Builtin.Int64) // users: %18, %13, %12
debug_value %11 : $Int, let, name "tmp2" // id: %12
%13 = enum $Optional<Int>, #Optional.some!enumelt, %11 : $Int // user: %16
%14 = ref_element_addr %0 : $NBBankClass, #NBBankClass.$__lazy_storage_$_age // user: %15
%15 = begin_access [modify] [dynamic] %14 : $*Optional<Int> // users: %16, %17
store %13 to %15 : $*Optional<Int> // id: %16
end_access %15 : $*Optional<Int> // id: %17
br bb3(%11 : $Int) // id: %18
// %19 // user: %20
bb3(%19 : $Int): // Preds: bb2 bb1
return %19 : $Int // id: %20
} // end sil function '$s8NBCBBank11NBBankClassC3ageSivg'
switch_enum %4 : $Optional, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 // id: %6
从地址 取值之前先判断是否为空,为空的话就去走bb2 去创建赋值,不为空就直接返回当前的值
从lldb查看
使用之后
Version:0.9 StartHTML:0000000105 EndHTML:0000000302 StartFragment:0000000141 EndFragment:0000000262
五、类型属性
class NBBankClass {
static var age :Int = 18
}
- 类型属性其实就是一个全局变量
sil中间代码
@_hasStorage @_hasInitialValue `static` var age: Int { get set }
// one-time initialization token for age
sil_global private @$s8NBCBBank11NBBankClassC3age_Wz : $Builtin.Word
// static NBBankClass.age
sil_global hidden @$s8NBCBBank11NBBankClassC3ageSivpZ : $Int
- 类型属性只会被初始化一次
swiftc -emit-ir 转为IR语法 builtin once 初始化一次
swift_once是什么呢?其实就是我们gcd里面的dispatch_once 只执行一次
单利的实现
class NBCBBankClass {
static let sharedInstance = NBCBBankClass()
// 指定初始化器私有化,外界访问不到
private init(){}
}
NBCBBankClass.sharedInstance
六、在macho中定位到属性的地址
第一篇文章我们讲了类的数据结构
struct Metadata{
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
}
其中typeDescriptor在第二篇文章也有 ,这里面记录了 V-Table 的相关信息,接下来我们需要认识一下 typeDescriptor 中的 fieldDescripto
struct TargetClassDescriptor{
var flags: UInt32
var parent: UInt32
var name: Int32
var accessFunctionPointer: Int32
var fieldDescriptor: Int32
var superClassType: Int32
var metadataNegativeSizeInWords: UInt32
var metadataPositiveSizeInWords: UInt32 var numImmediateMembers: UInt32
var numFields: UInt32
var fieldOffsetVectorOffset: UInt32
var Offset: UInt32
var size: UInt32
//V-Table
}
fieldDescriptor 记录了当前的属性信息,其中 fieldDescriptor 在源码中的结构如下:
struct FieldDescriptor {
MangledTypeName int32
Superclass int32
Kind uint16
FieldRecordSize uint16
NumFields uint32
FieldRecords [FieldRecord]
}
其中 NumFields 代表当前有多少个属性, FieldRecords 记录了每个属性的信息, FieldRecords 的结构体如下:
struct FieldRecord{
Flags uint32
MangledTypeName int32
FieldName int32
}
题外话:
写博客的时候鼠标触摸不小心让网页突然变大,谷歌网页整体的缩放都正常,只有当前的页面有问题。 解决方式: 1 、用 Magic Mouse 的,在 Mouse 表面轻触两下,不是按下两下,是触摸两下,不能让机械按键动作,放大和缩小都是这样操作; 2 、机身触摸板,用两手指同时轻触两下,也是不让机械按键动作,放大和缩小都是一样操作。