Swift 外部只读属性如何实现封装与访问控制的最佳实践

在 Swift 编程语言中,属性(Property)是类、结构体和枚举中存储值的特定位置。Swift 提供了灵活的属性访问控制机制,允许开发者精确地定义属性在不同模块或作用域下的可见性和可变性。其中,“外部只读属性”是一个常见的设计需求,即在模块外部只能读取属性值,而不能修改它,但在模块内部可以自由修改。这种机制是实现封装(Encapsulation)和数据保护的核心手段。
本文将深入探讨 Swift 中实现外部只读属性的各种方法,结合访问控制修饰符(Access Control Modifiers)和计算属性(Computed Properties),提供最佳实践建议。我们将通过详细的代码示例来说明每种方法的优缺点,并讨论在实际开发中的应用场景。
1. 理解 Swift 的访问控制修饰符
Swift 提供了五个主要的访问控制修饰符,用于控制代码元素的可见性。这些修饰符是实现外部只读属性的基础:
open
:最高访问级别,允许在模块内外访问和继承。
public
:允许在模块内外访问,但不能在模块外继承或重写。
internal
:默认级别,只允许在当前模块内访问。
fileprivate
:只允许在定义它们的源文件内访问。
private
:只允许在定义它们的声明作用域内访问。
在实现外部只读属性时,我们通常使用
public
或
open
与
private(set)
的组合,或者使用计算属性来限制外部的写权限。
2. 使用
private(set)
实现外部只读属性
private(set)
是一种特殊的访问控制修饰符,用于限制属性的 setter 方法的访问级别,而 getter 方法则保持原有的访问级别。这是实现外部只读属性最直接和常用的方法。
2.1 基本用法
假设我们有一个
User
类,它有一个
name
属性,我们希望在模块外部只能读取
name
,但在模块内部可以修改它。
public class User {
public var name: String {
get {
return _name
}
set {
_name = newValue
}
}

private var _name: String

public init(name: String) {
self._name = name
}

// 模块内部可以修改 name
internal func updateName(newName: String) {
self.name = newName
}
}

// 在模块外部
let user = User(name: “Alice”)
print(user.name) // 可以读取
// user.name = “Bob” // 编译错误:无法设置属性
然而,上述代码使用了计算属性和存储属性结合的方式,略显复杂。Swift 提供了更简洁的语法:使用
private(set)
修饰存储属性。
public class User {
public private(set) var name: String

public init(name: String) {
self.name = name
}

// 模块内部可以修改 name
internal func updateName(newName: String) {
self.name = newName
}
}
在这个例子中:
public
使得
name
属性在模块外部可见(可读)。
private(set)
限制了 setter 方法只能在当前模块内访问,因此在模块外部无法修改
name
。
在模块内部,我们可以直接通过
self.name = newName
修改属性。
2.2 使用
internal
和
private(set)
的组合
如果你希望属性只在模块内部可见和可写,而在模块外部完全不可见,可以使用
internal private(set)
:
internal class User {
internal private(set) var name: String

internal init(name: String) {
self.name = name
}

internal func updateName(newName: String) {
self.name = newName
}
}
在这个例子中:
internal
使得
name
属性在模块内部可见(可读)。
private(set)
限制了 setter 方法只能在当前类内部访问,因此在模块内部的其他地方也无法修改
name
,除非通过类内部的方法。
2.3 使用
fileprivate
和
private(set)
的组合
如果你希望属性只在当前文件内可见和可写,可以使用
fileprivate private(set)
:
public class User {
public private(set) var name: String

public init(name: String) {
self.name = name
}

// 只有在当前文件内才能修改 name
fileprivate func updateName(newName: String) {
self.name = newName
}
}
在这个例子中:
public
使得
name
属性在模块外部可见(可读)。
private(set)
限制了 setter 方法只能在当前类内部访问。
fileprivate
修饰的
updateName
方法可以在当前文件内调用,从而间接修改
name
。
3. 使用计算属性实现外部只读属性
除了使用
private(set)
,我们还可以通过计算属性(Computed Properties)来实现外部只读属性。计算属性本身不存储数据,而是通过 getter 和 setter 方法来访问和修改其他属性。
3.1 基本用法
假设我们有一个
Person
类,它有一个内部存储属性
_age
,和一个外部只读的计算属性
age
。
public class Person {
private var _age: Int

public var age: Int {
get {
return _age
}
// 没有 set 方法,因此外部无法修改 age
}

public init(age: Int) {
self._age = age
}

// 模块内部可以修改 _age
internal func updateAge(newAge: Int) {
self._age = newAge
}
}

