第八节属性、枚举rawValue 的原理
2021-02-02 本文已影响0人
天明天
一.属性
1.Swift中跟实例相关的属性
可以分为两大类
(1) 存储属性:
- 截屏2020-07-30 下午5.04.05.png
- 类似于
成员变量
的概念 - 存储属性 直接存放在实例(或者结构体对象)对象的内存里面
- 结构体、类都可以定义存储属性,
枚举不可以定义存储属性
struct Persron {
var age : Int// 8个字节
var num: Int // 8个字节
}
var p = Persron(x:10,y:20)
print(MemoryLayout.stride(ofValue:p));// 16个字节
// 枚举不能有存储属性
emun Season {
case spring(Int)//关联值 存储在枚举类型里面
case summer(Double)
}
var s = Season.summer //只需要一个字节
// 存储关联值跟原始值 都存储在 枚举内存中
(2)计算属性:
计算属性的本质就是方法(函数)
- 不占用实例对象的内存
- 枚举、类、结构体 都可以定义计算属性。枚举里面可以定义方法,所以可以定义计算属性。
Struct Circle {
//存储属性
var radis : Double
// 计算属性
var next : Double {
set {
radis = newValue/2
}
get {
radis * 2 //获取next 的值的时候 调用,就一句代码,可以省略return
}
}
}
//使用
var c = Circele(radis : 10)
c.radis = 11
c.next = 40 // 效果:调用 set 方法,newValue = 40,最后 radis = 20
也可以 自定义参数 这样写
set (newRadis){
radis = newRadis / 2 //此时 40 会赋值给 newRadis
}
c.next //调用 get方法,`注意这里 Get 方法 省略了return 字段`
print(c.radis) //20
上面的结构体 实例占用多少内存? 答案是:8个字节。计算属性不占用对象的存储。本质是方法
(3)存储属性的注意点
- 存储属性 Swift 有明确规定:
在创建类或者结构体的实例对象时,必须为所有的存储属性 赋一个合适初始值
- 可以在初始化器里面为存储属性设置一个初始值,
- 可以分配一个默认的属性值作为属性定义的一部分
例如:
struct Point {
var nameX: Int
var nameY: Int
init(){
self.nameX = 10
self.nameY = 20
}
}
//调用
var p1 = Point()
//或者是这个样子写:
struct Point1 {
var x : Int = 10
var y : Int = 20
}
(4) 计算属性的注意点
- Set 传入的新值 默认叫做
newValue
,也可以自定义 - 只读属性 只有
get
没有set
方法
截屏2020-07-30 下午7.45.51.png -
只读的计算属性 只有get方法,可以省略 get 字段例如:
截屏2020-07-30 下午7.49.03.png
计算属性 只能用
var
修饰,不能用let
, 因为计算属性的值是可能发生变化的(即使是只读计算属性,也可以通过其他属性的值计算出来)
- 有 set方法 则必须要有 get。不能省略get方法
2. 枚举 rawValue
的原理
- 枚举 rawValue 的实现 就是依据 其计算属性来实现的,本质就是只读的计算属性,原始值 不占用内存,本质是方法
enum Seesson : Int {
case spring = 1, summer, autumn, winter
//只实现计算属性
var rawValue : Int{
// 只有get方法 可以省略
switch self {
case .spring:
return 1
case .summer:
return 2
case .autumn:
return 3
case .winter:
return 4
}
}
}
var s = Seesson.spring.rawValue
print(s)
3. 延迟存储属性
- 使用
lazy
关键字 可以定义一个 延迟存储属性,即 在第一次用到时候才会进行初始化
-
在定义存储属性时候,前面加上lazy
打印结果: 截屏2020-07-31 下午12.04.42.png
-
注意: 经典应用
截屏2020-07-31 下午12.07.46.png -
这里 属性后面有
截屏2020-07-31 下午12.12.45.png{}
,这里不是计算属性,因为image : Image
后面 有等号(=)
, 正常的计算属性 后面不存在 = 号,这个相当于一个 赋值操作。这这里 相当于一个 闭包表达式,发送网络请求,获取图片,获取到后 赋值给 image。等价的操作是这个样子:
-
注意: lazy 属性 必须是var 不能是let
截屏2020-07-31 下午12.17.36.png -
lazy 不是线程安全的
- 截屏2020-07-31 下午12.21.01.png
4. 属性观察器
- 我们可以为
非lazy
的var存储属性
设置属性观察器 -
定义: 注意: 这里是存储属性,并不是计算属性
截屏2020-07-31 下午2.24.48.png - 这里面放的方法是
willSet
、didSet
方法//即将设置、设置完成。 -
newValue
即将传入的值、oldValue
就是设置以前的值,初始化方法里面init()方法 并不会调用 willSet 跟didSet。
截屏2020-07-31 下午2.29.48.png -
计算属性 不能设置属性观察器
截屏2020-07-31 下午2.32.13.png
5. 全局变量、局部变量
-
属性观察器、计算属性的功能,同样可以应用在全局变量跟局部变量上
截屏2020-07-31 下午2.36.48.png
6. inOut的再次说明 输入输出参数
- 添加inOut的本质是 引用传递,就是地址值的传递
func setAge( _ num : inOut Int) -> Int{
age = 20
return age
}
let age = 10
setAge(&age)//这里调用 要使用修饰符 &,
-
inOut 传递的是存储属性时候,实际传递的是 属性的地址值,当传递的是 计算属性时候,先调用 他的get 方法,获取临时的地址值,然后把临时的地址值传入,最后调用set方法。
-
当inOut传入的是 带有属性观察器的存储属性时候,会调用
willSet跟didSet
方法 -
调用时机是 setAge 调用完成后,再修改的存储属性的值。
-
总结:
- 截屏2020-07-31 下午4.01.27.png
6.类型属性
属性分类:(1)实例属性:只能通过实例去访问,实例属性又分为:
- 存储实例属性:存储在实例的内存中,每个实例都有一份
- 计算实例属性
(2)类型属性: 只能通过类型去访问
- 存储类型属性:整个程序运行中 只有一份(类似于全局变量)
struct Sheap {
var x : Int = 0
var y : int = 0 //都是实例属性
static var count = 0;//存储类型属性,前面加上 static,只有一份内存。类似全局变量
}
Sheap.count = 10 //调用类型属性
- 计算类型属性:
struct Sheap {
var x : Int = 0
var y : int = 0 //都是实例属性
static var count : Int{ // 计算类型属性,也是前面 加上 static
get {
return 10
}
}
}
- 类型属性 可以通过在属性前面
static
跟class
来定义。
截屏2020-07-31 下午4.22.04.png
(3)类型属性的细节
- 类型存储属性 可以不用赋初始化值
- 多次调用 只会初始化一次,并且是线程安全的
-
类型存储属性 可以是 let 的类型
截屏2020-07-31 下午4.28.17.png - 本质还是内存问题。
(4)类型属性的经典应用:单例模式
public class FileManager {
// 公共类属性,常量 延时初始化,并且线程安全
public static let shared : FileManager = FileManager()
//私有标识,外部无法访问
private init(){
}
func openFile(){
}
}
//单利:类型存储属性
public class FileManager {
public static let shared = {
// ....
// ....
return FileManager()
}() //闭包表达式 赋值给存储类型属性
private init() { }
}
//使用
FileManager.shared.open()
- 你如果想要 属性存储下来 就用存储属性。
- 计算属性 是当 存储属性跟计算属性相关联的情况 使用。
- 一个属性 可以根据另外一个计算出来 ,就使用计算属性。