swift5-属性

Posted by AndyCao on September 9, 2019

1. 属性

在swift中,跟实例相关的属性,可以分为2大类:

  • 存储属性(stored property)
    • 类似于成员变量这个概念
    • 存储在实例的内存中
    • 结构体、类可以定义存储属性
    • 枚举不可以定义存储属性
  • 计算属性(computed property)
    • 本质是方法(函数)
    • 不占用实例的内存
    • 枚举、结构体、类都可以定义计算属性
struct Circle {
    // 存储属性
    var radius: Double
    // 计算属性
    var diameter: Double {
        set {
            radius = newValue / 2
        }
        get {
            radius * 2
        }
    }
}

存储属性

关于存储属性,swift有个明确的规定: 在创建类 或 结构体的实例时, 必须为所有的存储属性设置一个合适的初始值,否则会报如下错误:

初始方法:

  • 可以在初始化器里为存储属性设置一个初始值

    var circle = Circle(radius: 40)

  • 可以分配一个默认的属性值作为属性定义的一部分

计算属性

  • set传入新值,默认叫做newValue,也可以自定义

  • 定义计算属性,只能用var,不能用let;let表示常量,值是一成不变的,而计算属性的值是可能发生变化的

  • 只读计算属性–只有get,没有set

struct Circle {
    // 存储属性
    var radius: Double;
    // 只读计算属性
    var diameter: Double {
        get {
            radius * 2
        }
    }
}

延迟存储属性

使用lazy,可以定义一个延迟存储属性,在第一次用到属性时才会初始化

class Car {
    init() {
        print("Car init!")
    }
    func run() {
        print("Car is running!")
    }
}
class Person {
    lazy var car = Car()
    init() {
        print("Person init!")
    }
    func goOut() {
        car.run()
    }
}
let p = Person()
print("--------")
p.goOut()

打印结果:

Person init!
--------
Car init!
Car is running!

从打印结果可以看出,在调用 goOut 这个方法时,属性car才开始进行的初始化

注意事项:

  • lazy属性,必须是var,不能是let;let必须在实例的初始化方法完成之前就拥有值
  • 如果是多条线程同时第一次访问lazy属性,需注意线程安全,因为无法保证属性只被初始化1次

属性观察器(property observer)

可以为非lazy的var存储属性设置属性观察器

struct Circle {
    var radius: Double {
        willSet {
            print("will set", newValue)
        }
        didSet {
            print("did set", oldValue, radius)
        }
    }
    init() {
        self.radius = 1.0
        print("circle init")
    }
}
var circle = Circle()
circle.radius = 2.0
print(circle.radius)

打印结果:

circle init
will set 2.0
did set 1.0 2.0
2.0
  • willSet会传递新值,默认叫做newValue
  • didSet会传递旧值,默认叫做oldValue
  • 在初始化器中,设置属性时,不会触发willSet和didSet

类型属性(type property)

严格来说,属性可以分为两大类:实例属性(instance property)和类型属性(type property),前面讲述的存储属性和计算属性,严格来说,应该是实例存储属性(stored instance property)和实例计算属性(computed instance property)。

类型属性,也分为存储属性和计算属性两类。只能通过类型去访问

类型属性的定义:可以通过static定义类型属性;如果是类,也可以通过关键字class来定义

struct Shape {
    // 实例属性
    var width: Int = 0
    // 类型属性
    static var count: Int = 0
}
// 通过实例访问,会报错
var s = Shape()
//s.count = 10

// 只能通过类型访问
Shape.count = 10

类型属性细节

  • 不同于存储实例属性,你必须给存储类型属性设定初始值:因为类型没有像实例那样的init初始化器来初始化存储属性
  • 存储类型属性,默认就是lazy,会在第一次使用时才初始化
  • 存储类型属性,可以是let,而延迟存储实例属性,只能是var
  • 默认是线程安全的,即使是被多个线程同时访问,也只会被初始化一次(底层实现,是基于dispatch_once)。

基于此特性,可以用来实现单利

class FileManager {
    public static let shared = FileManager()
    private init() { }
}

  • 枚举类型,也可以定义类型属性(存储类型属性和计算类型属性)

类型属性的本质

我们先定义三个全局变量:

var num1 = 10
var num2 = 11
var num3 = 12

通过汇编,可以看出,其在内存中的地址,分别为:

num1:	rip(0x100000d81) + 0x47ff = 0x1000055b0
num2:	rip(0x100000d8c) + 0x47fc = 0x1000055b8
num3:	rip(0x100000d97) + 0x47f9 = 0x1000055c0

我们再定义一个类型属性:

var num1 = 10
class Car {
    static var count = 0
}
Car.count = 11
var num3 = 12

通过汇编,可以看出,其在内存中的地址,分别为:

num1:	    rip(0x1000019a3) + 0x4d2d = 0x1000066d0
Car.count:	                        0x1000066d8
num3:	    rip(0x1000019ec) + 0x4cf4 = 0x1000066e0 

通过对比可以看出,类型属性,本质上就是加了访问权限的全局变量