// 在模块外部
let person = Person(age: 25)
print(person.age) // 可以读取
// person.age = 30 // 编译错误:无法设置属性,因为没有 set 方法
在这个例子中:
_age
是一个私有存储属性,用于实际存储年龄。
age
是一个公共计算属性,只有 getter 方法,没有 setter 方法,因此在模块外部只能读取。
在模块内部,可以通过
updateAge
方法修改
_age
,从而间接修改
age
。
3.2 使用计算属性封装逻辑
计算属性不仅可以实现外部只读,还可以在 getter 中添加额外的逻辑,例如数据验证、格式化等。
public class Product {
private var _price: Double

public var price: Double {
get {
return _price
}
// 外部无法直接设置 price
}

public var formattedPrice: String {
get {
return String(format: “$%.2f”, _price)
}
}

public init(price: Double) {
self._price = price
}

// 模块内部可以修改价格
internal func updatePrice(newPrice: Double) {
if newPrice >= 0 {
self._price = newPrice
}
}
}

// 在模块外部
let product = Product(price: 19.99)
print(product.price) // 19.99
print(product.formattedPrice) // $19.99
// product.price = 25.0 // 编译错误
在这个例子中:
_price
是私有存储属性,用于实际存储价格。
price
是公共计算属性,只有 getter 方法,外部只能读取。
formattedPrice
是另一个计算属性,用于格式化价格,外部可以读取但不能修改。
在模块内部,可以通过
updatePrice
方法修改
_price
,并添加了非负验证。
4. 使用结构体(Struct)实现外部只读属性
在 Swift 中,结构体(Struct)是值类型,通常用于表示简单的数据模型。结构体也可以有属性和方法,并且支持访问控制。
4.1 使用
private(set)
修饰结构体属性
结构体的属性也可以使用
private(set)
来实现外部只读。
public struct Point {
public private(set) var x: Double
public private(set) var y: Double

public init(x: Double, y: Double) {
self.x = x
self.y = y
}

// 模块内部可以修改 x 和 y
internal mutating func move(to newX: Double, newY: Double) {
self.x = newX
self.y = newY
}
}

// 在模块外部
var point = Point(x: 1.0, y: 2.0)
print(point.x, point.y) // 1.0 2.0
// point.x = 3.0 // 编译错误:无法设置属性
point.move(to: 3.0, newY: 4.0) // 可以通过方法修改
在这个例子中:
x
和
y
是公共存储属性,但使用了
private(set)
,因此在模块外部只能读取。
move
方法是内部方法,可以在模块内部调用,修改
x
和
y
。
注意:结构体是值类型,方法需要声明为
mutating
才能修改属性。
4.2 使用计算属性在结构体中
结构体也可以使用计算属性来实现外部只读。
public struct Rectangle {
private var _width: Double
private var _height: Double

public var width: Double {
get { return _width }
}

public var height: Double {
get { return _height }
}

public var area: Double {
get { return _width * _height }
}

public init(width: Double, height: Double) {
self._width = width
self._height = height
}

// 模块内部可以修改尺寸
internal mutating func resize(newWidth: Double, newHeight: Double) {
self._width = newWidth
self._height = newHeight
}
}

// 在模块外部
var rect = Rectangle(width: 5.0, height: 10.0)
print(rect.width) // 5.0
print(rect.height) // 10.0
print(rect.area) // 50.0
// rect.width = 6.0 // 编译错误
rect.resize(newWidth: 6.0, newHeight: 12.0) // 可以通过方法修改
在这个例子中:
_width
和
_height
是私有存储属性,用于实际存储尺寸。
width
和
height
是公共计算属性,只有 getter 方法,外部只能读取。
area
是另一个计算属性,用于计算面积,外部可以读取但不能修改。
在模块内部,可以通过
resize
方法修改私有属性。
5. 使用枚举(Enum)实现外部只读属性
枚举(Enum)是 Swift 中的另一种值类型,通常用于表示一组相关的选项。枚举可以有属性和方法,但枚举的属性必须是计算属性。
5.1 使用计算属性在枚举中
假设我们有一个
NetworkStatus
枚举,它有一个外部只读的计算属性
description
。
public enum NetworkStatus {
case connected
case disconnected
case connecting

public var description: String {
switch self {
case .connected:
return “Connected”
case .disconnected:
return “Disconnected”
case .connecting:
return “Connecting”
}
}
}

