Swift 中的属性(Properties)

hudson译 原文

就像许多其他编程语言一样,Swift通过将内存数据存储在属性中来组织内存数据——常量和变量附加到给定类型、值或对象上。在这篇Swift基础知识的文章中,我们将介绍各种属性的几个例子及其特征。

可变属性及其存储的值类型使用var关键字声明——除非编译器可以推断后者。例如,在这里,Book类型中定义了三个属性——其中两个是手动键入为 String,而第三个属性将从其默认值推断出其类型(Int):

struct Book {
    var name: String
    var author: String
    var numberOfStars = 0
}

另一方面,如果想防止一个属性在初始化后修改,可以使用let关键字使其成为常量属性——像这样:


struct Book {
    let id: UUID
    var name: String
    var author: String
    var numberOfStars = 0
}

下面代码使用了四个属性的值创建一个Book实例(如果愿意,可以省略numberOfStars,因为它有一个默认值),之后只能修改可变属性:

var book = Book(
    id: UUID(),
    name: ”The Swift Programming Language“,
    author: ”Apple“
)
book.id = UUID() // Compiler error
book.name =New name“ // Allowed
book.numberOfStars += 1 // Also allowed

以上都是存储属性的示例——属性在分配后其值存储在内存中。另一方面,计算属性在每次访问时重新计算,能够以属性的形式定义便利API。

例如,如果要求检查给定书籍的名称是否超过30个字符——可以将该计算封装在计算属性 hasLongName中,如下:

extension Book {
    var hasLongName: Bool {
        name.count > 30
    }
}

计算属性的好处是,不需要手动将它们与派生它们的底层状态同步,因为每次都会重新计算。然而,这也意味着必须小心,不要在它们内部进行任何繁重的计算。有关该主题的更多信息,请查看“Swift中的计算属性”。

虽然上述计算属性是只读的,但也可以定义同时具有gettersetter的属性(除了其值没有存储外,其它行为与var相同)。例如,假设想修改Book类型的author属性,以包含Author值,而不是String

struct Author {
    var name: String
    var country: String
}
struct Book {
    let id: UUID
    var name: String
    var author: Author
    var numberOfStars = 0
}

为了仍然能够轻松访问和修改给定书籍的作者名称,可以添加一个带有gettersetter的计算属性——像这样:

extension Book {
    var authorName: String {
        get { author.name }
        set { author.name = newValue }
    }
}

接下来,让我们看看lazy属性。 标有lazy关键字的属性必须是可变的,并为其分配一个默认值——但是,在首次访问该属性之前,不会计算该默认值。 这种特征使得惰性属性可以充当计算属性和存储属性之间的“中间地带”,这在构建基于视图控制器的用户界面时非常方便。

由于在系统调用viewDidLoad方法之前,视图控制器不应该创建其子视图,因此避免将此类子视图存储为可选视图的一个非常巧妙的方法是使它们变得懒惰——这将推迟其创建,直到它们被首次访问(例如在viewDidLoad中):

class BookViewController: UIViewController {
    private lazy var nameLabel = UILabel()
    private lazy var authorLabel = UILabel()
    ...
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(nameLabel)
        view.addSubview(authorLabel)
    }
}

上述两个属性被标记为私有的,仅使它们在视图控制器本身中可见。要了解更多信息,请查看有关访问控制的基础知识文章

懒惰属性也可以通过闭包或函数计算其值。例如,在这里,使用私有makeNameLabel方法为nameLabel属性创建和设置UILabel

class BookViewController: UIViewController {
    private lazy var nameLabel = makeNameLabel()
    private lazy var authorLabel = UILabel()
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(nameLabel)
        view.addSubview(authorLabel)
    }
    private func makeNameLabel() -> UILabel {
        let label = UILabel()
        label.font = .preferredFont(forTextStyle: .largeTitle)
        label.textColor = .orange
        return label
    }
}

到目前为止,我们一直专注于实例属性,这些属性都与一种类型的单个实例相关联。但我们也可以定义静态属性——那些附加到类型本身而不是其实例的属性。当想在一个类型的所有实例中共享给定对象时,这些属性非常有用,可以避免多次重新创建它们。

例如,在这里,定义了一个静态dateFormatter属性,该属性是通过自我执行的闭包计算的:

extension Book {
    static let dateFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = ”yyyy-MM-dd“
        return formatter
    }()
}

静态属性隐式是懒惰的,因为它们只有在首次访问时才会被计算。

如果想将与图书相关的日期转换为字符串,现在就可以使用上述日期格式化器, 反之亦然:

let string = Book.dateFormatter.string(from: date)

还可以在存储属性上附加观察器,在其每次赋值(或将要赋值)时运行一段代码。例如,使用didSet属性观察器在每次更改numberOfStars属性时自动更新标签:

class RatingViewController: UIViewController {
    var numberOfStars = 0 {
        didSet { starCountLabel.text = String(numberOfStars) }
    }
    private lazy var starCountLabel = UILabel()
    ...
}

属性观察器有一个willSet变体,在属性的值赋值之前触发,而不是之后触发。要了解更多信息,请查看“Swift中的属性观察器”。

属性观察器也可用于验证或修改每个新值——例如,确保数值保持在一定范围内,例如:

struct Book {
    let id: UUID
    var name: String
    var author: Author
    var numberOfStars = 0 {
        didSet {
            // If the new value was higher than 9999, we reduce
            // it down to that value, which is our maximum:
            numberOfStars = min(9999, numberOfStars)
        }
    }
}

最后,使用键路径可以以更动态的方式引用属性——只需传递对属性本身的引用,而不是对其值的引用。

键路径也可以自动转换为函数(自Swift 5.2以来),如果想从一组实例集合中提取给定属性的值的时候非常有用——例如,通过在数组上使用map,例如:

// Converting an array of books into an array of strings, by
// extracting each book‘s name:
let books = loadBooks() // [Book]
let bookNames = books.map(.name) // [String]

要了解有关关键路径及其与函数的关系的更多信息,请查看这一集Swift 剪辑

这些只是在Swift中使用属性的多种方法的几个例子。这篇文章温习了Swift属性的一些基本功能,希望能够激励你进一步探索其它一些深入主题。

感谢您的阅读!