屬性
Swift中跟實(shí)例相關(guān)的屬性可以分為2大類
存儲屬性(Stored Property)
類似于成員變量這個(gè)概念
存儲在實(shí)例的內(nèi)存中
結(jié)構(gòu)體、類可以定義存儲屬性
枚舉不可以定義存儲屬性
計(jì)算屬性(Computed Property)
本質(zhì)就是方法(函數(shù))
不占用實(shí)例的內(nèi)存
枚舉、結(jié)構(gòu)體、類都可以定義計(jì)算屬性
struct Circle {
// 存儲屬性
var radius: Double
// 計(jì)算屬性
var diameter: Double {
set {
radius = newValue / 2
}
get {
radius * 2 // 省了return
}
}
}
var circle = Circle(radius: 5)
print(circle.radius) // 5.0
print(circle.diameter) // 10.0
circle.diameter = 12
print(circle.radius) // 6.0
print(circle.diameter) // 12.0
print(MemoryLayout<Circle>.stride) // 8
// 本質(zhì)就是方法,接下來看匯編推導(dǎo)
// func setDiameter(newValue: Double) {
// radius = newValue / 2
// }
// func getDiameter() -> Double {
// radius * 2
// }
推導(dǎo)過程:
- 先看下17行radius的匯編:
struct Circle {
// 存儲屬性
var radius: Int // 這里改成int,方便查看匯編
// 計(jì)算屬性
var diameter: Int {
set {
radius = newValue / 2
}
get {
radius * 2 // 省了return
}
}
}
var c = Circle(radius: 10)
c.radius = 11
c.diameter = 12
var d = c.diameter

17 行:radius 存儲在結(jié)構(gòu)體的內(nèi)存里面。
- 看下diameter的匯編


調(diào)用的是一個(gè)setter方法。

19行:調(diào)用的是一個(gè)getter方法
存儲屬性
關(guān)于存儲屬性,Swift有個(gè)明確的規(guī)定
在創(chuàng)建類 或 結(jié)構(gòu)體的實(shí)例時(shí),必須為所有的存儲屬性設(shè)置一個(gè)合適的初始值
可以在初始化器里為存儲屬性設(shè)置一個(gè)初始值
可以分配一個(gè)默認(rèn)的屬性值作為屬性定義的一部分
計(jì)算屬性
set傳入的新值默認(rèn)叫做newValue,也可以自定義
struct Circle {
var radius: Double
var diameter: Double {
set(newDiameter) {
radius = newDiameter / 2
}
get {
radius * 2
}
}
}
只讀計(jì)算屬性:只有g(shù)et,沒有set
struct Circle {
var radius: Double
var diameter: Double {
get {
radius * 2
}
}
}
struct Circle {
var radius: Double
var diameter: Double { radius * 2 }
}
- 定義計(jì)算屬性只能用var,不能用let
- let代表常量:值是一成不變的
- 計(jì)算屬性的值是可能發(fā)生變化的(即使是只讀計(jì)算屬性)
枚舉rawValue
枚舉原始值rawValue的本質(zhì)是:只讀計(jì)算屬性
enum TestEnum : Int {
case test1 = 1, test2 = 2, test3 = 3
var rawValue: Int {
switch self {
case .test1:
return 10
case .test2:
return 11
case .test3:
return 12
}
}
}
print(TestEnum.test3.rawValue) // 12
延遲存儲屬性(Lazy Stored Property)
使用lazy可以定義一個(gè)延遲存儲屬性,在第一次用到屬性的時(shí)候才會(huì)進(jìn)行初始化
lazy屬性必須是var,不能是let
let必須在實(shí)例的初始化方法完成之前就擁有值
-
如果多條****線程同時(shí)第一次訪問lazy屬性
- 無法保證屬性只被初始化1次
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!
class PhotoView {
lazy var image: Image = {
let url = "https://www.520it.com/xx.png"
let data = Data(url: url)
return Image(data: data)
}() // 閉包
}
可以理解為一個(gè)函數(shù)調(diào)用返回值

