ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Swift 访问控制:从入门到实战

Swift 访问控制:从入门到实战 1. 引言访问控制是 Swift 语言中一项非常重要的特性它决定了代码中各个实体如类、结构体、属性、方法等可以被哪些作用域访问。合理使用访问控制不仅能让代码的接口更加清晰还能有效隐藏内部实现细节降低模块之间的耦合度。本文将从基础概念讲起结合丰富的代码实例带你全面掌握 Swift 访问控制的用法。2. 访问控制的基本概念在 Swift 中访问控制通过关键字来限制代码实体的可见范围。Swift 提供了五个级别的访问控制从最开放到最严格依次是open、public、internal、fileprivate和private。访问级别可见范围适用场景open模块内和模块外均可访问且可被外部继承和重写框架的公共 API允许外部子类化public模块内和模块外均可访问但外部不可继承和重写框架的公共 API但不希望被外部继承internal仅限当前模块内访问默认级别App 内部或框架内部使用的代码fileprivate仅限当前源文件内访问同一文件内多个类型共享的辅助代码private仅限当前声明及其同一文件内的扩展中访问隐藏类型内部的实现细节如果不显式声明访问级别Swift 默认使用internal。这意味着在大多数 App 开发场景中你写的代码默认可以在当前模块内任意位置访问。3. 五个访问级别的代码示例下面通过一个完整的示例来演示五个访问级别的具体用法。// 定义一个公开的类允许外部继承 open class Animal { // 公开属性外部可读写 public var name: String // 内部属性仅当前模块可访问 internal var age: Int // 文件私有属性仅当前文件可访问 fileprivate var secret: String // 私有属性仅当前类型及其扩展可访问 private var id: String public init(name: String, age: Int) { self.name name self.age age self.secret 这是一个秘密 self.id 唯一标识 } // 公开方法外部可调用 public func speak() { print(\(name) 在叫) } // 私有方法仅内部使用 private func generateID() { print(生成 ID: \(id)) } } // 在同一个文件中可以访问 fileprivate 成员 let animal Animal(name: 旺财, age: 3) print(animal.secret) // 可以访问因为 secret 是 fileprivate // 在同一个模块中可以访问 internal 成员 print(animal.age) // 可以访问因为 age 是 internal // 无法访问 private 成员 // print(animal.id) // 编译错误id is inaccessible due to private protection level上面的代码展示了不同访问级别在同一个文件中的表现。需要注意的是private成员只能在声明它的类型内部访问而fileprivate成员可以在同一个源文件中的任何位置访问。4. open 与 public 的区别open和public都允许外部模块访问但有一个关键区别open允许外部模块继承和重写而public不允许。下面通过一个跨模块的示例来说明。// 假设这是框架模块中的代码 open class OpenBaseClass { open func openMethod() { print(open 方法) } public func publicMethod() { print(public 方法) } } public class PublicBaseClass { public func publicMethod() { print(public 方法) } } // 假设这是使用框架的 App 模块中的代码 class MySubClass: OpenBaseClass { // 可以重写 open 方法 override func openMethod() { print(重写了 open 方法) } // 不能重写 public 方法 // override func publicMethod() { } // 编译错误 } // 不能继承 public 类 // class MyOtherClass: PublicBaseClass { } // 编译错误在设计框架时如果你希望外部开发者能够继承某个类并重写其方法就应该使用open如果只是希望外部使用而不允许继承则使用public。5. private 与 fileprivate 的区别private和fileprivate都用于限制访问范围但它们的粒度不同。private限制在声明所在的类型及其同文件扩展中而fileprivate限制在声明所在的整个源文件中。下面通过示例来区分。struct Person { // private 属性仅 Person 及其扩展可访问 private var privateName: String 私有名字 // fileprivate 属性整个文件可访问 fileprivate var fileprivateName: String 文件私有名字 func showPrivate() { print(privateName) // 可以访问 } } // 在同一个文件中定义扩展 extension Person { func showFromExtension() { print(privateName) // 可以访问因为扩展与声明在同一文件 } } // 在同一个文件中定义另一个类型 struct Helper { func showNames() { let person Person() // print(person.privateName) // 编译错误无法访问 private print(person.fileprivateName) // 可以访问因为 fileprivate 在整个文件内可见 } }从上面的例子可以看出fileprivate的访问范围比private更大。在 Swift 4 之前private的访问范围与fileprivate相同但在 Swift 4 之后private被限制在了类型及其扩展内部。6. 访问控制与属性、方法、初始化器访问控制不仅适用于类型本身还适用于类型的属性、方法和初始化器。下面通过一个实际例子来演示。public class BankAccount { // 私有属性外部无法直接访问 private var balance: Double 0 // 公开只读属性外部可以读取但无法修改 public private(set) var accountNumber: String // 内部属性仅模块内可访问 internal var bankName: String public init(accountNumber: String, bankName: String) { self.accountNumber accountNumber self.bankName bankName } // 公开方法外部可调用 public func deposit(amount: Double) { balance amount print(存入 \(amount)当前余额 \(balance)) } // 公开方法外部可调用 public func withdraw(amount: Double) { if amount balance { balance - amount print(取出 \(amount)当前余额 \(balance)) } else { print(余额不足) } } // 私有方法仅内部使用 private func calculateInterest() - Double { return balance * 0.03 } // 公开方法内部调用私有方法 public func showInterest() { let interest calculateInterest() print(当前利息为 \(interest)) } } let account BankAccount(accountNumber: 123456, bankName: 示例银行) account.deposit(amount: 1000) account.withdraw(amount: 200) account.showInterest() // 可以读取 accountNumber print(account.accountNumber) // 无法修改 accountNumber // account.accountNumber 654321 // 编译错误 // 无法直接访问 balance // print(account.balance) // 编译错误在这个例子中balance被设置为private外部无法直接访问只能通过公开方法进行存款和取款操作。而accountNumber使用了public private(set)表示外部可以读取但不能修改这在很多场景下非常实用。7. 访问控制与扩展在 Swift 中扩展可以访问原类型中private和fileprivate的成员前提是扩展与原类型在同一个文件中。这一特性让我们可以在扩展中补充功能同时保持内部实现的隐藏。struct User { private var password: String 123456 func validatePassword(_ input: String) - Bool { return input password } } // 在同一个文件中扩展 User extension User { // 可以访问 private 属性 func resetPassword() { // 这里可以修改 password // 注意由于 password 是 var且扩展在同一文件可以访问 // 但实际修改需要通过方法实现 print(密码已重置) } // 添加新的计算属性 var passwordLength: Int { return password.count } } let user User() print(user.passwordLength) // 输出6 print(user.validatePassword(123456)) // 输出true需要注意的是如果扩展与原类型不在同一个文件中那么扩展只能访问internal及以上级别的成员无法访问private和fileprivate成员。8. 访问控制与协议协议中的访问控制与类型略有不同。协议本身可以声明访问级别协议中的每个要求也会继承协议的访问级别。下面通过示例来说明。// 定义一个公开协议 public protocol Greetable { func greet() } // 定义一个内部协议 internal protocol InternalProtocol { func doSomething() } // 公开类遵循公开协议 public class EnglishSpeaker: Greetable { public func greet() { print(Hello!) } } // 内部类遵循内部协议 class InternalWorker: InternalProtocol { func doSomething() { print(Doing something...) } } // 注意协议要求的访问级别不能高于协议本身 // 如果协议是 internal那么它的要求也不能是 public在实现协议时协议要求的实现方法的访问级别必须与协议要求的访问级别一致或更高。例如如果协议要求是public那么实现方法也必须是public。9. 访问控制与泛型泛型类型和泛型函数的访问控制由其自身及其类型参数共同决定。下面通过一个泛型示例来演示。// 公开的泛型结构体 public struct StackElement { // 私有存储属性 private var items: [Element] [] // 公开方法 public mutating func push(_ item: Element) { items.append(item) } public mutating func pop() - Element? { return items.isEmpty ? nil : items.removeLast() } public var count: Int { return items.count } } // 使用泛型类型 var intStack StackInt() intStack.push(1) intStack.push(2) intStack.push(3) print(intStack.count) // 输出3 print(intStack.pop() ?? 0) // 输出3 var stringStack StackString() stringStack.push(Swift) stringStack.push(访问控制) print(stringStack.pop() ?? ) // 输出访问控制泛型类型的访问级别不能高于其类型参数的访问级别。例如一个public的泛型类型其类型参数也必须是public或open级别。10. 访问控制与嵌套类型嵌套类型的访问级别不能高于其外部类型的访问级别。下面通过示例来演示嵌套类型的访问控制。public struct OuterStruct { // 公开嵌套类型 public struct PublicNested { public var value: Int 0 } // 私有嵌套类型 private struct PrivateNested { var secret: String 秘密 } // 内部嵌套类型 struct InternalNested { var name: String 内部 } // 公开方法返回公开嵌套类型 public func makePublicNested() - PublicNested { return PublicNested() } // 内部方法使用内部嵌套类型 func makeInternalNested() - InternalNested { return InternalNested() } // 私有方法使用私有嵌套类型 private func makePrivateNested() - PrivateNested { return PrivateNested() } } let outer OuterStruct() let publicNested outer.makePublicNested() print(publicNested.value) // 无法访问私有嵌套类型 // let privateNested outer.makePrivateNested() // 编译错误嵌套类型的访问级别不能高于其外部类型。例如外部类型是internal那么嵌套类型就不能声明为public。11. 访问控制与元组元组的访问级别由其成员中访问级别最低的那个决定。下面通过示例来说明。// 定义一个包含不同访问级别成员的元组 // 元组的访问级别由最低访问级别的成员决定 private struct PrivateData { var value: Int 0 } // 这个元组包含 private 成员因此整个元组是 private private let mixedTuple: (name: String, data: PrivateData) (name: 示例, data: PrivateData()) // 在同一个文件中可以访问 print(mixedTuple.name) // 如果元组中包含 private 成员那么元组本身也只能是 private 或 fileprivate在实际开发中元组的访问级别通常由其成员自动推断我们很少需要显式声明元组的访问级别。12. 访问控制的最佳实践合理使用访问控制可以让代码更加健壮和易维护。以下是一些最佳实践建议默认使用 internal在 App 开发中大多数代码不需要跨模块访问使用默认的internal即可。对外接口使用 public 或 open如果开发框架或 SDK对外暴露的接口应使用public 或 open并谨慎设计。隐藏实现细节尽量将内部实现细节标记为private或fileprivate减少外部依赖。使用 private(set)对于需要外部读取但不需要修改的属性使用private(set)可以很好地控制可变性。避免过度暴露不要为了图方便把所有成员都设为public这会让接口变得臃肿且难以维护。// 一个遵循最佳实践的示例 public struct Product { // 对外只读对内可写 public private(set) var name: String public private(set) var price: Double // 内部属性模块内可访问 internal var discount: Double 0 // 私有属性隐藏内部状态 private var stock: Int 0 public init(name: String, price: Double) { self.name name self.price price } // 公开方法 public func displayInfo() { print(商品\(name)价格\(price) 元) } // 内部方法 internal func applyDiscount(_ rate: Double) { discount rate } // 私有方法 private func checkStock() - Bool { return stock 0 } } let product Product(name: iPhone, price: 5999) product.displayInfo() print(product.name) // 可以读取 // product.name iPad // 编译错误无法修改13. 总结本文详细介绍了 Swift 访问控制的五个级别open、public、internal、fileprivate和private并通过丰富的代码示例展示了它们在不同场景下的用法。掌握访问控制能够帮助你设计出接口清晰、内部实现隐藏的高质量代码。在实际开发中建议根据代码的使用范围合理选择访问级别遵循「最小暴露」原则让代码更加健壮和易维护。
返回列表