🎬 HoRain 云小助手个人主页

⛺️生活的理想,就是为了理想的生活!


⛳️ 推荐

前些天发现了一个超棒的服务器购买网站,性价比超高,大内存超划算!忍不住分享一下给大家。点击跳转到网站。

目录

⛳️ 推荐

Swift 属性详解

1. 存储属性(Stored Properties)

基本存储属性

延迟存储属性

2. 计算属性(Computed Properties)

3. 属性观察器(Property Observers)

4. 类型属性(Type Properties)

5. 属性包装器(Property Wrappers)

基本属性包装器

带参数的属性包装器

带 projectedValue 的属性包装器

6. 全局和局部变量

7. 属性访问控制

8. 属性与内存管理

weak 引用属性

unowned 引用属性

9. 属性键路径(Key Paths)

10. 实际应用示例

配置管理

网络请求包装

最佳实践


Swift 属性详解

属性是 Swift 中与特定类型、结构体、枚举或类实例关联的值。Swift 提供了多种属性类型,每种都有不同的用途。

1. 存储属性(Stored Properties)

基本存储属性

struct Person {
    // 变量存储属性
    var name: String
    var age: Int = 0  // 默认值
    
    // 常量存储属性
    let id: String
    
    // 延迟存储属性
    lazy var complexData: ComplexData = ComplexData()
    
    // 初始化器
    init(name: String, id: String) {
        self.name = name
        self.id = id
    }
}

// 使用
var john = Person(name: "John", id: "001")
john.age = 30
// john.id = "002"  // 错误:id 是常量

延迟存储属性

延迟属性只在第一次访问时初始化。

class DataImporter {
    var filename = "data.txt"
    // 假设这是耗时的初始化
    init() {
        print("DataImporter 被初始化")
    }
}

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

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// 此时 importer 还未被创建

print(manager.importer.filename)  // 这里才创建 importer

2. 计算属性(Computed Properties)

计算属性不存储值,而是提供 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)
        }
    }
    
    // 使用简写 setter
    var centerShort: Point {
        get {
            Point(x: origin.x + size.width / 2,
                  y: origin.y + size.height / 2)
        }
        set {
            origin.x = newValue.x - size.width / 2
            origin.y = newValue.y - size.height / 2
        }
    }
    
    // 只读计算属性(可省略 get 关键字)
    var area: Double {
        size.width * size.height
    }
}

3. 属性观察器(Property Observers)

属性观察器监控和响应属性值的变化。

class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("即将设置 totalSteps 为 \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue {
                print("增加了 \(totalSteps - oldValue) 步")
            }
        }
    }
    
    // 使用默认参数名
    var anotherProperty: String = "" {
        willSet {
            print("新值: \(newValue), 旧值: \(anotherProperty)")
        }
        didSet {
            print("已更改: 从 \(oldValue) 到 \(anotherProperty)")
        }
    }
}

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

4. 类型属性(Type Properties)

类型属性属于类型本身,而不是类型的实例。

struct SomeStructure {
    // 存储型类型属性
    static var storedTypeProperty = "Some value."
    
    // 计算型类型属性
    static var computedTypeProperty: Int {
        return 6
    }
}

class SomeClass {
    // 存储型类型属性
    static var storedTypeProperty = "Some value."
    
    // 可被子类重写的计算型类型属性
    class var overrideableComputedTypeProperty: Int {
        return 27
    }
    
    // 只读的静态计算属性
    static var computedTypeProperty: Int {
        return 42
    }
}

// 访问类型属性
print(SomeStructure.storedTypeProperty)  // Some value.
SomeStructure.storedTypeProperty = "Another value."

print(SomeClass.overrideableComputedTypeProperty)  // 27

5. 属性包装器(Property Wrappers)

属性包装器在管理属性存储方式的代码和定义属性的代码之间添加了一个隔离层。

基本属性包装器

@propertyWrapper
struct TwelveOrLess {
    private var number = 0
    var wrappedValue: Int {
        get { number }
        set { number = min(newValue, 12) }
    }
}

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

var rectangle = SmallRectangle()
rectangle.height = 10
print(rectangle.height)  // 10

rectangle.height = 24
print(rectangle.height)  // 12

带参数的属性包装器

@propertyWrapper
struct Clamping<Value: Comparable> {
    var value: Value
    let range: ClosedRange<Value>
    
    init(wrappedValue: Value, _ range: ClosedRange<Value>) {
        self.range = range
        self.value = range.clamp(wrappedValue)
    }
    
    var wrappedValue: Value {
        get { value }
        set { value = range.clamp(newValue) }
    }
}

extension ClosedRange {
    func clamp(_ value: Bound) -> Bound {
        return Swift.min(Swift.max(value, lowerBound), upperBound)
    }
}

struct Temperature {
    @Clamping(0...100) var celsius: Double = 0
}

var temp = Temperature()
temp.celsius = 50
print(temp.celsius)  // 50

temp.celsius = 150
print(temp.celsius)  // 100

temp.celsius = -50
print(temp.celsius)  // 0

带 projectedValue 的属性包装器

@propertyWrapper
struct UserDefaultsWrapper<T> {
    let key: String
    let defaultValue: T
    let userDefaults: UserDefaults = .standard
    
    var wrappedValue: T {
        get {
            return userDefaults.object(forKey: key) as? T ?? defaultValue
        }
        set {
            userDefaults.set(newValue, forKey: key)
        }
    }
    
    // projectedValue 可以提供额外功能
    var projectedValue: String {
        "Key: \(key), Value: \(wrappedValue)"
    }
}