延遲存儲屬性注意點(diǎn)
-
當(dāng)結(jié)構(gòu)體包含一個(gè)延遲存儲屬性時(shí),只有var才能訪問延遲存儲屬性
- 因?yàn)檠舆t屬性初始化時(shí)需要改變結(jié)構(gòu)體的內(nèi)存
struct Point {
var x = 0
var y = 0
lazy var z = 0
}
let p = Point()
print(p.z) Cannot use mutating getter on immutable value: 'p' is a 'let' constant
屬性觀察器 (Property Observer)
- 可以為非lazy的var存儲屬性設(shè)置屬性觀察器
struct Circle {
var radius: Double {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, radius)
}
}
init() {
self.radius = 1.0
print("Circle init!")
}
}
// Circle init!
var circle = Circle()
// willSet 10.5
// didSet 1.0 10.5
circle.radius = 10.5
// 10.5
print(circle.radius)
willSet會(huì)傳遞新值,默認(rèn)叫newValue
didSet會(huì)傳遞舊值,默認(rèn)叫oldValue
在初始化器中設(shè)置屬性值不會(huì)觸發(fā)willSet和didSet
在屬性定義時(shí)設(shè)置初始值也不會(huì)觸發(fā)willSet和didSet
全局變量、局部變量
- 屬性觀察器、計(jì)算屬性的功能,同樣可以應(yīng)用在全局變量、局部變量身上
var num: Int {
get {
return 10
}
set {
print("setNum", newValue)
}
}
num = 11 // setNum 11
print(num) // 10
func test() {
var age = 10 {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, age)
}
}
age = 11
// willSet 11
// didSet 10 11
}
test()
inout的再次研究
struct Shape {
// 存儲屬性
var width: Int
var side: Int {
willSet {
print("willSetSide", newValue)
}
didSet {
print("didSetSide", oldValue, side)
}
}
// 計(jì)算屬性
var girth: Int {
set {
width = newValue / side
print("setGirth", newValue) }
get {
print("getGirth")
return width * side // 周長
}
}
func show() {
print("width=\(width), side=\(side), girth=\(girth)")
}
}
func test(_ num: inout Int) {
num = 20
}
var s = Shape(width:10, side:4)
去掉干擾代碼先觀察:
test(&s.width)
test(&s.side)
test(&s.girth)
test(&s.width)
s.show()
print("----------")
test(&s.side)
s.show()
print("----------")
test(&s.girth)
s.show()
// log如下
getGirth
width=20, side=4, girth=80 ----------
willSetSide 20
didSetSide 4 20
getGirth
width=20, side=20, girth=400 ----------
getGirth
setGirth 20
getGirth
width=1, side=20, girth=20
推動(dòng)過程:
- 先看下width存儲屬性

s.width: s結(jié)構(gòu)體變量的地址值,傳遞給了test函數(shù),因?yàn)閣idth是存儲屬性,存在結(jié)構(gòu)體里,而且又是第一個(gè),第一個(gè)屬性的內(nèi)存地址值和結(jié)構(gòu)體內(nèi)存的地址值是重疊的
- 看下girth
// log輸出
getGirth
test
setGirth 20


調(diào)用test之前先調(diào)用get, get方法值放在局部變量去 20
局部變量內(nèi)存?zhèn)鬟f到 test
20 覆蓋掉局部變量
在調(diào)set
inout的本質(zhì)總結(jié)
-
如果實(shí)參有物理內(nèi)存地址,且沒有設(shè)置屬性觀察器
- 直接將實(shí)參的內(nèi)存地址傳入函數(shù)(實(shí)參進(jìn)行引用傳遞)
-
如果實(shí)參是計(jì)算屬性 或者 設(shè)置了屬性觀察器
- 采取了Copy In Copy Out的做法
?? 調(diào)用該函數(shù)時(shí),先復(fù)制實(shí)參的值,產(chǎn)生副本****【get】
?? 將副本的內(nèi)存地址傳入函數(shù)(副本進(jìn)行引用傳遞),在函數(shù)內(nèi)部可以修改副本的值
?? 函數(shù)返回后,再將副本的值覆蓋實(shí)參的值****【set】
總結(jié):inout的本質(zhì)就是引用傳遞(地址傳遞)
類型屬性 (Type Property)
嚴(yán)格來說,屬性可以分為
-
實(shí)例屬性(Instance Property):只能通過實(shí)例去訪問
?? 存儲實(shí)例屬性(Stored Instance Property):存儲在實(shí)例的內(nèi)存中,每個(gè)實(shí)例都有1份
?? 計(jì)算實(shí)例屬性(Computed Instance Property)
-
類型屬性(Type Property):只能通過類型去訪問
?? 存儲類型屬性(Stored Type Property):整個(gè)程序運(yùn)行過程中,就只有1份內(nèi)存(類似于全局變量)
?? 計(jì)算類型屬性(Computed Type Property)
可以通過static定義類型屬性
如果是類,也可以用關(guān)鍵字class
struct Car {
static var count: Int = 0
init() {
Car.count += 1
}
}
let c1 = Car()
let c2 = Car()
let c3 = Car()
print(Car.count) // 3
推導(dǎo)過程:
1、先看一下如下代碼的匯編本質(zhì)
var num1 = 10
var num2 = 11
var num3 = 12


2、把car放在中間查看匯編代碼本質(zhì)
var num1 = 10
Class Car {
static var count = 0
}
Car.count = 11
var num3 = 12



Lazy 第一次用到才會(huì)使用:


類型屬性細(xì)節(jié)
- 不同于存儲實(shí)例屬性,你必須給存儲類型屬性設(shè)定初始值
□ 因?yàn)轭愋蜎]有像實(shí)例那樣的init初始化器來初始化存儲屬性
- 存儲類型屬性默認(rèn)就是lazy,會(huì)在第一次使用的時(shí)候才初始化
□ 就算被多個(gè)線程同時(shí)訪問,保證只會(huì)初始化一次
存儲類型屬性可以是let
枚舉類型也可以定義類型屬性(存儲類型屬性、計(jì)算類型屬性)
單例模式
public class FileManager {
public static let shared = FileManager()
private init () {}
}
public class FileManager {
public static let shared = {
// ...
// ...
return FileManager()
}()
private init () {}
}