Swift 5.1 (10) - 屬性

級別: ★☆☆☆☆
標籤:「iOS」「Swift 5.1」「存儲屬性」「計算屬性」「屬性觀察」
做者: 沐靈洛
審校: QiShare團隊php


屬性

屬性將值與特定類,結構或枚舉相關聯。 存儲屬性:存儲值,將常量和變量值存儲爲實例的一部分。存儲屬性僅由類和結構體支持。 計算屬性:計算值,計算屬性由類,結構體和枚舉支持。git

存儲屬性

存儲屬性能夠是變量存儲屬性,經過var關鍵字來聲明,也能夠是常量存儲屬性,經過let關鍵字來聲明。 結構體存儲屬性定義示例:github

//定義一個結構體
struct StructEnumType {
    //! 定義一個常量存儲屬性
    let constantPro : Int
    //! 定義一個變量存儲屬性
    var varPro : Int //!< `var`關鍵字能夠這般聲明屬性,可是必須在初始化方法中賦值。
}
//使用`var`聲明StructEnumType的變量實例
var enumType = StructEnumType(constantPro: 0, varPro: 3)
//嘗試修改變量屬性
//enumType.constantPro = 0 //!< 編譯器報錯:Cannot assign to property: 'constantPro' is a 'let' constant
//嘗試修改變量屬性
enumType.varPro = 6
print(enumType)//!< 改變成功。輸出:StructEnumType(constantPro: 0, varPro: 6)
複製代碼

常量結構體實例的存儲屬性web

//使用`let`聲明StructEnumType的常量實例
let enumType = StructEnumType(constantPro: 0, varPro: 3)
//嘗試修改變量屬性
//enumType.constantPro = 0 //!< 編譯器報錯:Cannot assign to property: 'constantPro' is a 'let' constant
//嘗試修改變量屬性
enumType.varPro = 6 //!< 編譯器報錯:Cannot assign to property: 'enumType' is a 'let' constant
複製代碼

綜上述: 結構體StructEnumType分別定義了變量和常量屬性。編程

•使用var定義StructEnumType的變量實例,該變量實例沒法修改結構體中的常量屬性,可是能夠修改其變量屬性的值。swift

•使用let定義StructEnumType的常量實例,該常量實例沒法修改結構體中的常量屬性和變量屬性。bash

總結: 結構體是值類型,當標記值類型的實例爲常量時其全部屬性便都會標記爲常量。 類的存儲屬性定義示例:微信

//類的存儲屬性與變量屬性
var classVarPro : Int
let classConstantPro : Int = 8
override init() {
    classVarPro = 7
    super.init()
}
//使用`var`和`let`是同樣的
var classObj = PropertiesClass()
//    classObj.classConstantPro = 7 //!< 編譯器報錯:Cannot assign to property: 'constantPro' is a 'let' constant
classObj.classVarPro = 10 //!< 正確輸出
複製代碼

綜上述: • 類是引用類型,不論是使用類的變量實例仍是常量實例,均可以修改變量屬性的值,可是不能修改常量屬性的值。閉包

懶存儲屬性ide

懶存儲屬性:其初始值在初次使用的時候纔會被調用。相似懶加載。 聲明懶存儲屬性:使用關鍵字lazy。 注意:懶屬性必須使用var關鍵字聲明爲變量屬性。

存儲屬性和實例變量

Objective-C提供了兩種方法來存儲值和引用做爲類實例的一部分。除了屬性以外,還可使用實例變量。而Swift中只有屬性而且Swift中的屬性沒有相應的實例變量。

計算屬性

類,結構體和枚舉類型中能夠定義計算屬性,計算屬性會提供一個getter和一個可選的setter來間接獲取和設置其餘屬性和值。計算屬性的聲明只能使用var關鍵字

//! 定義一個座標點的結構體
struct Point {
    var x = 0,y = 0
}
//! 定義一個大小的結構體
struct Size {
    var width = 0,height = 0
}
/* 定義一個rect的結構體:一個rect會有原點,會有大小 根據這兩個存儲屬性,能夠計算得出 rect的center。
所以咱們會在`rect`中定義一個計算屬性。利用其提供的`getter`和`setter`方法進行值得獲取,和值被設置時進行相應處理*/
struct rect {
    var origin = Point()
    var size = Size()
    //! get 和 set 都須要出現,
    var center : Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(customValue){
            //`customValue`即是所賦新值的點
            origin.x = customValue.x - (size.width / 2)
            origin.y = customValue.y - (size.height / 2)
        }
    }
}
複製代碼

