10、【Swift】屬性

存儲屬性 - Stored Properties

  • 相當于 OC 的下劃線成員變量

  • 適用于:結構體 、 類

  • 類型:常量 、變量

  • 可以有 默認值(參考 默認構造器 一節(jié))

  • 構造 / 初始化 時,修改 存儲屬性,可修改常量存儲屬性(參考 構造過程中常量屬性的修改 一節(jié))

  • 存儲屬性必須被初始化:

    • 初始化器賦初值
    • 直接賦默認值
  • 結構體使用 存儲屬性

struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 該區(qū)間表示整數(shù) 0,1,2
rangeOfThreeItems.firstValue = 6
// 該區(qū)間現(xiàn)在表示整數(shù) 6,7,8
  • length 在創(chuàng)建實例的時候被初始化,且之后無法修改它的值,因為它是一個常量存儲屬性

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

  • 因結構體為值類型,聲明為常量的結構體實例,無法修改屬性(即使聲明的是變量屬性)(引用類型,可以修改)
struct FixedLengthRange {
    var firstValue: Int
    let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 該區(qū)間表示整數(shù) 0,1,2
rangeOfThreeItems.firstValue = 6
// 該區(qū)間現(xiàn)在表示整數(shù) 6,7,8

延時加載/延遲/懶加載 存儲屬性

  • 場景:將耗時加載的數(shù)據(jù),延時加載

  • 使用時,才會調用初始化方法 init()

  • 聲明前標注lazy 修飾語來表示一個延遲存儲屬性

lazy修飾的屬性,必須為變量。

因為常量屬性,必須在實例初始化完成之前有初始值。

class DataImporter {
    /*
    DataImporter 是一個負責將外部文件中的數(shù)據(jù)導入的類。
    這個類的初始化會消耗不少時間。
    */
    var fileName = "data.txt"
    // 這里會提供數(shù)據(jù)導入功能
}

class DataManager {
    lazy var importer = DataImporter()
    var data = [String]()
    // 這里會提供數(shù)據(jù)管理功能
}

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 實例的 importer 屬性還沒有被創(chuàng)建

lazy 屬性沒初始化,被多個線程訪問,可能會初始化多次

存儲屬性和實例變量

  • OC
    • 讀寫:setter + getter 方法
    • 實例變量:備份存儲
  • Swift
    • 計算屬性

計算屬性 - Computed Properties