// 在模块外部
let status = NetworkStatus.connected
print(status.description) // Connected
// status.description = “New Status” // 编译错误:无法设置属性
在这个例子中:
description
是一个公共计算属性,只有 getter 方法,没有 setter 方法,因此外部只能读取。
枚举的属性必须是计算属性,不能是存储属性。
5.2 使用关联值和计算属性
枚举可以使用关联值(Associated Values)来存储额外的数据,并通过计算属性访问这些数据。
public enum Result {
case success(T)
case failure(Error)

public var value: T? {
switch self {
case .success(let value):
return value
case .failure:
return nil
}
}

public var error: Error? {
switch self {
case .success:
return nil
case .failure(let error):
return error
}
}
}

// 在模块外部
let result = Result.success(42)
print(result.value) // Optional(42)
print(result.error) // nil
// result.value = 100 // 编译错误
在这个例子中:
value
和
error
是公共计算属性,用于从关联值中提取数据,外部只能读取。
由于枚举的属性必须是计算属性,因此无法直接修改关联值,只能通过创建新的枚举实例来改变状态。
6. 最佳实践总结
在 Swift 中实现外部只读属性有多种方法,选择哪种方法取决于具体的需求和上下文。以下是一些最佳实践建议:
6.1 优先使用
private(set)
对于存储属性,使用
private(set)
是最简洁和高效的方式。它直接限制了属性的 setter 方法,而保持 getter 方法的访问级别不变。这种方法适用于类和结构体。
public class MyClass {
public private(set) var value: Int

public init(value: Int) {
self.value = value
}

internal func updateValue(newValue: Int) {
self.value = newValue
}
}
6.2 使用计算属性封装复杂逻辑
如果属性的值需要动态计算、验证或格式化,使用计算属性是更好的选择。计算属性可以隐藏内部实现细节,并提供额外的逻辑。
public class MyClass {
private var _internalValue: Int

public var externalValue: Int {
get {
return _internalValue * 2
}
}

public init(value: Int) {
self._internalValue = value
}

internal func updateInternalValue(newValue: Int) {
self._internalValue = newValue
}
}
6.3 结构体和枚举的特殊考虑
结构体
:由于是值类型,修改属性需要使用
mutating
方法。使用
private(set)
可以确保外部无法直接修改属性,但可以通过方法间接修改。
枚举
:枚举的属性必须是计算属性,因此无法直接存储数据。通过计算属性可以提供外部只读的访问,但无法实现外部可写的属性(除非使用关联值和方法)。
6.4 模块化设计
在设计模块时,明确哪些属性是公共的、哪些是内部的。使用
internal
和
fileprivate
来进一步限制访问范围,提高封装性。
6.5 避免暴露可变状态
尽量减少公共可变状态的暴露。如果某个属性在外部不应该被修改,就将其设置为外部只读。这有助于减少副作用和提高代码的可维护性。
7. 实际应用场景
7.1 网络请求管理器
假设我们有一个网络请求管理器,它有一个
isFetching
属性,表示当前是否正在请求数据。我们希望外部只能读取这个状态,而不能修改它。
public class NetworkManager {
public private(set) var isFetching: Bool = false

public func fetchData() {
guard !isFetching else { return }
isFetching = true

// 模拟网络请求
DispatchQueue.global().asyncAfter(deadline: .now() + 2) {
self.isFetching = false
print(“Data fetched”)
}
}
}

// 在模块外部
let manager = NetworkManager()
print(manager.isFetching) // false
manager.fetchData()
print(manager.isFetching) // true
// manager.isFetching = false // 编译错误
在这个例子中:
isFetching
是外部只读的,外部无法直接修改它。
只有通过
fetchData
方法才能改变
isFetching
的状态,确保了状态的一致性。
7.2 用户配置管理器
假设我们有一个用户配置管理器,它有一个
theme
属性,表示当前的主题设置。我们希望外部只能读取主题,但不能修改它,修改主题只能通过特定的方法。
public enum Theme {
case light
case dark
}

public class UserSettings {
public private(set) var theme: Theme

public init(theme: Theme) {
self.theme = theme
}

// 模块内部可以修改主题
internal func updateTheme(newTheme: Theme) {
self.theme = newTheme
}
}