定義一個rect的結構體:一個rect會有原點,會有大小,根據這兩個存儲屬性,能夠計算得出中心。所以咱們在rect中定義一個計算屬性center。利用其提供的gettersetter方法進行center計算屬性值的獲取,屬性值的關聯處理。調用以下:

var frame = rect(origin: Point(x: 2, y: 2), size: Size.init(width: 10, height: 10)) //!< 不能使用`let`..
frame.center = Point.init(x: 10, y: 10) //!<不從新賦值便會獲得Point(x: 7, y: 7)
print("應該調用了set方法後:\(frame.center)")//Point(x: 10, y: 10)
複製代碼

使用frame.center時會調用結構體rect中的center計算屬性的getter方法。使用frame.center = Point.init(x: 10, y: 10)會調用setter方法,設置與獲取center屬性值關聯的屬性值。若外部對於center計算屬性只是獲取,則只須要getter方法便可,若不只會獲取,還會從新設置,則setter也是必要的。

簡寫的setter方法聲明

若是計算屬性的setter方法沒有爲要設置的新值定義名稱,則使用默認名稱newValue

struct rect {
    var origin = Point()
    var size = Size()
    
    var center : Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set{
            //`newValue`即是所賦新值的點,系統的默認值
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }
    }
}
複製代碼

只讀計算屬性

只讀計算屬性:具備getter但沒有setter方法的計算屬性。只讀計算屬性始終返回一個值,能夠經過點語法訪問,但不能設置爲其餘值。 注意:計算屬性,包括只讀計算屬性,必須使用var關鍵字將其聲明爲變量屬性,由於計算屬性的值不固定。let關鍵字僅用於常量屬性,一旦將它們設置爲實例初始化的一部分,就不能更改它們的值。

//! 定義面積的結構體
struct areaStruct {
    var width = 0.0,height = 0.0
    var area:Double {
        get {
            return width * height
        }
    }
}
複製代碼

只讀計算屬性的聲明,咱們也能夠經過刪除get關鍵字及其{}來簡化

struct areaStruct {
    var width = 0.0,height = 0.0
    var area:Double {
        return width * height
    }
}
/*使用`let`關鍵字修飾的結構體實例,其內部的變量屬性,都將變爲常量屬性。
即`setter`方法不可用,可是`getter`方法不收影響。*/
let area = areaStruct.init(width: 20.0, height: 3.0)
print("\(area.area)")
複製代碼

屬性觀察者

屬性觀察者:觀察和響應屬性值的變化。每次屬性的值被設置時,屬性觀察者都會被調用,即便新值和屬性當前的值是同樣的。 咱們能夠爲咱們定義的任何存儲屬性添加屬性觀察者,除了懶儲存屬性,即便用lazy修飾的變量屬性。也能夠經過在子類中重寫繼承自父類的屬性(存儲屬性和計算屬性都可),爲該屬性添加屬性觀察者。 屬性觀察者的定義:

willSet存儲新值以前調用

didSet存儲新值以後調用 能夠在屬性中定義willSetdidSet中的任一個,或者二者都定義。

willSet此觀察者方法,會將新屬性值做爲常量參數傳遞。能夠在該方法實現中指定該常量參數的名稱。若未在實現中指定參數名稱,將會使用默認參數名稱newValue

didSet此觀察者方法,會將舊屬性值做爲常量參數傳遞。能夠在該方法實現中指定該常量參數的名稱。若未在實現中指定參數名稱,將會使用默認參數名稱oldValue。另:
若是在didSet觀察者方法內,爲該屬性分配一個新值,則這個新的值將會替換剛剛設置的值。 當調用了父類的初始化方法後,並在子類的初始化方法中設置父類的屬性時,父類屬性的willSetdidSet的觀察者方法會被調用。在父類的初始化方法被調用以前,類在設置本身的屬性時,willSetdidSet的觀察者方法不會被調用。