  • 場景:不能直接賦值,要計算得出
  • 定義時:不寫 = ,直接寫大括號
  • 相當于 OC 的 property 屬性
  • 適用:類、結構體、枚舉(枚舉的 rawValue 本質:只讀計算屬性)
  • 不存儲值,只提供一個 getter 和一個可選的 setter
struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, 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(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
    size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// 打印“square.origin is now at (10.0, 10.0)” 
img

簡化 Setter 聲明

  • 默認參數(shù)名:newValue (getter 方法沒有參數(shù),只返回)
  • 上面代碼的 setter 簡化寫法
set {
            origin.x = newValue.x - (size.width / 2)
            origin.y = newValue.y - (size.height / 2)
        }

簡化 Getter 聲明

  • 單一表達式,可隱式返回
get {
            Point(x: origin.x + (size.width / 2), y: origin.y + (size.height / 2))
        }

只讀計算屬性

  • 只有 getter 沒有 setter 的計算屬性
  • 總是返回一個值,可以通過點運算符訪問,不能設置新的值

必須使用 var 關鍵字定義計算屬性,包括只讀計算屬性,因為它們的值不是固定的。

let 關鍵字只用來聲明常量屬性,表示初始化后再也無法修改的值。

  • 寫法:

    • 去掉 set
    • 去掉get,return 即可(可隱式返回)
  • 結構體,表示三維空間的立方體,包含 width、heightdepth 屬性。結構體還有一個名為 volume 的只讀計算屬性用來返回立方體的體積。

    struct Cuboid {
        var width = 0.0, height = 0.0, depth = 0.0
        var volume: Double {
            return width * height * depth
        }
    }
    let fourByFiveByTwo = Cuboid(width: 4.0, height: 5.0, depth: 2.0)
    // fourByFiveByTwo.volume = 10 
    // Cannot assign to property: 'volume' is a get-only property
    print("the volume of fourByFiveByTwo is \(fourByFiveByTwo.volume)")
    // 打印“the volume of fourByFiveByTwo is 40.0”
    

屬性觀察器 / 觀察者

  • 給屬性賦值時,都會調用屬性觀察器(新值和當前值相同的時候也不例外)
  • 適用:
    • 存儲屬性(自定義 + 繼承)(子類重寫父類屬性,添加屬性觀察器)
    • 計算屬性(繼承)(自定義建議適用 setter,不建議使用屬性觀察器)
  • 兩種觀察器:
    • willSet 設置新值之前調用
    • didSet 設置新值之后調用
  • 方法的 形式參數(shù)默認值(常量,可重新命名)
    • willSet - newValue
    • didSet - oldValue
    • 在 didSet 觀察器,給屬性賦新值,新值會覆蓋剛賦的值
  • 屬性定義(設置默認值)時,也不會觸發(fā)屬性觀察器
  • 初始化器賦值,不會觸發(fā)(自己)屬性觀察器(可以觸發(fā)父類的)
    • 父類屬性的 willSet 和 didSet ,會在子類初始化器中設置父類屬性時被調用
    • 父類初始化方法調用之前,給子類的屬性賦值時不會調用子類屬性的觀察器
class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("將 totalSteps 的值設置為 \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue  {
                print("增加了 \(totalSteps - oldValue) 步")
            }
        }
    }
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// 將 totalSteps 的值設置為 200
// 增加了 200 步
stepCounter.totalSteps = 360
// 將 totalSteps 的值設置為 360
// 增加了 160 步
stepCounter.totalSteps = 896
// 將 totalSteps 的值設置為 896
// 增加了 536 步

給函數(shù)inout參數(shù)傳參時,會觸發(fā)屬性觀察器

  • 普通實參:沒有設置屬性觀察器 、非計算屬性

    • 直接將 實參內存地址傳入函數(shù)(引用傳遞)
  • 設置了屬性觀察器 / 計算屬性 (采用了copy in copy out - 拷入拷出的內存模式)

    • 傳參時,復制實參的值,產(chǎn)生副本【get】
    • 將副本內存地址傳入函數(shù)(副本進行引用傳遞),函數(shù)內部修改副本的值
    • 函數(shù)結束 / 返回,將副本的值覆蓋實參的值【set】
func test(age:inout Int, name:inout String) -> Void {
    age = 111;
    name = "111"
}

test(age: &s.age, name: &s.name)

屬性包裝 / 包裹 器

  • 場景:利用結構體 + @propertyWrapper + wrapperValue(var wrapper: Int)+ @structName(@TwelveOrLess var height:Int),封裝屬性的 setter + getter 方法

  • 通過使用 wrappedValue 的 getter 和 setter 來獲取這個值,但不能直接使用 number

struct SmallRectangle {
    @TwelveOrLess var height: Int
    @TwelveOrLess var width: Int
}

var rectangle = SmallRectangle()
print(rectangle.height)
// 打印 "0"

rectangle.height = 10
print(rectangle.height)
// 打印 "10"

rectangle.height = 24
print(rectangle.height)
// 打印 "12"  
  • heightwidth 屬性從 TwelveOrLess 的定義中獲取它們的初始值。該定義把 TwelveOrLess.number 設置為 0

  • 存儲 24 的操作實際上存儲的值為 12,這是因為對于這個屬性的 setter 的規(guī)則來說,24 太大了。

  • 不使用 包裝屬性 語法, 是下面的代碼

struct SmallRectangle {
    private var _height = TwelveOrLess()
    private var _width = TwelveOrLess()
    var height: Int {
        get { return _height.wrappedValue }
        set { _height.wrappedValue = newValue }
    }
    var width: Int {
        get { return _width.wrappedValue }
        set { _width.wrappedValue = newValue }
    }
}