// 在模块外部
let settings = UserSettings(theme: .light)
print(settings.theme) // light
// settings.theme = .dark // 编译错误
在这个例子中:
theme
是外部只读的,外部无法直接修改它。
只有通过
updateTheme
方法才能改变主题,确保了主题修改的可控性。
8. 总结
Swift 提供了多种机制来实现外部只读属性,包括使用
private(set)
修饰符、计算属性、结构体和枚举的特性。选择合适的方法取决于具体的需求和上下文。
private(set)
是最直接和高效的方式,适用于存储属性。
计算属性
提供了更大的灵活性,适用于需要动态计算或封装逻辑的场景。
结构体和枚举
有其特殊性,需要根据值类型的特点选择合适的方法。
通过合理使用这些机制,可以有效地实现封装和访问控制,保护数据的完整性,提高代码的可维护性和可读性。在实际开发中,应根据具体需求选择最合适的方法,并遵循 Swift 的最佳实践。
# Swift 外部只读属性如何实现封装与访问控制的最佳实践
在 Swift 编程中,实现外部只读属性是封装和访问控制的核心需求。本文将详细探讨多种实现方式,并提供最佳实践建议。
1. 理解访问控制修饰符
Swift 提供了五个访问控制修饰符,它们决定了代码元素在不同模块和源文件中的可见性:
open
:最高访问级别,允许在模块内外访问和继承
public
:允许在模块内外访问,但不能在模块外继承或重写
internal
:默认级别,只允许在当前模块内访问
fileprivate
:只允许在定义它们的源文件内访问
private
:只允许在定义它们的声明作用域内访问
2. 使用 private(set) 实现外部只读属性
private(set)
是最常用的方法,它限制属性的 setter 方法访问级别,而 getter 方法保持原有访问级别。
2.1 基本实现
// 定义一个模块(例如 Framework)
public class User {
// 外部可读,内部可写
public private(set) var name: String
public private(set) var age: Int

public init(name: String, age: Int) {
self.name = name
self.age = age
}

// 内部方法可以修改属性
internal func updateName(newName: String) {
self.name = newName
}

internal func celebrateBirthday() {
self.age += 1
}
}

// 在另一个模块中使用
import UserModule

let user = User(name: “Alice”, age: 25)
print(user.name) // ✅ 可以读取:Alice
print(user.age) // ✅ 可以读取:25

// user.name = “Bob” // ❌ 编译错误:Cannot assign to property ‘name’
// user.age = 30 // ❌ 编译错误:Cannot assign to property ‘age’
2.2 不同访问级别的组合
public class BankAccount {
// 公开只读,内部可写
public private(set) var balance: Double = 0.0

// 模块内只读,外部不可见
internal private(set) var accountNumber: String

// 文件内可写,外部不可见
private(set) var transactionCount: Int = 0

public init(accountNumber: String) {
self.accountNumber = accountNumber
}

// 内部方法可以修改所有属性
internal func deposit(amount: Double) {
balance += amount
transactionCount += 1
}

internal func withdraw(amount: Double) {
if balance >= amount {
balance -= amount
transactionCount += 1
}
}
}
3. 使用计算属性实现外部只读
计算属性提供了更灵活的控制,可以在 getter 中添加额外逻辑。
3.1 基本计算属性
public class Product {
private var _price: Double
private var _discount: Double

// 外部只读的计算属性
public var price: Double {
return _price * (1 – _discount)
}

// 外部只读的格式化价格
public var formattedPrice: String {
return String(format: “$%.2f”, price)
}

// 外部只读的折扣信息
public var discountInfo: String {
return _discount > 0 ? “Discount: \(_discount * 100)%” : “No discount”
}

public init(price: Double, discount: Double = 0.0) {
self._price = price
self._discount = discount
}

// 内部方法可以修改基础属性
internal func updatePrice(newPrice: Double) {
self._price = newPrice
}

internal func applyDiscount(_ discount: Double) {
self._discount = max(0, min(discount, 1.0)) // 限制在 0-1 之间
}
}

// 使用示例
let product = Product(price: 100.0, discount: 0.1)
print(product.price) // 90.0
print(product.formattedPrice) // $90.00
print(product.discountInfo) // Discount: 10.0%

