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
通过对比可以看出,类型属性,本质上就是加了访问权限的全局变量