設置被包裝屬性的初始值

  • 上述方法弊端:無法在定義時,給屬性賦初值 + 其他自定義操作
  • 增加構造器
@propertyWrapper
struct SmallNumber {
    private var maximum: Int
    private var number: Int

    var wrappedValue: Int {
        get { return number }
        set { number = min(newValue, maximum) }
    }

    init() {
        maximum = 12
        number = 0
    }
    init(wrappedValue: Int) {
        maximum = 12
        number = min(wrappedValue, maximum)
    }
    init(wrappedValue: Int, maximum: Int) {
        self.maximum = maximum
        number = min(wrappedValue, maximum)
    }
}
  • SmallNumber 的定義包括三個構造器——init()、init(wrappedValue:)init(wrappedValue:maximum:)——下面的示例使用這三個構造器來設置被包裝值和最大值。

  • 使用 init() 構造器來設置包裝器。

struct ZeroRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int
}

var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// 打印 "0 0"
  • 構造器內部的代碼使用默認值 0 和 12 設置初始的被包裝值和初始的最大值
  • 使用 init(wrappedValue:) 構造器來設置包裝器
struct UnitRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber var width: Int = 1
}

var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// 打印 "1 1"
  • Swift 使用 init(wrappedValue:maximum:) 構造器
struct NarrowRectangle {
    @SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
    @SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}

var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// 打印 "2 3"

narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// 打印 "5 4"
  • 使用賦值來指定初始值
struct MixedRectangle {
    @SmallNumber var height: Int = 1
    @SmallNumber(maximum: 9) var width: Int = 2
}

var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// 打印 "1"

mixedRectangle.height = 20
print(mixedRectangle.height)
// 打印 "12"

從屬性包裝器中呈現(xiàn)一個值 / 通過屬性包裝映射值

  • 給 SmallNumber 結構體添加了 projectedValue 屬性,以追蹤屬性包裝是否在保存新值之前調整了新值的大小
@propertyWrapper
struct SmallNumber {
    private var number: Int
    var projectedValue: Bool
    init() {
        self.number = 0
        self.projectedValue = false
    }
    var wrappedValue: Int {
        get { return number }
        set {
            if newValue > 12 {
                number = 12
                projectedValue = true
            } else {
                number = newValue
                projectedValue = false
            }
        }
    }
}
struct SomeStructure {
    @SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()

someStructure.someNumber = 4
print(someStructure.$someNumber)
// 打印 "false"

someStructure.someNumber = 55
print(someStructure.$someNumber)
// 打印 "true"
  • 使用 s.someNumber 來訪問包裝的映射值。在保存一個小數(shù)字比如四之后,s.someNumber 的值是 false 。總之,在嘗試保存一個過大的數(shù)字時映射的值就是true 了,比如 55.
enum Size {
    case small, large
}
struct SizedRectangle {
    @SmallNumber var height: Int
    @SmallNumber var width: Int

    mutating func resize(to size: Size) -> Bool {
        switch size {
        case .small:
            height = 10
            width = 20
        case .large:
            height = 100
            width = 100
        }
        return $height || $width
    }
}   
  • 在 resize(to:) 結尾,返回語句檢查height 和width 來決定屬性包裝是否調整了 height 或 width 。

全局變量和局部變量

  • 計算屬性 + 觀察屬性 適用 全局變量局部變量

  • 全局變量: 定義在任何函數(shù)、方法、閉包或者類型環(huán)境 外包的變量

  • 局部變量:定義在函數(shù)、方法或者閉包環(huán)境 內部的變量

  • 存儲型變量:跟存儲屬性類似(前面章節(jié)提到的全局變量 + 局部變量 都是存儲變量)

  • 計算型變量:跟計算屬性類似,與計算屬性的寫法一致,返回變量計算,而不是存儲值

全局的常量或變量都是延遲計算的,跟 延時加載存儲屬性 相似,不同的地方在于,全局的常量或變量不需要標記 lazy 修飾符。

局部范圍的常量和變量從不延遲計算。

類型屬性

