• Swift中计算属性的特性和用法
  • 发布于 3天前
  • 34 热度
    0 评论
前言
计算属性是 Swift 中一个很实用得特性,它不存储值,而是提供 getter 和可选的 setter 来间接获取和设置其他属性地值。

基础用法
最常见得用法是根据其他属性来推导出新的值。比如我们经常遇到得文件名拼接:
struct Content {
    var name: String
    let fileExtension: String
    
    var filename: String {
        return name + "." + fileExtension
    }
}

let content = Content(name: "iOS开发笔记", fileExtension: "md")
print(content.filename) // 输出:"iOS开发笔记.md"
filename 属性没有存储任何值,每次访问时都会重新计算。可以去掉 return 关键字:

var filename: String {
    name + "." + fileExtension
}
读写计算属性
上面的例子是只读的,如果需要设置值,可以添加 setter:
struct ContentViewModel {
    privatevar content: Content
    
    init(_ content: Content) {
        self.content = content
    }
    
    var name: String {
        get {
            content.name
        }
        set {
            content.name = newValue
        }
    }
}
// 堆代码 duidaima.com
var content = Content(name: "原始文档", fileExtension: "txt")
var viewModel = ContentViewModel(content)
viewModel.name = "修改后的文档"
print(viewModel.name) // 输出:"修改后的文档"
这样 name 成为了访问内部数据的接口,既可以读取也可以设置。

扩展中使用计算属性
计算属性可以在扩展中为现有类型添加新的属性:
extension CGRect {
    var width: CGFloat {
        size.width
    }
    
    var height: CGFloat {
        size.height
    }
}
let rect = CGRect(x: 0, y: 0, width: 320, height: 480)
print(rect.width) // 输出:320
重写系统属性
在子类中可以重写只读属性:
final class HomeViewController: UIViewController {
    private var shouldHideStatusBar: Bool = true {
        didSet {
            setNeedsStatusBarAppearanceUpdate()
        }
    }
    override var prefersStatusBarHidden: Bool {
        return shouldHideStatusBar
    }
}
高级特性
抛出错误
计算属性可以在计算失败时抛出错误:
struct Content {
    enum ContentError: Error {
        case emptyFileExtension
    }
    
    let name: String
    let fileExtension: String
    
    var filename: String {
        getthrows(ContentError) {
            guard !fileExtension.isEmpty else {
                throw .emptyFileExtension
            }
            return name + "." + fileExtension
        }
    }
}

// 使用时需要加 try
do {
    let filename = try content.filename
    print(filename)
} catch {
    print("文件名生成失败")
}
异步计算属性
对于需要异步操作的场景:
struct FileManager {
    var filename: String
    
    var isValid: Bool {
        get async throws {
            let isValid = await validateFilename(filename)
            return isValid
        }
    }
}

// 使用时需要 await
let isValid = try await fileManager.isValid
使用场景
计算属性适用于以下情况:
1.值依赖其他属性的计算
2.在扩展中为现有类型添加属性
3.作为内部数据的访问接口
需要注意性能问题,计算属性每次访问都会重新执行。看个例子:
struct PeopleViewModel {
    let people: [Person]
    
    var oldest: Person? {
        people.sorted { $0.age > $1.age }.first
    }
}
每次访问 oldest 都会对数组排序,数据量大的时候会影响性能。这种情况建议改为存储属性:
struct PeopleViewModel {
    let people: [Person]
    let oldest: Person?
    
    init(people: [Person]) {
        self.people = people
        oldest = people.sorted { $0.age > $1.age }.first
    }
}
计算属性 vs 方法
选择计算属性还是方法主要看以下几点:
计算属性适用于:
1.表示对象的某种属性状态
2.不需要外部参数
3.计算逻辑相对简单
方法适用于:
1.需要传入参数
2.有复杂的业务逻辑
3.会产生副作用
struct Rectangle {
    var width: Double
    var height: Double
    
    // 计算属性:获取面积
    var area: Double {
        width * height
    }
    
    // 方法:缩放操作
    func scaled(by factor: Double) -> Rectangle {
        Rectangle(width: width * factor, height: height * factor)
    }
}
实际开发中的应用
Model 层的组合属性
struct User {
    let firstName: String
    let lastName: String
    
    var fullName: String {
        "\(firstName) \(lastName)"
    }
}
UIKit 扩展
extension UIView {
    var centerX: CGFloat {
        get { center.x }
        set { center.x = newValue }
    }
    // 堆代码 duidaima.com
    var centerY: CGFloat {
        get { center.y }
        set { center.y = newValue }
    }
}
状态判断
struct DownloadTask {
    var progress: Double
    
    var isCompleted: Bool {
        progress >= 1.0
    }
}
写在最后
计算属性很实用,但要注意性能开销。复杂计算尽量用存储属性或者方法。另外就是语义要清晰,属性就是属性,动作就是动作,别搞混了。你在项目中用过计算属性吗?遇到过什么坑?评论区聊聊!
用户评论