首页 Swift中的属性
文章
取消

Swift中的属性

在iOS应用程序中,属性是一种常见的编程元素。它们是访问和设置数据的方法,并且还可以用于执行其他操作。本文将探讨iOS开发中的属性以及如何使用它们。

什么是属性?

属性是一种特殊的变量,它具有getter和setter方法。它们通过getter方法返回属性的值,并且通过setter方法设置属性的值。通常,属性定义为类的成员变量,但也可以在扩展中定义属性。

1
2
3
class MyClass {
    var myProperty: Int = 0
}

这个例子中,myPropertyMyClass 类的一个属性。它被定义为类型为 Int 的存储属性,并且初始化为 0。如果要访问 myProperty 属性,可以使用点语法:

1
2
3
let instance = MyClass()
instance.myProperty = 1
print(instance.myProperty) // 输出 "1"

上面的代码创建了 MyClass 的实例 instance ,并将 myProperty 属性设置为 1。在打印 myProperty 的值时,输出结果为 1。

计算属性

另一种类型的属性是计算属性。与存储属性不同,计算属性没有实际值。它们提供了一个getter方法和(可选)setter方法,用于计算和返回某些值。

1
2
3
4
5
6
7
8
9
10
11
12
13
class Rectangle {
    var width = 0.0
    var height = 0.0
    
    var area: Double {
        return width * height
    }
}

let rect = Rectangle()
rect.width = 10.0
rect.height = 20.0
print(rect.area) // 输出 "200.0"

在这个示例中,Rectangle 类包含两个存储属性:widthheightarea 是一个计算属性,它通过获取 widthheight 的值来计算矩形的面积。在访问 area 属性时,它将返回计算得出的面积值。

属性观察者

属性观察者是一种特殊的方法,用于监视属性值的更改。willSet 方法会在属性值即将更改时调用,而 didSet 方法则会在属性值已经更改后调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class StepCounter {
    var totalSteps: Int = 0 {
        willSet(newTotalSteps) {
            print("About to set totalSteps to \(newTotalSteps)")
        }
        didSet {
            if totalSteps > oldValue {
                print("Added \(totalSteps - oldValue) steps")
            }
        }
    }
}

let stepCounter = StepCounter()
stepCounter.totalSteps = 100
// About to set totalSteps to 100
// Added 100 steps
stepCounter.totalSteps = 150
// About to set totalSteps to 150
// Added 50 steps

在这个例子中,StepCounter 类有一个名为 totalSteps 的属性。当属性的值更改时,willSetdidSet 方法都会被调用。在 willSet 方法中,我们打印要更改到的新值。在 didSet 方法中,我们检查新值是否比旧值大,并输出步数增加的数量,以此来模拟计步器的功能。

延迟存储属性

延迟存储属性是一种特殊类型的属性,它的值只有在第一次被访问时才会被计算。它们被标记为 lazy 关键字,并且必须是变量(即使用 var 而非 let 声明)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class DataImporter {
    var filename = "data.txt"
    // ...
}

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

let manager = DataManager()
manager.data.append("Some data")
manager.data.append("More data")
// 此时 importer 属性还未被创建
print(manager.importer.filename)
// 此时 importer 属性已经被创建

在这个例子中,我们定义了一个 DataImporter 类来从文件中导入数据,并且将其作为延迟存储属性添加到 DataManager 类中。在创建 DataManager 的实例时,importer 属性并不会被立即创建。只有在第一次访问该属性时,它才会被创建。

在主函数中,我们向 DataManagerdata 数组中添加了一些数据,然后访问了 importer 属性的 filename 值。由于 importer 属性是延迟存储属性,因此它在第一次访问时被创建,并且可以访问其 filename 值。

类型属性和静态属性

静态属性和类型属性都是属于类型本身而不是实例的属性。它们共享相同的特点,例如:

  • 属于类型本身而不是实例。
  • 可以是存储属性或计算属性。
  • 可以通过访问类型名称作为前缀来访问。

尽管静态属性和类型属性有很多相同之处,但它们之间还有一些重要的区别:

  1. 继承和重写

静态属性不能被子类继承或重写。如果我们在一个父类中定义了一个静态属性,那么该属性的值对于所有子类而言是相同的,并且无法在子类中修改。

1
2
3
4
5
6
7
8
9
10
11
12
class MyClass {
    static var myStaticProperty: Int = 0
}

class MySubclass: MyClass {
    // 尝试重写静态属性会导致编译错误
    static override var myStaticProperty: Int {
        didSet {
            print("New value: \(myStaticProperty)")
        }
    }
}

上面的代码演示了一个父类 MyClass 和一个子类 MySubclass。由于 myStaticProperty 是静态属性,因此子类无法重写它。

相反,类型属性可以被子类继承和重写。如果我们在一个父类中定义了一个类型属性,那么每个子类都可以定义其自己的值,并在需要时进行覆盖。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyClass {
    class var myTypeProperty: Int {
        return 0
    }
}

class MySubclass: MyClass {
    override class var myTypeProperty: Int {
        return 1
    }
}

print(MyClass.myTypeProperty) // 输出 "0"
print(MySubclass.myTypeProperty) // 输出 "1"

在这个示例中,我们定义了一个名为 myTypeProperty 的类型属性,并使用 class 关键字标记它可以被子类继承和重写。在 MySubclass 中,我们重写了 myTypeProperty 并返回了不同的值。

  1. 实现方式