  • 屬性分類
    • 實例屬性:實例之間的屬性相互獨立
    • 類型屬性:只有唯一一份(靜態(tài)常量 / 靜態(tài)變量)
      • 存儲型類型屬性:可以是變量或常量
      • 計算型類型屬性:只能是變量
  • 跟實例的存儲型屬性不同,必須給存儲型類型屬性一個默認值
    • 類型本身沒有構造器
    • 也無法初始化過程中使用構造器給類型屬性賦值
  • 存儲型類型屬性會延遲初始化
    • 只有在第一次被訪問的時候才會被初始化
    • 被多個線程同時訪問,系統(tǒng)也保證只會對其進行一次初始化
    • 不需要使用 lazy 修飾符

類型屬性語法

  • 在 C 或 Objective-C 中

    • 靜態(tài)常量和靜態(tài)變量,是作為 global(全局)靜態(tài)變量定義的
  • 在 Swift 中

    • 寫在類型最外層的花括號內
    • 作用范圍也就在類型支持的范圍內
  • 使用關鍵字 static 來定義類型屬性

    • 定義計算型類型屬性時,可以改用關鍵字 class 來支持子類對父類的實現(xiàn)進行重寫
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
    }
}
  • 例子中的計算型類型屬性是只讀的

  • 也可以定義可讀可寫的計算型類型屬性,跟計算型實例屬性的語法相同。

獲取和設置類型屬性的值

  • 跟實例屬性一樣,類型屬性也是通過點運算 / 點語法 符來訪問
  • 類型屬性是通過類型本身來訪問,而不是通過實例
print(SomeStructure.storedTypeProperty)
// 打印“Some value.”
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印“Another value.”
print(SomeEnumeration.computedTypeProperty)
// 打印“6”
print(SomeClass.computedTypeProperty)
// 打印“27”
  • 下圖展示了如何把兩個聲道結合來模擬立體聲的音量。當聲道的音量是 0,沒有一個燈會亮;當聲道的音量是 10,所有燈點亮。本圖中,左聲道的音量是 9,右聲道的音量是 7
img
  • 上面所描述的聲道模型使用 AudioChannel 結構體的實例來表示:
struct AudioChannel {
    static let thresholdLevel = 10
    static var maxInputLevelForAllChannels = 0
    var currentLevel: Int = 0 {
        didSet {
            if currentLevel > AudioChannel.thresholdLevel {
                // 將當前音量限制在閾值之內
                currentLevel = AudioChannel.thresholdLevel
            }
            if currentLevel > AudioChannel.maxInputLevelForAllChannels {
                // 存儲當前音量作為新的最大輸入音量
                AudioChannel.maxInputLevelForAllChannels = currentLevel
            }
        }
    }
}
  • 最大上限閾值,它是一個值為 10 的常量,對所有實例都可見
  • 變量存儲型屬性 maxInputLevelForAllChannels,它用來表示所有 AudioChannel 實例的最大輸入音量,初始值是 0。
  • 定義了一個名為 currentLevel 的存儲型實例屬性
    • 包含 didSet 屬性觀察器來檢查每次設置后的屬性值

在第一個檢查過程中,didSet 屬性觀察器將 currentLevel 設置成了不同的值,但這不會造成屬性觀察器被再次調用

  • 使用
var leftChannel = AudioChannel()
var rightChannel = AudioChannel()
  • 將左聲道的 currentLevel 設置成 7,類型屬性 maxInputLevelForAllChannels 也會更新成 7
leftChannel.currentLevel = 7
print(leftChannel.currentLevel)
// 輸出“7”
print(AudioChannel.maxInputLevelForAllChannels)
// 輸出“7”
  • 右聲道的 currentLevel 設置成 11,它會被修正到最大值 10,同時 maxInputLevelForAllChannels 的值也會更新到 10
rightChannel.currentLevel = 11
print(rightChannel.currentLevel)
// 輸出“10”
print(AudioChannel.maxInputLevelForAllChannels)
// 輸出“10”
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容