//屬性觀察者
class propertyObeserver: NSObject {
    var progress = 0 {
        willSet(newprogress) {
          print("將要設置的新值\(newprogress)")
        }
//            willSet {
//                print("將要設置的新值\(newValue)")
//            }
        didSet(oldProgress){
            print("設置的新值與舊值得差值\(progress - oldProgress)")
        }
//            didSet {
//               print("設置的新值與舊值得差值\(progress - oldValue)")
//            }
    }
}
//使用
//屬性觀察者
let propertyObj = propertyObeserver.init()
propertyObj.progress = 200 //!< 將要設置的新值200; 設置的新值與舊值得差值200
propertyObj.progress = 100 //!< 將要設置的新值
複製代碼

注意:若是將具備觀察者的屬性做爲輸入輸出參數傳遞給函數,則始終調用willSetdidSet觀察者方法。由於in-out參數的copy-in copy-out內存模型:在函數結束的時候做爲參數的屬性的值老是會從新寫入原始的屬性中。

關於in-out參數傳遞時的copy-in copy-out copy-in copy-out的行爲也稱爲按值調用結果,具體的行爲有:

•調用該函數時,將複製inout參數的值。

•在函數體中,參數值的副本被修改

•當函數返回的時候,參數值的副本將會賦值給原始的參數。

例如:計算屬性或者具備觀察者的屬性做爲函數的輸入輸出參數傳遞時,它們的getter方法會做爲函數調用的一部分被調用。它們的setter做爲函數返回的一部分被調用。
做爲優化,當參數值是存儲在內存中的物理地址時,在函數的內部和外部會使用相同的內存區域。這個優化被稱爲引用調用。它知足了copy-in copy-out模型的全部要求,同時消除了複製的開銷。使用copy-in copy-out模型編寫代碼,而不依賴於按引用調用優化,以便在有或沒有優化的狀況下copy-in copy-out的行爲是正確的。
在函數內,不要訪問做爲輸入輸出參數傳遞的值,即便原始值在當前範圍內可用。由於違反了Swift的內存獨佔性,同時也沒法將相同的值傳遞給多個輸入輸出參數。

var stepSize = 1

func increment(_ number: inout Int) {
    number += stepSize
}

increment(&stepSize)//!< Error: 訪問 stepSize衝突了
複製代碼

一個捕獲輸入輸出參數的閉包或者嵌套函數必定是非逃逸的。若是須要捕獲輸入輸出參數而不改變它或者要觀察其餘代碼所作的更改,須要使用捕獲列表以不可變的方式顯式捕獲參數。

static func someFunction1(a: inout Int) -> () -> Int {
    return { [a] in return a + 1 }
}
//方法的調用
var b = 7
let c =  someFunction1(a: &b)()
print("\(b)...\(c)")//7...8
複製代碼

上述示例:函數someFunction1具備輸入輸出參數a,函數的返回值是() -> Int函數類型。在someFunction1函數的返回值,中須要捕獲輸入輸出參數a,並在someFunction1函數外部進行調用,所以使用捕獲列表的形式[a]捕獲該輸入輸出參數。
若是須要捕獲並改變輸入輸出參數,則須要使用顯式的本地副本。

//可變的輸入輸出參數
static func mutateFuncation(x:inout Int) ->Void{
    
    //輸出輸出參數`x`會被改變,因此定義一個本地的副本
    var localX = x
    //在函數結束以前須要調用defer關鍵字修飾的代碼,一遍將副本的改變操做,賦值給原始參數`x`
    defer {/*< 在結束範圍以前的'defer'語句老是當即執行。
做用:修飾一段函數內任一段代碼,使其必須在函數中的其他代碼都執行完畢,函數即將結束前調用*/
        x = localX
    }
    changeValue(b: &localX)
}
static func changeValue(b : inout Int) {
    b += 3
}
//調用
var b = 7
mutateFuncation(x: &b)
print("可變的調用結果\(b)")//!< 可變的調用結果10
複製代碼

上述示例:函數mutateFuncation具備輸入輸出參數x,且該參數會在函數內部被changeValue函數改變,所以輸入輸出參數x在函數mutateFuncation內部是被改變的。因此須要使用顯式的本地副本。並定義defer{}代碼塊,以便副本修改完成後能夠賦值給原始的值。

全局和局部變量

計算屬性和具備觀察者的屬性可用於全局變量和局部變量。也能夠在全局或局部範圍定義計算變量,也能夠爲存儲變量定義觀察者。計算變量的定義方式與計算屬性相同。 全局變量:是在任何函數,方法,閉包或類型的上下文以外定義的變量。 局部變量:是在函數,方法或閉包上下文中定義的變量。