静态属性是使用关键字 static 实现的。它们可以用于所有类型,包括类、结构体和枚举。

1
2
3
4
5
6
7
8
9
10
11
struct MyStruct {
    static var myStaticProperty: String = "Hello, world!"
}

enum MyEnum {
    static var myStaticProperty: Int = 42
}

class MyClass {
    static var myStaticProperty: Double = 3.14
}

类型属性是使用关键字 staticclass 实现的。它们只能用于类和协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protocol MyProtocol {
    static var myTypeProperty: Bool { get }
}

class MyClass: MyProtocol {
    static var myTypeProperty: Bool {
        return true
    }
}

class MySubclass: MyClass {
    override static var myTypeProperty: Bool {
        return false
    }
}

在这个示例中,我们定义了一个协议 MyProtocol 和两个类 MyClassMySubclass。每个类都定义了一个名为 myTypeProperty 的类型属性,并使用 staticclass 关键字进行实现。

由于类型属性只能用于类和协议,因此如果我们想在结构体或枚举中使用类型属性,我们必须将它们声明为遵循某个协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protocol MyProtocol {
    static var myTypeProperty: Bool { get }
}

struct MyStruct: MyProtocol {
    static var myTypeProperty: Bool {
        return true
    }
}

enum MyEnum: MyProtocol {
    static var myTypeProperty: Bool {
        return false
    }
}

在这个示例中,我们定义了一个名为 MyProtocol 的协议,并在 MyStructMyEnum 中实现了它。由于结构体和枚举不能直接定义类型属性,我们将它们声明为遵循 MyProtocol 来实现。

属性包装

在 Swift 中,我们可以使用属性包装器(Property Wrappers)来简化属性的定义和管理。属性包装器是一种方便的方式,用于封装属性和与其相关的存储和访问逻辑。它们提供了一个通用框架,允许我们定制属性的行为,而无需每次都重复相同的代码。

属性包装器通过添加一个附加值来实现其功能。这个附加值可以是任何类型的值,例如结构体、类或函数。在使用属性包装器时,我们需要在属性声明前添加一个 @ 符号,并提供对属性包装器类型的引用。

下面是一个示例,演示如何使用属性包装器来限制整数的取值范围:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@propertyWrapper
struct BoundedInteger {
    var value: Int
    let minimum: Int
    let maximum: Int

    init(wrappedValue initialValue: Int, _ minimum: Int, _ maximum: Int) {
        precondition(minimum <= initialValue && initialValue <= maximum,
                     "Initial value \(initialValue) must be between \(minimum) and \(maximum).")
        value = initialValue
        self.minimum = minimum
        self.maximum = maximum
    }

    var wrappedValue: Int {
        get { value }
        set {
            precondition(minimum <= newValue && newValue <= maximum,
                         "New value \(newValue) must be between \(minimum) and \(maximum).")
            value = newValue
        }
    }
}

struct BoundedRectangle {
    @BoundedInteger(0, 2, 10) var height: Int
    @BoundedInteger(0, 3, 12) var width: Int
}

var rectangle = BoundedRectangle()

rectangle.height = 5
print(rectangle.height) // 输出 "5"

rectangle.width = 15 // 抛出 precondition 失败异常

在上面的示例中,我们定义了一个名为 BoundedInteger 的属性包装器,用于限制整数的取值范围。我们还定义了一个结构体 BoundedRectangle,其中包含两个使用 BoundedInteger 包装器的属性:heightwidth

当创建 BoundedRectangle 实例时,height 属性被初始化为 0,width 属性被初始化为 0。可以看到,我们已经在使用 @BoundedInteger 属性包装器时传递了所需的最小和最大值。由于 height 的最小值为 2,因此将其设置为 5 不会产生错误。然而,width 的最大值为 12,因此将其设置为 15 会导致 precondition 失败异常。

利用属性包装器,我们可以方便地扩展和定制属性的行为,避免了重复编写相似代码的问题。

访问控制

在Swift语言中,属性和其他代码元素可以被标记为特定的访问级别。这些级别控制了哪些代码可以访问它们。

属性可以具有以下四个访问级别:

  • private:只能在包含该属性的源文件中访问。
  • fileprivate:只能在同一文件中定义的其他实体中访问。
  • internal:在整个模块内可用(默认级别)。
  • public:在模块外可用,但不能被其他模块继承或重写。
  • open:在模块外可用,并且可以被其他模块继承或重写。

要指定属性的访问级别,请在属性声明前添加 privatefileprivateinternalpublicopen 关键字。

1
2
3
4
5
6
7
public class MyClass {
    private var myPrivateProperty: Int = 0
    fileprivate var myFilePrivateProperty: Int = 0
    internal var myInternalProperty: Int = 0
    public var myPublicProperty: Int = 0
    open var myOpenProperty: Int = 0
}

在这个例子中,我们定义了一个名为 MyClass 的公共类,并为它的五个属性设置了不同的访问级别。myPrivateProperty 可以在类的内部访问,但不能从类的外部访问。myFilePrivateProperty 只能在同一文件中定义的其他实体中访问。myInternalProperty 在整个模块内可用。myPublicProperty 在模块外可用,但不能被其他模块继承或重写。最后,myOpenProperty 在模块外可用,并且可以被其他模块继承或重写。

本文由作者按照 CC BY 4.0 进行授权

Swift中的类和结构体

Swift中的方法(Methods)