Swift5中的存儲屬性和計算屬性以及屬性觀察器

Swift 屬性將值跟特定的類、結構或枚舉關聯。

屬性可分爲存儲屬性和計算屬性:

存儲屬性 計算屬性
存儲常量或變量作爲實例的一部分 計算(而不是存儲)一個值
用於類和結構體 用於類、結構體和枚舉

存儲屬性和計算屬性通常用於特定類型的實例。
屬性也可以直接用於類型本身,這種屬性稱爲類型屬性。
另外,還可以定義屬性觀察器來監控屬性值的變化,以此來觸發一個自定義的操作。屬性觀察器可以添加到自己寫的存儲屬性上,也可以添加到從父類繼承的屬性上。

一、存儲屬性

簡單來說,一個存儲屬性就是存儲在特定類或結構體的實例裏的一個常量或變量。
存儲屬性可以是變量存儲屬性(用關鍵字var定義),也可以是常量存儲屬性(用關鍵字let定義)。
可以在定義存儲屬性的時候指定默認值
也可以在構造過程中設置或修改存儲屬性的值,甚至修改常量存儲屬性的值

import Cocoa

struct Number
{
   var digits: Int
   let pi = 3.1415
}

var n = Number(digits: 12345)
n.digits = 67

print("\(n.digits)")
print("\(n.pi)")

以上程序執行輸出結果爲:

67
3.1415

考慮以下代碼:

let pi = 3.1415

代碼中 pi 在定義存儲屬性的時候指定默認值(pi = 3.1415),所以不管你什麼時候實例化結構體,它都不會改變。
如果你定義的是一個常量存儲屬性,如果嘗試修改它就會報錯,如下所示:

import Cocoa

struct Number
{
    var digits: Int
    let numbers = 3.1415
}

var n = Number(digits: 12345)
n.digits = 67

print("\(n.digits)")
print("\(n.numbers)")
n.numbers = 8.7

以上程序,執行會報錯,錯誤如下所示:

error: cannot assign to property: 'numbers' is a 'let' constant
n.numbers = 8.7

意思爲 ‘numbers’ 是一個常量,你不能修改它。

##1.1 延遲存儲屬性

延遲存儲屬性是指當第一次被調用的時候纔會計算其初始值的屬性。
在屬性聲明前使用 lazy 來標示一個延遲存儲屬性。

注意:
必須將延遲存儲屬性聲明成變量(使用var關鍵字),因爲屬性的值在實例構造完成之前可能無法得到。而常量屬性在構造過程完成之前必須要有初始值,因此無法聲明成延遲屬性。

延遲存儲屬性一般用於:
延遲對象的創建。
當屬性的值依賴於其他未知類

import Cocoa

class sample {
    lazy var no = number() // `var` 關鍵字是必須的
}

class number {
    var name = "Runoob Swift 教程"
}

var firstsample = sample()
print(firstsample.no.name)

以上程序執行輸出結果爲:

Runoob Swift 教程

1.2 實例化變量

如果您有過 Objective-C 經驗,應該知道Objective-C 爲類實例存儲值和引用提供兩種方法。對於屬性來說,也可以使用實例變量作爲屬性值的後端存儲。
Swift 編程語言中把這些理論統一用屬性來實現。Swift 中的屬性沒有對應的實例變量,屬性的後端存儲也無法直接訪問。這就避免了不同場景下訪問方式的困擾,同時也將屬性的定義簡化成一個語句。
一個類型中屬性的全部信息——包括命名、類型和內存管理特徵——都在唯一一個地方(類型定義中)定義。

1.3 計算屬性

除存儲屬性外,類、結構體和枚舉可以定義計算屬性,計算屬性不直接存儲值,而是提供一個 getter 來獲取值,一個可選的 setter 來間接設置其他屬性或變量的值。

import Cocoa

class sample {
    var no1 = 0.0, no2 = 0.0
    var length = 300.0, breadth = 150.0
    
    var middle: (Double, Double) {
        get{
            return (length / 2, breadth / 2)
        }
        set(axis){
            no1 = axis.0 - (length / 2)
            no2 = axis.1 - (breadth / 2)
        }
    }
}

var result = sample()
print(result.middle)
result.middle = (0.0, 10.0)

print(result.no1)
print(result.no2)

以上程序執行輸出結果爲:

(150.0, 75.0)
-150.0
-65.0

如果計算屬性的 setter 沒有定義表示新值的參數名,則可以使用默認名稱 newValue。

##1.4 只讀計算屬性

只有 getter 沒有 setter 的計算屬性就是隻讀計算屬性。
只讀計算屬性總是返回一個值,可以通過點(.)運算符訪問,但不能設置新的值。

import Cocoa

class film {
    var head = ""
    var duration = 0.0
    var metaInfo: [String:String] {
        return [
            "head": self.head,
            "duration":"\(self.duration)"
        ]
    }
}

var movie = film()
movie.head = "Swift 屬性"
movie.duration = 3.09

print(movie.metaInfo["head"]!)
print(movie.metaInfo["duration"]!)

以上程序執行輸出結果爲:

Swift 屬性
3.09

注意:
必須使用var關鍵字定義計算屬性,包括只讀計算屬性,因爲它們的值不是固定的。let關鍵字只用來聲明常量屬性,表示初始化後再也無法修改的值。

二、屬性觀察器

