天天看點

10.屬性

  屬性将值跟特定的類、結構或枚舉關聯。存儲屬性存儲常量或變量作為執行個體的一部分,而計算屬性計算(不是存儲)一個值。計算屬性可以用于類、結構體和枚舉,存儲屬性隻能用于類和結構體。

  存儲屬性和計算屬性通常與特定類型的執行個體關聯。但是,屬性也可以直接作用于類型本身,這種屬性稱為類型屬性。

  另外,還可以定義屬性觀察器來監控屬性值的變化,以此來觸發一個自定義的操作。屬性觀察器可以添加到自己定義的存儲屬性上,也可以添加到從父類繼承的屬性上。

1.存儲屬性

  • 簡單的說,一個存儲屬性就是存儲在特定類或結構體執行個體裡的一個常量或變量。
struct FixedLengthRange
{
    var firstValue : Int;  //變量
    let length : Int;  //常量
}

var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3);
rangeOfThreeItems.firstValue = 3;
//rangeOfThreeItems.length = 4;  //錯誤,因為length是常量

let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4);
//rangeOfFourItems.firstValue = 3;  //錯誤,因為結構體是值類型

//說明:當值類型的執行個體被聲明為常量的時候,它的所有屬性也就成了常量。      

  在Swift中,有一種特殊的存儲屬性,叫延遲存儲屬性,類似于OC中的懶加載,通過關鍵字 lazy 來标示一個延遲存儲屬性。

class DataImporter
{
    var fileName = "data.txt";
}

class DataManager
{
    lazy var importer = DataImporter();
    var data = [String]();
}

let manager = DataManager();
manager.data.append("Some data");
manager.data.append("Some more data");  //到這裡為止,DataImporter 執行個體的 importer 屬性還沒有被建立

print(manager.importer.fileName);  //DataImporter 執行個體的 importer 屬性現在被建立了

//說明:
//1.使用lazy ,importer 屬性隻有在第一次被通路的時候才被建立。
//2.必須将延遲存儲屬性聲明成變量(使用 var 關鍵字),因為屬性的初始值可能在執行個體構造完成之後才會得到。而常量屬性在構造過程完成之前必須要有初始值,是以無法聲明成延遲屬性。
//3.如果一個被标記為 lazy 的屬性在沒有初始化時就同時被多個線程通路,則無法保證該屬性隻會被初始化一次。
//4.當屬性的值依賴于在執行個體的構造過程結束後才會知道影響值的外部因素時,或者當獲得屬性的初始值需要複雜或大量計算時,可以隻在需要的時候計算它。
//5.延遲存儲屬性必須初始化。      

  類比一下OC中的懶加載:

//實際上就是重寫get方法
- (NSArray *)dataArray
{
    if (nil == _dataArray){
        _dataArray = [NSArray array];
    }
    return _dataArray;
}      

2.計算屬性

  • 計算屬性不直接存儲值,而是提供一個 getter 和一個可選的 setter,來間接擷取和設定其他屬性或變量的值。
struct Point
{
    var x = 0.0;
    var y = 0.0;
}

struct Size
{
    var width = 0.0;
    var height = 0.0;
}

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(value){
            origin.x = value.x - (size.width / 2);
            origin.y = value.y - (size.height / 2);
        }
    };
    
}

var square = Rect(origin: Point(x: 0.0, y: 0.0), size: Size(width: 10.0, height: 20.0));
square.Center = Point(x: 30.0, y: 40.0);

//說明
//1.如果計算屬性的 setter 沒有定義表示新值的參數名,則可以使用預設名稱 newValue。
//2.隻有 getter 沒有 setter 的計算屬性就是隻讀計算屬性。隻讀計算屬性總是傳回一個值,可以通過點運算符通路,但不能設定新的值。隻讀計算屬性的聲明可以去掉 get 關鍵字和花括号。
//3.必須使用 var 關鍵字定義計算屬性,包括隻讀計算屬性,因為它們的值不是固定的。let 關鍵字隻用來聲明常量屬性,表示初始化後再也無法修改的值。      