// product.price = 80.0 // ❌ 编译错误
// product.discount = 0.2 // ❌ 编译错误
3.2 带有复杂逻辑的计算属性
public class UserProfile {
private var _firstName: String
private var _lastName: String
private var _birthDate: Date

// 外部只读的全名
public var fullName: String {
return “\(_firstName) \(_lastName)”
}

// 外部只读的年龄(动态计算)
public var age: Int {
let calendar = Calendar.current
let components = calendar.dateComponents([.year], from: _birthDate, to: Date())
return components.year ?? 0
}

// 外部只读的个人资料摘要
public var profileSummary: String {
return “\(fullName), \(age) years old”
}

public init(firstName: String, lastName: String, birthDate: Date) {
self._firstName = firstName
self._lastName = lastName
self._birthDate = birthDate
}

// 内部方法可以更新基础信息
internal func updateName(firstName: String, lastName: String) {
self._firstName = firstName
self._lastName = lastName
}
}
4. 结构体中的外部只读属性
结构体是值类型,需要使用
mutating
方法来修改属性。
4.1 使用 private(set) 的结构体
public struct Point {
public private(set) var x: Double
public private(set) var y: Double

public init(x: Double, y: Double) {
self.x = x
self.y = y
}

// 移动点的 mutating 方法
public mutating func move(to newX: Double, newY: Double) {
self.x = newX
self.y = newY
}

// 相对移动
public mutating func translate(dx: Double, dy: Double) {
self.x += dx
self.y += dy
}

// 外部只读的计算属性:距离原点的距离
public var distanceFromOrigin: Double {
return sqrt(x * x + y * y)
}
}

// 使用示例
var point = Point(x: 3.0, y: 4.0)
print(point.x) // 3.0
print(point.y) // 4.0
print(point.distanceFromOrigin) // 5.0

// point.x = 5.0 // ❌ 编译错误
point.move(to: 5.0, newY: 12.0) // ✅ 可以通过方法修改
print(point.distanceFromOrigin) // 13.0
4.2 嵌套结构体的封装
public struct Rectangle {
public private(set) var origin: Point
public private(set) var size: Size

public init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}

// 外部只读的计算属性
public var area: Double {
return size.width * size.height
}

public var perimeter: Double {
return 2 * (size.width + size.height)
}

public var center: Point {
return Point(
x: origin.x + size.width / 2,
y: origin.y + size.height / 2
)
}

// 内部移动方法
internal mutating func move(to newOrigin: Point) {
self.origin = newOrigin
}

// 内部调整大小
internal mutating func resize(to newSize: Size) {
self.size = newSize
}
}

public struct Size {
public var width: Double
public var height: Double

public init(width: Double, height: Double) {
self.width = width
self.height = height
}
}
5. 枚举中的外部只读属性
枚举的属性必须是计算属性,因为枚举本身不存储实例变量。
5.1 基本枚举属性
public enum NetworkState {
case idle
case connecting
case connected
case downloading(progress: Double)
case failed(error: Error)

// 外部只读的描述
public var description: String {
switch self {
case .idle:
return “Idle”
case .connecting:
return “Connecting…”
case .connected:
return “Connected”
case .downloading(let progress):
return “Downloading: \(Int(progress * 100))%”
case .failed(let error):
return “Failed: \(error.localizedDescription)”
}
}

// 外部只读的状态值
public var isActive: Bool {
switch self {
case .connecting, .connected, .downloading:
return true
case .idle, .failed:
return false
}
}

// 外部只读的进度值
public var progress: Double {
switch self {
case .downloading(let progress):
return progress
default:
return 0.0
}
}
}

// 使用示例
let state1 = NetworkState.idle
print(state1.description) // “Idle”
print(state1.isActive) // false

let state2 = NetworkState.downloading(progress: 0.75)
print(state2.description) // “Downloading: 75%”
print(state2.isActive) // true
print(state2.progress) // 0.75
5.2 关联值与外部只读属性
public enum Result {
case success(T)
case failure(Error)

// 外部只读的成功值
public var value: T? {
guard case .success(let value) = self else { return nil }
return value
}

// 外部只读的错误值
public var error: Error? {
guard case .failure(let error) = self else { return nil }
return error
}

// 外部只读的状态
public var isSuccess: Bool {
guard case .success = self else { return false }
return true
}

// 外部只读的描述
public var description: String {
switch self {
case .success(let value):
return “Success: \(value)”
case .failure(let error):
return “Failure: \(error)”
}
}
}

// 使用示例
let result1 = Result.success(42)
print(result1.value) // Optional(42)
print(result1.isSuccess) // true
print(result1.description) // “Success: 42”