屬性觀察器監控和響應屬性值的變化,每次屬性被設置值的時候都會調用屬性觀察器,甚至新的值和現在的值相同的時候也不例外。
可以爲除了延遲存儲屬性之外的其他存儲屬性添加屬性觀察器,也可以通過重載屬性的方式爲繼承的屬性(包括存儲屬性和計算屬性)添加屬性觀察器。

注意:
不需要爲無法重載的計算屬性添加屬性觀察器,因爲可以通過 setter 直接監控和響應值的變化。

可以爲屬性添加如下的一個或全部觀察器:
1,不僅可以在屬性值改變後觸發didSet,也可以在屬性值改變前觸發willSet。
2,給屬性添加觀察者必須要聲明清楚屬性類型,否則編譯器報錯。
3,willSet可以帶一個newName的參數,沒有的話,該參數默認命名爲newValue。
4,didSet可以帶一個oldName的參數,表示舊的屬性,不帶的話默認命名爲oldValue。
5,屬性初始化時,willSet和didSet不會調用。只有在初始化上下文之外,當設置屬性值時纔會調用。
6,即使是設置的值和原來值相同,willSet和didSet也會被調用

import Cocoa

class Samplepgm {
    var counter: Int = 0{
        willSet(newTotal){
             print("計數器: \(counter)”) //0
            print("計數器: \(newTotal)”) //100 800
        }
        didSet{
             print("計數器: \(counter)”) //100 800
            print("計數器: \(oldValue)”) //0
        }
    }
}
let NewCounter = Samplepgm()
NewCounter.counter = 100
NewCounter.counter = 800

以上程序執行輸出結果爲:

計數器: 100
新增數 100
計數器: 800
新增數 700

三、初始化強制調用

如果想要強制這個在初始化過程中調用呢,那麼可以使用defer延遲聲明,也可以使用自定義函數,這兩種方案都可行
1、使用defer關鍵詞

    var mIconImage:UIImage = UIImage(named: "cell_weather")! {
        willSet {
            self.mIconImageView.image = newValue
        }
    }
    
    var mTitleText : String = "" {
        willSet {
            self.mTitleLabel.text = newValue
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.p_createUI()
    }
    
    convenience init(iconImage:UIImage, titleText:String) {
        self.init(frame: CGRect.zero)
        defer {
            self.mIconImage = iconImage
            self.mTitleText = titleText
        }
    }

2、構造一個賦值的函數,在函數中調用

    var mIconImage:UIImage = UIImage(named: "cell_weather")! {
        willSet {
            self.mIconImageView.image = newValue
        }
    }
    
    var mTitleText : String = "" {
        willSet {
            self.mTitleLabel.text = newValue
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.p_createUI()
    }
    
    convenience init(iconImage:UIImage, titleText:String) {
        self.init(frame: CGRect.zero)
        self.p_loadData(iconImage: iconImage, titleText: titleText)
    }
    
    func p_loadData(iconImage:UIImage, titleText:String) -> Void {
        self.mIconImage = iconImage
        self.mTitleText = titleText
    }

至於網上的通過KVC方法賦值後,再添加setValueforUndefinedKey方法做特殊處理的方案就不要採用了

3.1 全局變量和局部變量

計算屬性和屬性觀察器所描述的模式也可以用於全局變量和局部變量。

局部變量 全局變量
在函數、方法或閉包內部定義的變量。 函數、方法、閉包或任何類型之外定義的變量。
用於存儲和檢索值。 用於存儲和檢索值。
存儲屬性用於獲取和設置值。 存儲屬性用於獲取和設置值。
也用於計算屬性。 也用於計算屬性。

3.2 類型屬性

類型屬性是作爲類型定義的一部分寫在類型最外層的花括號({})內。
使用關鍵字 static 來定義值類型的類型屬性,關鍵字 class 來爲類定義類型屬性。

struct Structname {
   static var storedTypeProperty = " "
   static var computedTypeProperty: Int {
      // 這裏返回一個 Int 值
   }
}

enum Enumname {
   static var storedTypeProperty = " "
   static var computedTypeProperty: Int {
      // 這裏返回一個 Int 值
   }
}

class Classname {
   class var computedTypeProperty: Int {
      // 這裏返回一個 Int 值
   }
}

注意:
例子中的計算型類型屬性是隻讀的,但也可以定義可讀可寫的計算型類型屬性,跟實例計算屬性的語法類似。

3.3獲取和設置類型屬性的值

類似於實例的屬性,類型屬性的訪問也是通過點運算符(.)來進行。但是,類型屬性是通過類型本身來獲取和設置,而不是通過實例。實例如下:

import Cocoa

struct StudMarks {
   static let markCount = 97
   static var totalCount = 0
   var InternalMarks: Int = 0 {
      didSet {
         if InternalMarks > StudMarks.markCount {
            InternalMarks = StudMarks.markCount
         }
         if InternalMarks > StudMarks.totalCount {
            StudMarks.totalCount = InternalMarks
         }
      }
   }
}

var stud1Mark1 = StudMarks()
var stud1Mark2 = StudMarks()

stud1Mark1.InternalMarks = 98
print(stud1Mark1.InternalMarks) 

stud1Mark2.InternalMarks = 87
print(stud1Mark2.InternalMarks)
以上程序執行輸出結果爲:
97
87
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章