3.屬性觀察器

  • 屬性觀察器監控和響應屬性值的變化,每次屬性被設定值的時候都會調用屬性觀察器,即使新值和目前值相同的時候也不例外。
  • willSet:在新的值被設定之前調用。 觀察器會将新的屬性值作為常量參數傳入,在 willSet 的實作代碼中可以為這個參數指定一個名稱,如果不指定則參數仍然可用,這時使用預設名稱 newValue 表示。
  • didSet:在新的值被設定之後立即調用。觀察器會将舊的屬性值作為參數傳入,可以為該參數命名或者使用預設參數名 oldValue。如果在 didSet 方法中再次對該屬性指派,那麼新值會覆寫舊的值。
class StepCounter
{
    var totalSteps: Int = 0{
        willSet(newTotalStep){
            print("About to set totalSteps to \(newTotalStep)");
        }
        didSet(oldTotalStep){
            print("Added \(totalSteps - oldTotalStep) steps");
        }
    };
}

let stepCounter = StepCounter();
stepCounter.totalSteps = 200;
stepCounter.totalSteps = 360;
stepCounter.totalSteps = 900;

/*
 列印結果:
 
 About to set totalSteps to 200
 Added 200 steps
 About to set totalSteps to 360
 Added 160 steps
 About to set totalSteps to 900
 Added 540 steps
 */      

4.全局變量和局部變量

  • 計算屬性和屬性觀察器所描述的功能也可以用于全局變量和局部變量。全局變量是在函數、方法、閉包或任何類型之外定義的變量。局部變量是在函數、方法或閉包内部定義的變量。
  • 全局的常量或變量都是延遲計算的,跟延遲存儲屬性相似,不同的地方在于,全局的常量或變量不需要标記

    lazy

    修飾符。

5.類型屬性

  • 類型屬性用于定義某個類型所有執行個體共享的資料。存儲型類型屬性可以是變量或常量,計算型類型屬性跟執行個體的計算型屬性一樣隻能定義成變量屬性。
  • 跟執行個體的存儲型屬性不同,必須給存儲型類型屬性指定預設值,因為類型本身沒有構造器,也就無法在初始化過程中使用構造器給類型屬性指派。
  • 存儲型類型屬性是延遲初始化的,它們隻有在第一次被通路的時候才會被初始化。即使它們被多個線程同時通路,系統也保證隻會對其進行一次初始化,并且不需要對其使用 lazy 修飾符。
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 let storedTypePropertyConstant = "Constant value.";
    static var computedTypeProperty: Int {
        return 27;
    };
    
    class var overrideableComputedTypeProperty: Int {
        return 107;
    };
}

print(SomeStructure.storedTypeProperty)  //輸出 "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)  // 輸出 "Another value."
print(SomeEnumeration.computedTypeProperty)  //輸出 "6"
print(SomeClass.computedTypeProperty)  //輸出 "27"

//說明
//1.在 Swift 中,類型屬性是作為類型定義的一部分寫在類型最外層的花括号内,是以它的作用範圍也就在類型支援的範圍内。
//2.使用關鍵字 static 來定義類型屬性。在為類定義計算型類型屬性時,可以改用關鍵字 class 來支援子類對父類的實作進行重寫。      

 6.通過閉包或函數設定屬性的預設值

  • 如果某個存儲型屬性的預設值需要一些定制或設定,可以使用閉包或全局函數為其提供定制的預設值。每當某個屬性所在類型的新執行個體被建立時,對應的閉包或函數會被調用,而它們的傳回值會當做預設值指派給這個屬性。
  • 這種類型的閉包或函數通常會建立一個跟屬性類型相同的臨時變量,然後修改它的值以滿足預期的初始狀态,最後傳回這個臨時變量,作為屬性的預設值。
  • 閉包結尾的大括号後面接了一對空的小括号。這用來告訴 Swift 立即執行此閉包。如果忽略了這對括号,相當于将閉包本身作為值指派給了屬性,而不是将閉包的傳回值指派給屬性。
class SomeClass
{
    let someProperty: Int =
    {
        let iTemp = 3;
        return iTemp;
    }();
}      

無善無惡心之體,

有善有惡意之動,

知善知惡是良知,

為善去惡是格物。

上一篇: 2.運算符
下一篇: 11.方法