Swift之屬性

本文首發於個人我的博客html

前言

前面瞭解了 Swift之類Swift之結構體git

這篇文章分享一下Swift中的屬性github

Swift中跟實例相關的屬性能夠分爲2大類

存儲屬性(Stored Property)

  • 相似於成員變量這個概念
  • 存儲在實例的內存中
  • 結構體、類能夠定義存儲屬性
  • 枚舉不能夠定義存儲屬性
    • 關於枚舉不能夠定義存儲屬性,根據以前的Swift枚舉一文可知, 枚舉中存儲關聯值或者keys,不存儲屬性的。

關於存儲屬性,Swift有個明確的規定

  • 在建立類 或 結構體的實例時,必須爲全部的存儲屬性設置一個合適的初始值
    • 能夠在初始化器裏爲存儲屬性設置一個初始值
    • 能夠分配一個默認的屬性值做爲屬性定義的一部分

關於這個規定,咱們在Swift之結構體 一文中已經說過了,這裏稍微提一下,好比下面代碼,x和y都是存儲屬性,當初始化的時候,若是沒值,編譯器會直接報錯。編程

struct Point{
    var x: Int
    var y: Int
}

var p1 = Point(x: 10, y: 20)
var p2 = Point(y: 20) //報錯 Missing argument for parameter 'x' in call
var p3 = Point(x: 10) //報錯 Missing argument for parameter 'y' in call
var p4 = Point()    //報錯 Missing argument for parameter 'x' in call
複製代碼

計算屬性(Computed Property)

  • 本質就是方法(函數)
  • 不佔用實例的內存
  • 枚舉、結構體、類均可以定義計算屬性
  • set傳入的新值默認叫作newValue,也能夠自定義

以下面結構體Circle包括了存儲屬性radius和計算屬性diameterswift

truct Circle {
    // 存儲屬性
    var radius: Double
    // 計算屬性
    var diameter: Double {
        set {
            radius = newValue / 2
        }
        get {
           return radius * 2
        }
    }
}

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
複製代碼
  • 只讀計算屬性:只有get,沒有set 以下
struct Circle {
    var radius: Double
    var diameter: Double {
        get {
            radius * 2
        }
    }
}
複製代碼

只讀計算屬性能夠簡寫,例如上面的代碼能夠以下表示安全

struct Circle {
    var radius: Double
    var diameter: Double { radius * 2 }
}
複製代碼

計算屬性不佔用實例的內存

關於不佔用實例的內存,能夠以下代碼證實bash

struct Circle {
    // 存儲屬性
    var radius: Double
    // 計算屬性
    var diameter: Double {
        set {
            radius = newValue / 2
        }
        get {
           return radius * 2
        }
    }
}
print("Double佔用字節", MemoryLayout<Double>.stride)
print("Circle佔用字節",MemoryLayout<Circle>.stride) // 8

複製代碼

輸出爲app

Double佔用字節 8
Circle佔用字節 8
複製代碼

也就是說Circle佔用的僅僅是其存儲屬性radius所佔用的內存。和計算屬性無關的,讀者也能夠多寫幾個計算屬性,自行驗證。ide

彙編分析存儲屬性和計算屬性的區別

代碼以下函數

struct Circle {
    // 存儲屬性
    var radius: Int
    // 計算屬性
    var diameter: Int {
        set {
            radius = newValue / 2
        }
        get {
           return radius * 2
        }
    }
}

var circle = Circle(radius: 9)
circle.radius = 5 
circle.diameter = 8 //這裏打斷點
複製代碼

  • 5存儲到了全局變量0x3e96(%rip),全局變量只有circle,因此也就是說存儲屬性的值,被直接放在告終構體的內存中。

  • 8賦值給寄存器%r8d,又給寄存器%edi,做爲參數調用函數0x100001ae0的時候傳入,而函數0x100001ae0就是testSwift.Circle.diameter.setter這就是存儲屬性和計算屬性的區別

枚舉rawValue的原理

Swift枚舉一文中,咱們說過枚舉原始值是不佔用內存的。

enum Season: Int{
    case test1 = 1, test2, test3, test4

}

var s = Season.test2
print(s.rawValue) //輸出2

複製代碼

上面代碼輸出爲2。 這些原始值 test1 = 1, test2, test3, test4,系統內部徹底能夠寫成只讀計算屬性

enum Season: Int{
    case test1 = 1, test2, test3, test4
    
    var rawValue : Int {
        switch self {
        case .test1:
            return 11
        case .test2:
            return 12
        case .test3:
            return 13
        case .test4:
            return 14

        }
    }
}

var s = Season.test2
print(s.rawValue) //輸出12
複製代碼

上面代碼輸出爲12,這樣就完成了獲取枚舉的原始值的時候,直接獲取的是隻讀計算屬性。光看結果是不夠使人信服的,那就看彙編