struct Settings {
    @UserDefaultsWrapper(key: "darkMode", defaultValue: false)
    var darkMode: Bool
    
    @UserDefaultsWrapper(key: "fontSize", defaultValue: 16)
    var fontSize: Int
}

var settings = Settings()
settings.darkMode = true
print(settings.darkMode)  // true
print(settings.$darkMode)  // Key: darkMode, Value: true

6. 全局和局部变量

全局变量是定义在任何函数、方法、闭包或类型上下文之外的变量。

局部变量是定义在函数、方法或闭包上下文中的变量。

// 全局变量
var globalVariable = "I'm global"

func someFunction() {
    // 局部变量
    var localVariable = "I'm local"
    
    // 局部变量也可以有属性观察器
    var observedVariable: Int = 0 {
        didSet {
            print("Local variable changed to \(observedVariable)")
        }
    }
    
    observedVariable = 10
}

7. 属性访问控制

public struct TrackedString {
    public private(set) var numberOfEdits = 0
    public var value: String = "" {
        didSet {
            numberOfEdits += 1
        }
    }
    public init() {}
}

var stringToEdit = TrackedString()
stringToEdit.value = "This string will be tracked."
stringToEdit.value += " This edit will increment numberOfEdits."
stringToEdit.value += " So will this one."
print("The number of edits is \(stringToEdit.numberOfEdits)")
// 输出: The number of edits is 3
// stringToEdit.numberOfEdits = 0  // 错误:setter 是私有的

8. 属性与内存管理

weak 引用属性

class Person {
    let name: String
    weak var spouse: Person?  // 弱引用避免循环引用
    
    init(name: String) {
        self.name = name
    }
    
    deinit {
        print("\(name) 被释放")
    }
}

var john: Person? = Person(name: "John")
var jane: Person? = Person(name: "Jane")

john?.spouse = jane
jane?.spouse = john

john = nil
jane = nil

unowned 引用属性

class Customer {
    let name: String
    var card: CreditCard?
    
    init(name: String) {
        self.name = name
    }
    
    deinit { print("\(name) 被释放") }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer  // 无主引用
    
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    
    deinit { print("卡号 \(number) 被释放") }
}

var john: Customer? = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)
john = nil

9. 属性键路径(Key Paths)

struct Person {
    var name: String
    var age: Int
}

let people = [
    Person(name: "Alice", age: 30),
    Person(name: "Bob", age: 25),
    Person(name: "Charlie", age: 35)
]

// 键路径
let nameKeyPath = \Person.name
let ageKeyPath = \Person.age

// 使用键路径
let names = people.map(\.name)  // ["Alice", "Bob", "Charlie"]
let totalAge = people.reduce(0) { $0 + $1[keyPath: ageKeyPath] }

// 实例使用
let person = Person(name: "David", age: 28)
print(person[keyPath: nameKeyPath])  // David

// 可写键路径
var peopleMutable = people
peopleMutable[0][keyPath: nameKeyPath] = "Alicia"

// 嵌套键路径
struct Company {
    var name: String
    var employees: [Person]
}

let company = Company(
    name: "Tech Corp",
    employees: [
        Person(name: "Eve", age: 30),
        Person(name: "Frank", age: 40)
    ]
)

let employeeNamesKeyPath = \Company.employees[0].name
print(company[keyPath: employeeNamesKeyPath])  // Eve

10. 实际应用示例

配置管理

@propertyWrapper
struct UserDefault<T> {
    let key: String
    let defaultValue: T
    
    init(_ key: String, defaultValue: T) {
        self.key = key
        self.defaultValue = defaultValue
    }
    
    var wrappedValue: T {
        get {
            UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
        }
        set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

struct UserSettings {
    @UserDefault("isLoggedIn", defaultValue: false)
    static var isLoggedIn: Bool
    
    @UserDefault("username", defaultValue: "")
    static var username: String
    
    @UserDefault("theme", defaultValue: "light")
    static var theme: String
}

网络请求包装

@propertyWrapper
struct RequestWrapper<T: Decodable> {
    let url: String
    var value: T?
    var error: Error?
    
    var wrappedValue: T? {
        mutating get {
            if value == nil && error == nil {
                loadData()
            }
            return value
        }
        set {
            value = newValue
        }
    }
    
    var projectedValue: Error? { error }
    
    private mutating func loadData() {
        // 实际网络请求代码
        // 这里简化处理
    }
}

struct UserProfile {
    @RequestWrapper(url: "https://api.example.com/profile")
    var profile: Profile?
    
    var isLoading: Bool {
        profile == nil && $profile == nil
    }
}

最佳实践

  1. 明智使用 lazy:只对确实需要延迟初始化的属性使用

  2. 避免循环引用:适当使用 weakunowned

  3. 属性包装器的使用:抽取重复的访问逻辑

  4. 属性观察器的时机:避免在 didSet中设置自身造成无限循环

  5. 类型属性的线程安全:考虑使用 dispatch_once模式

  6. 计算属性缓存:对耗时的计算考虑缓存结果

  7. 属性访问控制:最小化暴露,使用 private(set)

  8. 键路径的优势:在需要动态访问属性时使用

Swift 的属性系统非常强大,通过合理使用各种属性类型,可以编写出更安全、更清晰、更易于维护的代码。

❤️❤️❤️本人水平有限,如有纰漏,欢迎各位大佬评论批评指正!😄😄😄

💘💘💘如果觉得这篇文对你有帮助的话,也请给个点赞、收藏下吧,非常感谢!👍 👍 👍

🔥🔥🔥Stay Hungry Stay Foolish 道阻且长,行则将至,让我们一起加油吧!🌙🌙🌙

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