類型屬性

實例屬性:特定類型的實例的屬性。每次建立的特定類型的新實例,它都會有屬於本身的一組屬性值,而且和其餘的實例是區分開的。 類型屬性:屬於該類型自己的屬性,無論咱們建立多少該類型的實例,類型屬性只會有一個副本。 類型屬性能夠定義對特定類型的全部實例通用的值。例如全部實例均可以使用的常量屬性(如C中的靜態常量),或者全部實例均可以使用的存儲全局值的變量屬性(如C中的靜態變量)。存儲的類型屬性能夠是變量或常量。計算類型屬性始終聲明爲變量屬性,與實例計算屬性的方式相同。

類型屬性語法

C和Objective-C中,類型屬性是將與類型關聯的靜態常量和變量定義爲全局靜態變量。參考 Swift中,類型屬性是做爲類型定義的一部分進行定義的,在類型的外部{}中,而且每一個類型屬性都顯式限定爲它支持的類型。 使用static關鍵字定義類型屬性。對於類類型的計算類型屬性,可使用class關鍵字來代替static定義類型屬性,從而容許子類重寫父類的實現。

struct SomeStructure {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 1
    }
}
enum SomeEnumeration {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 6
    }
}
class SomeClass {
    static var storedTypeProperty = "Some value."
    static var computedTypeProperty: Int {
        return 27
    }
    class var overrideableComputedTypeProperty: Int {
        return 107
    }
}
class subSomeClass: SomeClass {
   //! 當父類實用static時 此處的重寫會報錯`Cannot override static property1
   override class var overrideableComputedTypeProperty: Int {
        return 227
    }
}
複製代碼

查詢和設置類型屬性

使用點語法進行查詢類型屬性和設置類型屬性,就像實例屬性同樣。可是類型屬性是在類型上查詢和設置類型屬性,而不是在該類型的實例上。

print("獲取結構體的存儲類型屬性\(SomeStructure.storedTypeProperty)")//!< 獲取結構體的存儲類型屬性Some value.
SomeStructure.storedTypeProperty = "Another value."
print("設置結構體的存儲類型屬性後\(SomeStructure.storedTypeProperty)")//!< 設置結構體的存儲類型屬性後Another value.
print("獲取結構體的計算類型屬性\(SomeStructure.computedTypeProperty)")//!< 獲取結構體的計算類型屬性1
print("獲取枚舉類型的計算類型屬性\(SomeEnumeration.computedTypeProperty)")//!< 獲取枚舉類型的計算類型屬性6
print("獲取類類型的計算類型屬性\(SomeClass.computedTypeProperty)")//!< 獲取類類型的計算類型屬性27
print("獲取子類的類類型的計算類型屬性\(subSomeClass.overrideableComputedTypeProperty)")//!< 獲取類類型的計算類型屬性227
//實用場景
struct volume {//!< 定義音量結構體
    static let maxLevel = 20 //!< 最大的音量
    static var isNorse : Bool = false //!< 是不是噪音
    var currentLevel: Int = 0 {//!< 當前音量
        didSet {
            if currentLevel > volume.maxLevel {
                currentLevel = volume.maxLevel
            }
            
            if currentLevel > volume.maxLevel/2 {
                volume.isNorse = true
            } else {
                volume.isNorse = false
            }
        }
    }
}
複製代碼

參考資料: swift 5.1官方編程指南


小編微信:可加並拉入《QiShare技術交流羣》。

關注咱們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公衆號)

推薦文章:
iOS App後臺保活
Swift 中使用 CGAffineTransform
iOS 性能監控(一)—— CPU功耗監控
iOS 性能監控(二)—— 主線程卡頓監控
iOS 性能監控(三)—— 方法耗時監控
初識Flutter web
用SwiftUI給視圖添加動畫
用SwiftUI寫一個簡單頁面
iOS App啓動優化(三)—— 本身作一個工具監控App的啓動耗時
iOS App啓動優化(二)—— 使用「Time Profiler」工具監控App的啓動耗時
iOS App啓動優化(一)—— 瞭解App的啓動流程
奇舞週刊

相關文章
相關標籤/搜索