enum Season: Int{
    case test1 = 1, test2, test3, test4

}

var s = Season.test2
print(s.rawValue) //這裏打斷點

複製代碼

上述代碼在最後一行打斷點

能夠看到,確實調用了rawValue.getter

因此彙編纔是看出本質的神器。

延遲存儲屬性(Lazy Stored Property)

使用lazy能夠定義一個延遲存儲屬性,在第一次用到屬性的時候纔會進行初始化

  • lazy屬性必須是var,不能是let
    • let必須在實例的初始化方法完成以前就擁有值
  • 若是多條線程同時第一次訪問lazy屬性
    • 沒法保證屬性只被初始化1次

eg:有類 Car和類Person ,car做爲Person的延遲存儲屬性,那麼當使用car的是,纔會調用car的初始化方法。

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!
複製代碼

注意點

  • 當結構體包含一個延遲存儲屬性時,只有var才能訪問延遲存儲屬性
    • 由於延遲屬性初始化時須要改變結構體的內存

屬性觀察器

  • 能夠爲非lazy的var存儲屬性設置屬性觀察器
struct Circle {
    var radius: Double {
        willSet {
            print("willSet", newValue)
        }
        didSet {
            print("didSet", oldValue, radius)
        }
    }
    init() {
        self.radius = 1.0
        print("Circle init!")
    }
}

var circle = Circle()// 輸出 Circle init!

circle.radius = 10.5 
// 輸出 willSet 10.5 
// didSet 1.0 10.5

print(circle.radius) //輸出 10.5
複製代碼

注意點

  • willSet會傳遞新值,默認叫newValue
  • didSet會傳遞舊值,默認叫oldValue
  • 在初始化器中設置屬性值不會觸發willSet和didSet
  • 在屬性定義時設置初始值也不會觸發willSet和didSet

全局變量、局部變量

  • 屬性觀察器、計算屬性的功能,一樣能夠應用在全局變量、局部變量身上

eg:

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()
複製代碼

類型屬性(Type Property)

類型屬性分類

  1. 實例屬性(Instance Property):只能經過實例去訪問
    • 存儲實例屬性(Stored Instance Property):存儲在實例的內存中,每一個實例都有1份
    • 計算實例屬性(Computed Instance Property)
  2. 類型屬性(Type Property):只能經過類型去訪問
    • 存儲類型屬性(Stored Type Property):整個程序運行過程當中,就只有1分內存(相似於全局變量)
    • 計算類型屬性(Computed Type Property)
  3. 能夠經過static定義類型屬性
    • 若是是類,也能夠用關鍵字class

eg:

struct Car {
    static var count: Int = 0
    init() {
        Car.count += 1
    }
}
let c1 = Car()
let c2 = Car()
let c3 = Car()
print(Car.count) // 輸出3
複製代碼

類型屬性細節

  1. 不一樣於存儲實例屬性,你必須給存儲類型屬性設定初始值
    • 由於類型沒有像實例那樣的init初始化器來初始化存儲屬性
  2. 存儲類型屬性默認就是lazy,會在第一次使用的時候才初始化
    • 就算被多個線程同時訪問,保證只會初始化一次
    • 存儲類型屬性能夠是let
  3. 枚舉類型也能夠定義類型屬性(存儲類型屬性、計算類型屬性)

單例模式

關於單例模式,能夠參考個人另外一篇文章你真的懂單例模式麼

不一樣語言的單例模式,都是相似的,這裏給出Swift版本單例的實現。

import Foundation

public class FileManager {
	//單例模式
    public static let shared = FileManager()
    private init() { }
}

// 若是單例裏面代碼過多,能夠寫成以下
public class FileManager {
    public static let shared = {
        // ....
        // ....
        return FileManager()
    }()
    private init() { }
}
複製代碼

彙編分析 static

下面的代碼

import Foundation

public class YZPerson {
    static  var count = 3 //這裏打斷點
}

YZPerson.count = 6 

複製代碼

以下圖所示,能夠看出,會調用 swift_once函數,來到這個調用位置,si彙編調試指令,一直跟進去

最終會來到這裏,調用dispatch_once_f

也就是說static內部封裝了dispatch_once_dispatch_once_能保證線程安全的,只能被初始化一次,因此單例的時候能夠用static 關於 dispatch_once的分析,能夠看這篇文章你真的懂單例模式麼

  • 一樣的,咱們能夠從代碼角度,彙編角度分別證實 static 修飾的變量,屬於全局變量。讀者有興趣本身證實。這裏再也不贅述。

參考資料:

Swift官方源碼

從入門到精通Swift編程

你真的懂單例模式麼

更多資料,歡迎關注我的公衆號,不定時分享各類技術文章。

相關文章
相關標籤/搜索