Swift中跟实例相关的属性可以分为2大类,计算属性和存储属性
struct Circle {// 存储属性var radius: Double// 计算属性var diameter: Double {set {radius = newValue / 2}get {radius * 2}}}
存储属性(Stored Property)
类似于成员变量的概念
存储在实例的内存中
结构体和类可以定义存储属性
枚举不可以定义存储属性
计算属性(Computed Property)
本质就是方法(函数)
不占用实例的内存
枚举、结构体、类都可以定义计算属性
存储属性
关于存储属性,Swift有个明确的规定
在创建类 或 结构体的实例时,必须为所有的存储属性设置一个合适的初始值
- 可以在初始化器里为存储属性设置一个初始值 ```swift struct Point { var x: Int var y: Int }
var p = Point(x: 10, y: 20)
- 可以分配一个默认的属性值作为属性定义的一部分```swiftstruct Point {var x: Int = 10var y: Int = 20}var p = Point()
计算属性
set传入的新值默认叫做newValue,也可以自定义:
struct Point {var x: Int = 10var y: Int {set(myNewValue) {x = myNewValue * 2}get {return x/2}}}
只读计算属性:只有get,没有set
struct Point {var x: Int = 10var y: Int {get {20}}}
计算属性只能用var,不能用let
(因为存储的内容是会变的)
枚举rawValue原理
enum Season: Int {case spring = 1, summer, automn, wintervar rawValue: Int {switch self {case .spring:return 11case .summer:return 22case .automn:return 33case .winter:return 44}}}var s = Season.summerprint(s.rawValue) // 22
rawValue本质是只读的计算属性
可以通过汇编查看:
0x1000039dd <+93>: callq 0x100003af0 ; SwiftTest.Season.rawValue.getter : Swift.Int at <compiler-generated>
延迟存储属性(Lazy Stored Property)
使用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()}}var p = Person()print("-----------")p.goOut()
打印结果:
Person init!-----------Car init!Car is running!
创建Person的过程中,没有创建car属性,在用到car属性时才会调用car的初始化器,进行创建。
lazy var imageView: UIImageView = {let imageView = UIImageView()imageView.image = UIImage(named: "123.png")return imageView}()
imageView是一个存储属性,只不过是一个带有返回值的闭包表达式,用lazy修饰后,在使用时,就会执行闭包表达式里的内容。
延迟存储属性特点
lazy属性必须是var,不能是let,因为let必须在实例的初始化方法完成之前拥有值。
如果多条线程同时第一次访问lazy属性,无法保证属性只被初始化一次。
延迟存储属性注意点
当结构体包含一个延迟存储属性是,只有var才能访问延迟存储属性,因为延迟存储属性初始化时需要改变结构体的内存
属性观察器(Property Observer)
可以为非lazy的var存储属性设置属性观察器
struct Point {var x: Int = 10var y: Int = 20 {willSet {print("willset - newValue = \(newValue), y = \(y)")}didSet {print("didset - oldValue = \(oldValue), y = \(y)")}}}var p = Point()p.y = 21
打印结果:
willset - newValue = 21, y = 20didset - oldValue = 20, y = 21
willSet会传递新值,默认叫newValue
didSet会传递旧值,默认叫oldValue
在初始化器中设置属性值不会出发willset和didSet
在属性定义时设置初始值也不会出发willSet和didSet
全局变量、局部变量
属性观察器、计算属性的功能,同样可以应用在全局变量、局部变量身上
/// 全局变量var num: Int {set {print("setNum", newValue)}get {10}}
/// 局部变量func test() {var age: Int = 10 {willSet {print("willSet", newValue)}didSet {print("didSet", oldValue, age)}}age = 11print("age = \(age)")}test()
inout的再次研究
struct Point {/// 存储属性var x: Int/// 计算属性var y: Int {set {x = newValue / 2}get {x * 2}}func log() {print("point = (\(x), \(y))")}}/// inout参数func test(_ num: inout Int) {num = 20}// 创建point实例var p = Point(x: 1)p.log()// 传入p的计算属性作为inout参数test(&p.y)p.log()
打印结果:
point = (1, 2)point = (10, 20)
证明计算属性也可以作为inout参数,原理是调用test方法前,会先调用y的get方法拿到y的值并创建一个局部变量,再调用test方法把这个局部变量的地址值进行赋值,最后调用y的set方法将局部变量赋值给y。可以通过汇编验证:
0x100003c49 <+57>: callq 0x100003cc0 ; SwiftTest.Point.y.getter : Swift.Int at main.swift:4400x100003c4e <+62>: movq %rax, -0x28(%rbp)0x100003c52 <+66>: leaq -0x28(%rbp), %rdi0x100003c56 <+70>: callq 0x100004170 ; SwiftTest.test(inout Swift.Int) -> () at main.swift:4510x100003c5b <+75>: movq -0x28(%rbp), %rdi0x100003c5f <+79>: leaq 0x8562(%rip), %r13 ; SwiftTest.p : SwiftTest.Point0x100003c66 <+86>: callq 0x100003d60 ; SwiftTest.Point.y.setter : Swift.Int at main.swift:437
inout的本质总结
如果实参有物理地址,且没有设置属性观察器:
- 直接将实参的内存地址传入函数(实参进行引用传递)
如果实参是计算属性或者设置了属性观察器,采取了Copy In Copy Out的做法:
- 调用该函数时,先复制实参的值,产生副本【get】
- 将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
-
类型属性(Type Property)
严格来说属性可分为:
实例属性(Instance Property):只能通过实例去访问 存储实例属性(Store Instance Property):存储在实例的内存中,每个实例都有1份
- 计算实例属性(Computed Instance Property)
类型属性(Type Property):只能通过类型去访问
- 存储类型属性(Store Type Property):整个程序运行中,就只用1份内存(类似于全局变量)
计算类型属性(Computed Type Property)
struct Point {// 计算类型属性 xstatic var x: Int {get {10}}// 存储类型属性 ystatic var y: Int = 20}
可以通过static定一个类型属性
如果是类,也可以用关键词class修饰计算属性
类型属性举例: ```swift struct Car { static var count: Int = 0 init() {Car.count += 1
} }
var c1 = Car() var c2 = Car() var c3 = Car() print(“(Car.count)”) // 3
<a name="g49fC"></a>## 类型属性的细节不同于存储实例属性,你必须给存储类型属性设定初始值,因为类型没有实例那样的init初始化器来初始化存储属性。类型存储类型属性默认就是lazy,会在第一次使用的时候才初始化,就算被多个线程同时访问,保证只会初始化一次。<br />类型存储属性可以是let枚举类型也可以定义类型属性(存储类型属性、计算类型属性)<a name="Cs59c"></a>## 类型属性和全局变量```swiftvar num1 = 10class Car {static var count = 10}Car.count = 20var num2 = 12
num1、count、num2都是全局变量,只是count在访问时有限制(需要通过Car去访问),通过查看给count赋值的汇编代码:
SwiftTest`Car.count.unsafeMutableAddressor:-> 0x100003f40 <+0>: pushq %rbp0x100003f41 <+1>: movq %rsp, %rbp0x100003f44 <+4>: cmpq $-0x1, 0x83c4(%rip) ; SwiftTest.num3 : Swift.Int + 70x100003f4c <+12>: sete %al0x100003f4f <+15>: testb $0x1, %al0x100003f51 <+17>: jne 0x100003f55 ; <+21> at main.swift:514:160x100003f53 <+19>: jmp 0x100003f5e ; <+30> at main.swift0x100003f55 <+21>: leaq 0x83a4(%rip), %rax ; static SwiftTest.Car.count : Swift.Int0x100003f5c <+28>: popq %rbp0x100003f5d <+29>: retq0x100003f5e <+30>: leaq -0x45(%rip), %rsi ; one-time initialization function for count at main.swift0x100003f65 <+37>: leaq 0x83a4(%rip), %rdi ; one-time initialization token for count0x100003f6c <+44>: callq 0x1000076e2 ; symbol stub for: swift_once0x100003f71 <+49>: jmp 0x100003f55 ; <+21> at main.swift:514:16
由于类型存储类型属性默认就是lazy,所以调用了swift_once方法,内部其实是调用了dispatch_once方法,相当于:
dispatch_once {count = 10}
当类型属性第一次被访问时是通过dispatch_once去初始化,也保证了线程安全。
单例模式
class FileManager {// 类型存储属性public static let shared = FileManager()private init() {// do something}}// 访问let manager = FileManager.shared
static保证内存中只有一份,let保证不会在其他地方修改这个内存,这样就实现了单例功能。