let result2 = Result.failure(NSError(domain: “com.example”, code: 500, userInfo: nil))
print(result2.error) // Optional(Error Domain=com.example Code=500 …)
print(result2.isSuccess) // false
6. 最佳实践和高级技巧
6.1 使用私有存储属性配合公共计算属性
public class DataManager {
// 私有存储属性
private var _data: [String] = []
private var _isDirty: Bool = false

// 公共只读接口
public var count: Int {
return _data.count
}

public var isEmpty: Bool {
return _data.isEmpty
}

public var firstItem: String? {
return _data.first
}

public var lastItem: String? {
return _data.last
}

public var allItems: [String] {
return _data // 返回副本,防止外部修改
}

// 内部可写方法
internal func addItem(_ item: String) {
_data.append(item)
_isDirty = true
}

internal func removeItem(at index: Int) {
guard index < _data.count else { return } _data.remove(at: index) _isDirty = true } internal func clear() { _data.removeAll() _isDirty = false } } 6.2 使用协议定义只读接口 // 定义只读协议 public protocol ReadOnlyUser { var name: String { get } var email: String { get } var registrationDate: Date { get } var isActive: Bool { get } } // 实现类 public class UserAccount: ReadOnlyUser { public private(set) var name: String public private(set) var email: String public private(set) var registrationDate: Date public private(set) var isActive: Bool private var _loginCount: Int = 0 public init(name: String, email: String) { self.name = name self.email = email self.registrationDate = Date() self.isActive = true } // 内部方法 internal func deactivate() { self.isActive = false } internal func incrementLoginCount() { _loginCount += 1 } // 只读的计算属性 public var loginCount: Int { return _loginCount } } // 使用协议作为参数类型 func displayUser(_ user: ReadOnlyUser) { print("Name: \(user.name)") print("Email: \(user.email)") print("Active: \(user.isActive)") } // 使用示例 let user = UserAccount(name: "Alice", email: "alice@example.com") displayUser(user) // ✅ 可以工作 // user.name = "Bob" // ❌ 编译错误 6.3 线程安全的外部只读属性 import Foundation public class ThreadSafeCounter { private var _count: Int = 0 private let lock = NSLock() // 外部只读的当前值 public var count: Int { lock.lock() defer { lock.unlock() } return _count } // 内部可写的方法 internal func increment() { lock.lock() defer { lock.unlock() } _count += 1 } internal func reset() { lock.lock() defer { lock.unlock() } _count = 0 } } // 使用示例 let counter = ThreadSafeCounter() print(counter.count) // 0 // 在多线程环境中安全地递增 DispatchQueue.concurrentPerform(iterations: 100) { _ in counter.increment() } print(counter.count) // 100 // counter.count = 50 // ❌ 编译错误 6.4 使用属性观察器增强封装 public class TemperatureMonitor { private var _celsius: Double = 0.0 // 外部只读的摄氏度 public private(set) var celsius: Double = 0.0 { didSet { // 内部可以添加副作用 if celsius > 100 {
print(“Warning: High temperature detected!”)
}
}
}

// 外部只读的华氏度
public var fahrenheit: Double {
return celsius * 9/5 + 32
}

// 外部只读的开尔文
public var kelvin: Double {
return celsius + 273.15
}

public init(celsius: Double) {
self.celsius = celsius
}

// 内部更新方法
internal func updateTemperature(newCelsius: Double) {
self.celsius = newCelsius
}
}
7. 总结和选择指南
7.1 方法对比表
方法
适用场景
优点
缺点
private(set)
简单存储属性
简洁、高效、类型安全
无法添加复杂逻辑
计算属性
需要动态计算或验证
灵活、可添加逻辑
性能稍低(每次访问都计算)
结构体+mutating
值类型封装
线程安全、语义清晰
需要复制语义
协议+实现
多态需求
接口清晰、易于测试
需要额外协议定义
7.2 选择建议
优先使用
private(set)
:对于简单的存储属性,这是最直接和高效的方式
需要计算逻辑时使用计算属性
:当属性值需要动态计算、验证或格式化时
值类型考虑结构体
:对于简单的数据模型,结构体比类更合适
复杂场景使用协议
:当需要多态或接口隔离时
线程安全考虑锁
:在多线程环境中,确保属性访问的线程安全
7.3 最终建议
在 Swift 中实现外部只读属性时,始终遵循以下原则:
最小权限原则
:只暴露必要的接口
封装内部状态
:使用
private
或
fileprivate
保护内部实现
提供清晰的公共接口
:使用计算属性提供易用的只读访问
考虑性能影响
:对于频繁访问的属性,避免过度使用计算属性
保持一致性
:在整个项目中使用统一的模式
通过合理运用这些技术,你可以创建出既安全又易用的 Swift API,为其他开发者提供清晰的接口边界,同时保护内部状态不被意外修改。