屬性
什么是屬性哎榴?
- 屬性(Property)可以將值與特定的類、結(jié)構(gòu)體或者枚舉聯(lián)系起來
- 屬性嚴(yán)格來說根據(jù)訪問方式可以分為實(shí)例屬性(Instance Property)僵蛛、類型屬性(Type Property)
與實(shí)例相關(guān)的屬性(該節(jié)中屬性均表示為實(shí)例相關(guān)的屬性)
- 存儲(chǔ)屬性 特點(diǎn):
- 類似于成員變量尚蝌,存儲(chǔ)在實(shí)例的內(nèi)存中,
- 結(jié)構(gòu)體充尉、類可以定義存儲(chǔ)屬性飘言,枚舉不可以定義存儲(chǔ)屬性
規(guī)定:在創(chuàng)建類、結(jié)構(gòu)體的實(shí)例時(shí)驼侠,必須為所有的存儲(chǔ)屬性設(shè)置一個(gè)合適的初始值
- 可以再初始化器里為存儲(chǔ)屬性設(shè)置一個(gè)初始值
- 可以分配一個(gè)默認(rèn)的屬性值作為屬性定義的一部分
其實(shí)本質(zhì)上是一樣的(通過匯編可以證明姿鸿,都是在init初始化方法中)
- 計(jì)算屬性 特點(diǎn):
- 本質(zhì)就是方法(函數(shù)),不占用實(shí)例的內(nèi)存
- 枚舉倒源、結(jié)構(gòu)體苛预、類都可以定義計(jì)算屬性
其中: - set傳入的新值默認(rèn)叫做newValue,也可以自定義
- 只讀計(jì)算屬性只有g(shù)et笋熬,沒有set(set可以不寫热某,即為只讀),此時(shí)可以簡寫
- 定義計(jì)算屬性只能用var胳螟,不能用let(即使是只讀計(jì)算屬性)苫拍,因?yàn)橛?jì)算屬性的值是可能根據(jù)存儲(chǔ)屬性發(fā)生變化的
//例1、存儲(chǔ)屬性旺隙、計(jì)算屬性的舉例
struct Circle {
var radius :Int //存儲(chǔ)屬性
var diameter :Int {//計(jì)算屬性
set{
radius = newValue / 2
}
get{
radius * 2
}
}
}
var cir = Circle(radius: 10)
cir.radius = 20
print(cir.radius, cir.diameter)//輸出:20 40
cir.diameter = 20
print(cir.radius, cir.diameter)//輸出:10 20
//例2绒极、只讀計(jì)算屬性的簡寫
struct Circle {
var radius :Int //存儲(chǔ)屬性
var diameter :Int {//計(jì)算屬性
get{
radius * 2
}
}
var area :Int{//只讀計(jì)算屬性 簡寫形式
radius * radius
}
}
var cir = Circle(radius: 10)
cir.radius = 20
print(cir.radius, cir.diameter)//輸出:20 40
//cir.diameter = 20//報(bào)錯(cuò) 只讀計(jì)算屬性
print(cir.diameter,cir.area)//輸出:40 400
rawValue原理
前面講過枚舉,如果對(duì)枚舉的原始值/關(guān)聯(lián)值的分配內(nèi)存有疑惑的蔬捷,在這里可以解釋
//rawValue的本質(zhì)其實(shí)就是枚舉實(shí)例的get方法 是不占用內(nèi)存的
enum TestEnum :Int {
case test1 = 1, test2, test3, test4
var rawValue :Int {
get{
switch self {
case .test1:
return 2
case .test2:
return 3
case .test3:
return 4
case .test4:
return 5
}
}
}
}
print(TestEnum.test1.rawValue)//2 與此前定義原始值1不同
如何證明呢垄提?將rawValue相關(guān)代碼注釋榔袋,斷點(diǎn)調(diào)試 查看匯編
如何證明rawValue是只讀的呢?賦值即會(huì)報(bào)錯(cuò)
延遲存儲(chǔ)屬性(Lazy Stored Property)
- 使用lazy定義一個(gè)延遲存儲(chǔ)屬性 在第一次用到屬性的時(shí)候才會(huì)進(jìn)行初始化
如下例子铡俐,執(zhí)行結(jié)果會(huì)有不同:
【注意】
- lazy屬性必須是var凰兑,不能是let( let必須在實(shí)例初始化方法完成之前就擁有值,lazy的本質(zhì)是第一次用到時(shí)改變car對(duì)應(yīng)內(nèi)存地址的值)
- 如果多條線程同時(shí)第一次訪問lazy屬性审丘,則無法保證屬性只被初始化一次
- 當(dāng)結(jié)構(gòu)體(上栗為class)包含一個(gè)延遲存儲(chǔ)屬性時(shí)吏够,只有var才能訪問延遲存儲(chǔ)屬性(∵結(jié)構(gòu)體為值類型)
屬性觀察器(Property Observer)
- 可以為非lazy的var屬性設(shè)置屬性觀察器(可以類比OC中的kvo)
- willSet會(huì)傳遞新值,默認(rèn)叫newValue,didSet會(huì)傳遞舊值滩报,默認(rèn)叫oldValue
- 在初始化器中設(shè)置屬性不會(huì)觸發(fā)willSet和didSet
//例.初始化器中設(shè)置屬性不會(huì)觸發(fā)
struct Circle {
var radius :Int {
willSet{
print("willset",newValue)
}
didSet{
print("didset",oldValue,radius)
}
}
var diameter :Int {
set{
radius = newValue / 2
}
get{
radius * 2
}
}
init() {
self.radius = 10
print("Circle init!")
}
}
var c = Circle()//輸出:Circle init!
c.radius = 20
//輸出:willset 20
// didset 10 20
屬性觀察器锅知、計(jì)算屬性 應(yīng)用場景
inout再次研究
- 如果實(shí)參有物理內(nèi)存地址售睹,且沒有屬性觀察器,直接將實(shí)參的內(nèi)存地址傳入函數(shù)(實(shí)參進(jìn)行引用傳遞)
- 如果實(shí)參是計(jì)算屬性 或者 設(shè)置了屬性觀察器 則會(huì)采取Copy In Copy Out的做法
- 第一步可训、調(diào)用該函數(shù)時(shí)昌妹,先復(fù)制實(shí)參的值,產(chǎn)生副本【get】
- 第二步握截、將副本的內(nèi)存地址傳入函數(shù)(副本進(jìn)行引用傳遞)飞崖,在函數(shù)內(nèi)部修改副本的值
- 第三步、函數(shù)返回后谨胞,再將副本的值覆蓋實(shí)參的值【set】
因此固歪,inout本質(zhì)就是引用傳遞(地址傳遞)
怎么證明上述結(jié)論是正確的呢?
struct Shape {
var width :Int
var side :Int {
willSet{
print("willSetSide",newValue)
}
didSet{
print("didSetSide",oldValue,side)
}
}
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 change(_ num: inout Int){
num = 20
}
var shape1 = Shape(width: 10, side: 4)
change(&shape1.width)
shape1.show()
print("----------------")
change(&shape1.side)
shape1.show()
print("----------------")
change(&shape1.girth)
shape1.show()
/*輸出:
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
*/
-
先看shape1.width畜眨,查看匯編
-
再看shape1. side
再看shape1. girth 查看匯編 重點(diǎn)在3個(gè)藍(lán)框 和3條紅線
類型屬性(Type Property)
- 實(shí)例屬性(Instance Property):只能通過實(shí)例去訪問
- *存儲(chǔ)實(shí)例屬性(Stored Intance Property)存儲(chǔ)在實(shí)例的內(nèi)存中胞四,每個(gè)實(shí)例都有一份
- *計(jì)算實(shí)例屬性(Computed Intance Property)
- 類型屬性(Type Property):只能通過類型去訪問
- *存儲(chǔ)類型屬性(Stored Type Property)整個(gè)程序運(yùn)中恬汁,就只有一份內(nèi)存(類似全局變量)
- *計(jì)算類型屬性(Computed Type Property)
- 可以通過static定義(存儲(chǔ)、計(jì)算)類型屬性辜伟,如果是類氓侧,其計(jì)算屬性也可以用關(guān)鍵字class
struct Car {
static var count :Int = 0
var wheels :Int
init(wheels:Int) {
self.wheels = wheels
Car.count += 1
}
}
var car1 = Car.init(wheels: 4)
var car2 = Car.init(wheels: 2)
var car3 = Car.init(wheels: 3)
print(Car.count)//輸出:3
//類 static定義存儲(chǔ)屬性 static/class定義計(jì)算屬性
class Dinner {
static var count :Int = 0
var price :Int
class var totalPrice: Int {
set{
count = newValue / 5
}
get{
count * 5
}
}
init(price:Int) {
self.price = price
Dinner.count += 1
}
}
var dinner1 = Dinner.init(price: 5)
var dinner2 = Dinner.init(price: 10)
var dinner3 = Dinner.init(price: 20)
print(Dinner.count,Dinner.totalPrice)//輸出:3 15
猜想:類型屬性的內(nèi)存是存在哪里呢?
var num1 = 10
class Car {
static var count = 0
}
Car.count = 11
var num = 11
為什么0xb賦值給%rax导狡,而不是一個(gè)準(zhǔn)確的地址類似全局變量呢约巷?注意到匯編中有這樣一個(gè)方法
類型屬性重點(diǎn):
- 不同于存儲(chǔ)實(shí)例屬性踩麦,存儲(chǔ)類型屬性必須設(shè)定初始值(因?yàn)闆]有init初始化器)
- 存儲(chǔ)類型屬性默認(rèn)就是lazy,會(huì)在第一次使用的時(shí)候初始化
- 就算是多個(gè)線程同時(shí)訪問氓癌,也只會(huì)初始化一次
- 存儲(chǔ)類型屬性可以是let(因?yàn)楹蛯?shí)例初始化沒有關(guān)系)
- 枚舉類型也可以定義類型屬性(存儲(chǔ)類型屬性谓谦、計(jì)算類型屬性)
- 類型屬性的應(yīng)用場景:單例模式 保證該類僅會(huì)被初始化一次
public class FileManager {
public static let fileManager = FileManager()
private init(){
}
}
FileManager.fileManager
//var f1 = FileManager()//報(bào)錯(cuò) 'FileManager' initializer is inaccessible due to 'private' protection level
Swift學(xué)習(xí)日記10