好的,我们来详细了解一下 Swift 中的 fileprivate
关键字,以及它和 private
关键字之间的区别。
这两个关键字都属于 Swift 的访问控制(Access Control)体系,用于限制代码中某些部分的访问权限,从而帮助你隐藏实现细节,构建清晰的模块接口。
fileprivate
关键字
#
- 定义:
fileprivate
限制了实体(如属性、方法、类型等)的访问权限,使其只能在其定义的源文件 (source file) 内部被访问。 - 作用域: 整个
.swift
文件。这意味着,在同一个源文件中定义的任何类、结构体、枚举、函数或扩展,都可以访问被fileprivate
修饰的成员或类型。 - 使用场景:
- 当你需要在同一个源文件中的多个类型或全局函数之间共享某些实现细节,但又不希望这些细节暴露给文件外部时,
fileprivate
就非常有用。 - 例如,一个文件内定义了多个协同工作的类或结构体,它们之间可能需要共享一些辅助函数或属性,这些辅助内容对文件外的代码没有意义。
- 当你需要在同一个源文件中的多个类型或全局函数之间共享某些实现细节,但又不希望这些细节暴露给文件外部时,
示例:
// MyFile.swift
// 这个类在 MyFile.swift 中定义
class MyClass {
fileprivate var secretCode = "12345-file" // fileprivate 属性
fileprivate func revealSecretCode() { // fileprivate 方法
print("Fileprivate secret code: \(secretCode)")
}
func performAction() {
print("MyClass performing action.")
helperFunctionInSameFile() // 可以调用同一文件中的 fileprivate 全局函数
let another = AnotherClassInSameFile()
another.accessFileprivateFromMyClass() // 另一个类可以访问 MyClass 的 fileprivate 成员
}
}
// 同一个文件中的另一个类
class AnotherClassInSameFile {
func doSomething() {
let instance = MyClass()
// 可以访问 MyClass 的 fileprivate 成员,因为在同一个文件中
instance.secretCode = "67890-file"
instance.revealSecretCode()
print("AnotherClassInSameFile accessed MyClass's secret code: \(instance.secretCode)")
}
func accessFileprivateFromMyClass() {
let myClassInstance = MyClass()
// 虽然不推荐直接从另一个类访问,但技术上是允许的,因为是 fileprivate
print("Accessing MyClass secret from AnotherClass: \(myClassInstance.secretCode)")
}
}
// 同一个文件中的全局函数
fileprivate func helperFunctionInSameFile() {
print("This is a fileprivate helper function in MyFile.swift")
let instance = MyClass()
instance.revealSecretCode() // 可以访问 MyClass 的 fileprivate 方法
}
// 同一个文件中对 MyClass 的扩展
extension MyClass {
func anotherFileprivateAccess() {
// 可以访问自身的 fileprivate 成员
print("Accessing from extension in same file: \(self.secretCode)")
self.revealSecretCode()
}
}
// 如果在另一个文件 (e.g., OtherFile.swift) 中尝试访问:
// let myInstance = MyClass()
// myInstance.secretCode // 错误: 'secretCode' is inaccessible due to 'fileprivate' protection level
// myInstance.revealSecretCode() // 错误: 'revealSecretCode' is inaccessible due to 'fileprivate' protection level
// helperFunctionInSameFile() // 错误: 'helperFunctionInSameFile' is inaccessible due to 'fileprivate' protection level
private
关键字
#
- 定义:
private
限制了实体的访问权限,使其只能在其定义的封闭声明 (enclosing declaration) 及其在同一个源文件中的扩展 (extensions) 内被访问。- “封闭声明” 指的是定义该实体的直接代码块,例如一个类、结构体、枚举或一个特定的扩展。
- 作用域: 严格限定在定义的那个类型或扩展内部 (以及该类型在同一文件内的其他扩展)。它是最严格的访问级别。
- 使用场景:
- 当你希望某个属性或方法仅仅是其所属类型的一个内部实现细节,不应该被该类型之外的任何代码(包括同一文件中的其他类型)访问时,使用
private
。 - 这是隐藏实现细节、封装行为的首选。
- 当你希望某个属性或方法仅仅是其所属类型的一个内部实现细节,不应该被该类型之外的任何代码(包括同一文件中的其他类型)访问时,使用
示例:
// MyOtherFile.swift
class SecureData {
private var highlySensitiveKey = "abc-private" // private 属性
private var accessCount = 0
private func incrementAccessCount() { // private 方法
accessCount += 1
print("Private access count incremented to: \(accessCount)")
}
func getData() -> String {
incrementAccessCount() // 可以在类内部访问 private 成员
return "Data secured with key: \(highlySensitiveKey)"
}
func tryAccessingFromWithinClass() {
print(self.highlySensitiveKey) // OK
self.incrementAccessCount() // OK
}
}
// 在同一个文件中对 SecureData 的扩展
extension SecureData {
func updateKeyFromExtension(newKey: String) {
// 可以访问原始声明中的 private 成员,因为扩展在同一个文件
self.highlySensitiveKey = newKey
self.incrementAccessCount() // 也可以访问 private 方法
print("Key updated via extension in same file. New key: \(self.highlySensitiveKey)")
}
}
class AnotherTypeInSameFile {
func attemptAccess() {
let data = SecureData()
// data.highlySensitiveKey // 错误: 'highlySensitiveKey' is inaccessible due to 'private' protection level
// data.incrementAccessCount() // 错误: 'incrementAccessCount' is inaccessible due to 'private' protection level
print(data.getData()) // 可以调用 public/internal 方法
}
}
// 如果在另一个文件 (e.g., YetAnotherFile.swift) 中尝试访问:
// let secureData = SecureData()
// secureData.highlySensitiveKey // 错误: 'highlySensitiveKey' is inaccessible due to 'private' protection level
fileprivate
vs. private
的区别
#
特性 | private | fileprivate |
主要作用域 | 封闭声明 (类、结构体、枚举、扩展) | 整个源文件 (.swift 文件) |
谁可以访问 | 只能由定义它的那个类型,以及该类型在同一个源文件中的扩展访问。 | 同一个源文件中的任何代码(包括其他类型、全局函数、同一类型的扩展等)都可以访问。 |
严格程度 | 更严格 | 相对宽松一些(在文件级别) |
典型用例 | 隐藏类型的内部实现细节,确保只有类型自身能操作这些细节。是封装的首选和最常用方式。 | 当多个类型或函数在同一个文件中紧密协作,需要共享某些实现细节,但这些细节不应对外暴露时使用。 |
关键区别点总结:
作用范围的粒度:
private
: 作用于声明级别。一个private
成员只能被其直接所属的类/结构体/枚举及其在同一文件内的扩展访问。同一文件中的 其他独立类型 不能访问。fileprivate
: 作用于文件级别。一个fileprivate
成员可以被定义在同一源文件中的 任何 代码访问,包括其他类、结构体、全局函数等。
对同一文件中其他类型的影响:
- 如果类 A 有一个
private
成员x
,那么在同一个文件中定义的类 B 不能访问A.x
。 - 如果类 A 有一个
fileprivate
成员y
,那么在同一个文件中定义的类 B 可以 访问A.y
。
- 如果类 A 有一个
Swift 演进中的变化:
值得注意的是,private
的行为在 Swift 3 中有所调整。在 Swift 2 及更早版本中,private
的行为更接近现在的 fileprivate
(即文件内可见)。Swift 3 将 private
的范围缩小到当前的“封闭声明及其在同文件内的扩展”,并引入了 fileprivate
来覆盖之前的“文件内可见”语义。
何时选择哪个?
- 默认使用
private
: 当你希望一个属性或方法只被它所属的类型和该类型在同一文件内的扩展使用时,private
是最佳选择。这是实现良好封装的常用方式。 - 谨慎使用
fileprivate
: 只有当你确实需要在同一个源文件内的多个不同类型或全局函数之间共享某些实现细节,并且这些细节不应该暴露给文件外部时,才考虑使用fileprivate
。过度使用fileprivate
可能会降低代码的模块化程度,因为它允许文件内的不同部分过于紧密地耦合。通常,如果多个类型需要共享功能,更好的方式可能是将共享功能提取到一个新的类型中,并通过明确的接口(可能是internal
或public
)进行交互,或者将这些紧密协作的类型组织得更合理。
总的来说,Swift 鼓励使用最严格的访问级别来保护代码的实现细节。因此,优先考虑 private
,只有在确实需要在文件级别共享时才退一步使用 fileprivate
。
private(set) #
在 Swift 中,private(set)
是一种访问控制修饰符的组合,用于限制属性的写(set)权限,同时保留读(get)权限的开放性。具体到你的代码 private(set) var cars: [Product]
,其含义和用途如下:
1. 写操作权限限制 #
- 内部可修改:
private(set)
表示cars
属性的 设置(setter) 仅在声明它的类型(如类、结构体、枚举)内部可用。例如,在该类型的初始化方法、方法或计算属性中可以修改cars
的值。 - 外部只读:在类型外部(如其他模块或代码中),只能通过
cars
的 获取(getter) 访问其值,但无法直接修改它。
示例: #
class ProductManager {
private(set) var cars: [Product] = []
func addCar(_ product: Product) {
cars.append(product) // ✅ 内部可修改
}
}
let manager = ProductManager()
manager.addCar(Product(name: "Tesla"))
print(manager.cars) // ✅ 外部可读取
manager.cars.removeAll() // ❌ 编译错误:无法从外部修改
2. 访问级别的默认行为 #
- 若未显式指定读权限,
private(set)
会默认将读权限设为 internal
(模块内可见)。例如,上述代码中cars
的读权限为internal
,其他模块无法访问它。 - 若需要更严格的读权限,可显式声明为
public private(set)
或private private(set)
。
3. 适用场景 #
- 封装数据:确保关键数据(如缓存、状态变量)只能在类型内部修改,防止外部误操作。
- 协议实现:在协议中声明属性时,可通过
private(set)
要求遵循协议的类型提供内部写权限,同时对外暴露只读接口。 - 线程安全:结合锁或其他同步机制,可在内部安全地修改属性,避免并发问题。
4. 与其他修饰符的对比 #
-
private
:完全私有,内外均不可访问(除非嵌套作用域内)。 fileprivate
:文件内可见。-
internal
(默认):模块内可见。 public
:跨模块可见,但写权限仍受private(set)
限制。
5. 注意事项 #
- 值类型(如结构体):若在结构体内部修改
private(set)
属性,需使用mutating
关键字标记方法。 - 继承:子类无法直接修改父类的
private(set)
属性,除非父类提供受保护的方法或属性。
总结 #
private(set) var cars: [Product]
表示:
- 内部:该属性可被读取和修改。
- 外部:仅允许读取,任何直接修改(如赋值、增删元素)会触发编译错误。
这是 Swift 实现数据封装和访问控制的常用方式,既能保护数据完整性,又能提供必要的接口灵活性。