第八节属性、枚举rawValue 的原理

一.属性

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关键字 可以定义一个 延迟存储属性,即 在第一次用到时候才会进行初始化
截屏2020-07-31 下午12.03.35.png
  • 在定义存储属性时候,前面加上lazy

    打印结果:
    截屏2020-07-31 下午12.04.42.png
  • 注意: 经典应用


    截屏2020-07-31 下午12.07.46.png
  • 这里 属性后面有{},这里不是计算属性,因为image : Image 后面 有 等号(=), 正常的计算属性 后面不存在 = 号,这个相当于一个 赋值操作。这这里 相当于一个 闭包表达式,发送网络请求,获取图片,获取到后 赋值给 image。等价的操作是这个样子:

    截屏2020-07-31 下午12.12.45.png

  • 注意: lazy 属性 必须是var 不能是let


    截屏2020-07-31 下午12.17.36.png
  • lazy 不是线程安全的

  • 截屏2020-07-31 下午12.21.01.png

4. 属性观察器

  • 我们可以为非lazyvar存储属性 设置属性观察器
  • 定义: 注意: 这里是存储属性,并不是计算属性


    截屏2020-07-31 下午2.24.48.png
  • 这里面放的方法是willSetdidSet方法//即将设置、设置完成。
  • 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 
     }
   }
}
  • 类型属性 可以通过在属性前面 staticclass来定义。
    截屏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()

  • 你如果想要 属性存储下来 就用存储属性。
  • 计算属性 是当 存储属性跟计算属性相关联的情况 使用。
  • 一个属性 可以根据另外一个计算出来 ,就使用计算属性。

你可能感兴趣的:(第八节属性、枚举rawValue 的原理)