100 Days of Swift 学习笔记:Day11 结构体进阶!访问控制与静态属性,让代码更安全、更灵活
Day11 聚焦结构体的 “进阶特性”——访问控制让我们能隐藏内部数据、避免非法修改(比如银行账户的余额不能直接篡改),静态属性 / 方法则让结构体本身拥有 “共享数据”(比如应用的版本号、学校的总学生数)。这两个特性是从 “写能运行的代码” 到 “写安全、可维护的代码” 的关键,尤其在团队开发或复杂项目中必不可少。整理了实操场景、避坑点和核心用法,新手朋友跟着练能快速掌握 “代码封装” 的进阶思路~
目录
一、访问控制:控制结构体的 “内外可见性”
访问控制的核心是 “隐藏内部实现,只暴露必要的接口”,避免外部代码绕过逻辑直接修改数据(比如银行账户的 funds 不能直接改,必须通过 deposit/withdraw 方法)。
1. 为什么需要访问控制?
先看一个 “不安全” 的例子:
struct BankAccount {
var funds = 0 // 外部可直接访问和修改
mutating func deposit(amount: Int) {
funds += amount // 正常存款逻辑
}
mutating func withdraw(amount: Int) -> Bool {
if funds >= amount {
funds -= amount // 正常取款逻辑(判断余额是否足够)
return true
} else {
return false
}
}
}
// 问题:外部代码可以直接修改 funds,绕过取款逻辑
var sjxAccount = BankAccount()
sjxAccount.deposit(amount: 1000) // 正常存款,funds=1000
sjxAccount.funds = -500 // 直接篡改余额为负数,没有触发任何校验
print(sjxAccount.funds) // 输出 -500(非法状态)
- 风险:外部代码直接修改内部属性,会破坏结构体的逻辑一致性(比如余额为负、库存为负)。访问控制就是为了避免这种情况。
2. 常用访问控制修饰符
Swift 提供了 4 种核心访问控制修饰符,新手重点掌握前 3 种即可:
| 修饰符 | 作用(控制 “谁能访问”) | 适用场景 |
|---|---|---|
private |
仅允许结构体内部访问(外部完全不可见) | 隐藏核心数据(如银行账户的 funds 底层存储) |
private(set) |
允许外部读取,但仅允许结构体内部修改(只读不写) | 暴露状态但禁止外部修改(如余额可查不可改) |
public |
允许任何地方访问(完全公开) | 暴露对外接口(如 deposit/withdraw 方法) |
fileprivate |
允许当前文件内访问(跨文件不可见) | 同文件内共享数据(较少用,新手可暂忽略) |
3. 实战:给银行账户加访问控制
用 private(set) 修饰 funds,实现 “外部可查、内部可改”,确保修改必须通过方法:
struct SafeBankAccount {
// private(set):外部能读,内部能改
private(set) var funds = 0
// 公开方法:外部通过方法存款
mutating func deposit(amount: Int) {
// 可额外加校验(如存款金额不能为负)
guard amount > 0 else {
print("存款金额必须为正数!")
return
}
funds += amount
}
// 公开方法:外部通过方法取款
mutating func withdraw(amount: Int) -> Bool {
guard amount > 0 else {
print("取款金额必须为正数!")
return false
}
if funds >= amount {
funds -= amount
return true
} else {
print("余额不足!")
return false
}
}
}
// 测试:外部只能通过方法修改,无法直接篡改
var safeAccount = SafeBankAccount()
safeAccount.deposit(amount: 1000) // 正常存款,funds=1000
print(safeAccount.funds) // 外部可读取:1000(没问题)
// safeAccount.funds = -500 // 编译报错!外部不能修改(private(set) 限制)
let withdrawSuccess = safeAccount.withdraw(amount: 1500) // 余额不足,返回 false
print(safeAccount.funds) // 仍为 1000(未被非法修改)
- 关键效果:外部代码只能通过
deposit/withdraw方法修改funds,而方法内的校验逻辑(如金额为正、余额足够)能确保数据合法。
4. 注意事项:private 属性与自定义初始化器
如果结构体有 private 或 private(set) 的属性,Swift 会取消自动生成的默认初始化器,必须手动写自定义初始化器(确保所有属性初始化):
struct User {
let name: String
private(set) var age: Int // 有 private(set) 属性
// 必须手动写初始化器(默认初始化器被取消)
init(name: String, age: Int) {
self.name = name
self.age = age
}
// 内部方法修改 age
mutating func birthday() {
age += 1
}
}
let user = User(name: "SunJiaxing", age: 25)
print(user.age) // 外部可读取:25
// user.age = 26 // 编译报错!外部不能修改
二、静态属性与方法:让结构体 “自带共享数据”
默认情况下,结构体的属性和方法属于 “实例”(每个实例有自己的副本,比如每个 BankAccount 有自己的 funds)。而静态属性 / 方法属于 “结构体本身”,所有实例共享一份数据(比如学校的总学生数,不管创建多少 School 实例,总人数只有一个)。
1. 定义静态属性与方法:用 static 关键字
struct School {
// 静态属性:属于 School 结构体本身,所有实例共享
static var studentCount = 0
// 静态方法:属于 School 结构体本身,操作静态属性
static func addStudent(name: String) {
print("\(name) 加入学校!")
studentCount += 1 // 直接修改静态属性,无需 mutating
}
}
// 调用:直接用“结构体名.属性/方法”,无需创建实例
School.addStudent(name: "SunJiaxing")
School.addStudent(name: "LiSi")
print("学校总人数:\(School.studentCount)") // 输出 2
- 关键区别:
- 普通属性 / 方法:
let account = BankAccount()→account.deposit()(需先创建实例); - 静态属性 / 方法:
School.addStudent()(直接用结构体名调用,无需实例)。
- 普通属性 / 方法:
- 为什么不用
mutating:静态属性属于结构体本身,不是某个实例,因此修改静态属性的方法不需要mutating(mutating只用于修改实例的属性)。
2. 静态 vs 非静态:访问规则
静态属性 / 方法和非静态属性 / 方法的访问有严格规则,核心是 “静态不能访问非静态,非静态可以访问静态”:
| 访问方向 | 是否允许?原因 |
|---|---|
| 静态代码 → 非静态代码 | ❌ 不允许(非静态属于实例,静态代码没有实例) |
| 非静态代码 → 静态代码 | ✅ 允许(静态属于结构体本身,随时可访问) |
示例:规则验证
struct Example {
// 非静态属性(属于实例)
var instanceProperty = 10
// 静态属性(属于结构体本身)
static var staticProperty = 100
// 非静态方法:可以访问静态属性(用“结构体名.属性”或“Self.属性”)
func instanceMethod() {
print("静态属性值:\(Example.staticProperty)") // ✅ 允许
print("静态属性值:\(Self.staticProperty)") // ✅ 允许(Self 指当前结构体)
print("非静态属性值:\(instanceProperty)") // ✅ 允许(访问自身实例的属性)
}
// 静态方法:不能访问非静态属性(没有实例,不知道访问哪个 instanceProperty)
static func staticMethod() {
// print("非静态属性值:\(instanceProperty)") // ❌ 编译报错!
print("静态属性值:\(staticProperty)") // ✅ 允许(访问自身静态属性)
}
}
// 测试非静态方法访问静态属性
let example = Example()
example.instanceMethod() // 输出:静态属性值:100;非静态属性值:10
// 测试静态方法
Example.staticMethod() // 输出:静态属性值:100
- 小技巧:非静态代码访问静态属性时,用
Self.静态属性比结构体名.静态属性更灵活(如果结构体名修改,Self无需同步改)。
3. 静态属性的实用场景
静态属性 / 方法在实际开发中非常常用,新手重点掌握两个场景:
场景 1:存储全局共享数据
比如应用的版本号、配置文件名、基础 URL 等,所有地方都需要访问,用静态属性统一管理:
struct AppConfig {
// 应用版本号(所有地方都能访问,且不会变)
static let appVersion = "1.0.0"
// 数据存储文件名(统一管理,避免硬编码)
static let saveFileName = "user_data.json"
// 服务器基础 URL(修改时只需改一处)
static let baseURL = "https://api.example.com"
}
// 其他地方使用(无需创建实例)
print("当前版本:\(AppConfig.appVersion)") // 输出 1.0.0
let requestURL = "\(AppConfig.baseURL)/user" // 拼接 URL
场景 2:创建示例数据(SwiftUI 预览常用)
开发 UI 时,需要示例数据来预览效果(比如展示一个示例用户、示例订单),用静态属性快速生成:
struct Employee {
let username: String
let department: String
let salary: Double
// 静态示例:UI 预览时直接用
static let example = Employee(
username: "sunjx",
department: "iOS 开发",
salary: 20000.0
)
}
// SwiftUI 预览时使用(无需重复创建实例)
// struct EmployeeView_Previews: PreviewProvider {
// static var previews: some View {
// EmployeeView(employee: Employee.example) // 直接用示例数据
// }
// }
三、Day11 总结:访问控制与静态属性的核心价值
| 特性 | 核心作用 | 关键语法 / 注意事项 |
|---|---|---|
访问控制(private/private(set)) |
隐藏内部数据,避免非法修改,保证逻辑一致性 | private:仅内部可见;private(set):外部只读、内部可改;有私有属性需手动写初始化器 |
静态属性 / 方法(static) |
让结构体拥有共享数据,无需创建实例 | 用 “结构体名。属性 / 方法” 调用;静态不能访问非静态,非静态可以访问静态;Self 代表当前结构体 |
明日计划
Day12 要学 Swift 的 “类(Class)”—— 与结构体类似,但支持继承(子类可以复用父类的代码),这是面向对象编程的核心。类和结构体在 Swift 中分工不同:结构体适合存储简单数据(如 Album、Employee),类适合复杂逻辑和继承场景(如 User、Order)。有一起打卡的朋友吗?评论区可以分享你用 “访问控制 + 静态属性” 设计的结构体(比如 ShoppingCart 购物车,用 private(set) 控制商品列表,用静态属性存储默认折扣)~
更多推荐

所有评论(0)