在iOS应用程序中,属性是一种常见的编程元素。它们是访问和设置数据的方法,并且还可以用于执行其他操作。本文将探讨iOS开发中的属性以及如何使用它们。
什么是属性?
属性是一种特殊的变量,它具有getter和setter方法。它们通过getter方法返回属性的值,并且通过setter方法设置属性的值。通常,属性定义为类的成员变量,但也可以在扩展中定义属性。
1
2
3
class MyClass {
var myProperty: Int = 0
}
这个例子中,myProperty
是 MyClass
类的一个属性。它被定义为类型为 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
类包含两个存储属性:width
和 height
。area
是一个计算属性,它通过获取 width
和 height
的值来计算矩形的面积。在访问 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
的属性。当属性的值更改时,willSet
和 didSet
方法都会被调用。在 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
属性并不会被立即创建。只有在第一次访问该属性时,它才会被创建。
在主函数中,我们向 DataManager
的 data
数组中添加了一些数据,然后访问了 importer
属性的 filename
值。由于 importer
属性是延迟存储属性,因此它在第一次访问时被创建,并且可以访问其 filename
值。
类型属性和静态属性
静态属性和类型属性都是属于类型本身而不是实例的属性。它们共享相同的特点,例如:
- 属于类型本身而不是实例。
- 可以是存储属性或计算属性。
- 可以通过访问类型名称作为前缀来访问。
尽管静态属性和类型属性有很多相同之处,但它们之间还有一些重要的区别:
- 继承和重写
静态属性不能被子类继承或重写。如果我们在一个父类中定义了一个静态属性,那么该属性的值对于所有子类而言是相同的,并且无法在子类中修改。
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
并返回了不同的值。
- 实现方式
静态属性是使用关键字 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
}
类型属性是使用关键字 static
或 class
实现的。它们只能用于类和协议。
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
和两个类 MyClass
和 MySubclass
。每个类都定义了一个名为 myTypeProperty
的类型属性,并使用 static
或 class
关键字进行实现。
由于类型属性只能用于类和协议,因此如果我们想在结构体或枚举中使用类型属性,我们必须将它们声明为遵循某个协议。
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
的协议,并在 MyStruct
和 MyEnum
中实现了它。由于结构体和枚举不能直接定义类型属性,我们将它们声明为遵循 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
包装器的属性:height
和 width
。
当创建 BoundedRectangle
实例时,height
属性被初始化为 0,width
属性被初始化为 0。可以看到,我们已经在使用 @BoundedInteger
属性包装器时传递了所需的最小和最大值。由于 height
的最小值为 2,因此将其设置为 5 不会产生错误。然而,width
的最大值为 12,因此将其设置为 15 会导致 precondition 失败异常。
利用属性包装器,我们可以方便地扩展和定制属性的行为,避免了重复编写相似代码的问题。
访问控制
在Swift语言中,属性和其他代码元素可以被标记为特定的访问级别。这些级别控制了哪些代码可以访问它们。
属性可以具有以下四个访问级别:
- private:只能在包含该属性的源文件中访问。
- fileprivate:只能在同一文件中定义的其他实体中访问。
- internal:在整个模块内可用(默认级别)。
- public:在模块外可用,但不能被其他模块继承或重写。
- open:在模块外可用,并且可以被其他模块继承或重写。
要指定属性的访问级别,请在属性声明前添加 private
、fileprivate
、internal
、public
或 open
关键字。
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
在模块外可用,并且可以被其他模块继承或重写。