Swift 是一种面向对象(OOP,Object-Oriented Programming)语言,支持面向对象设计中的主要特性,比如 类、对象、继承、多态 和 封装。同时,Swift 提供了许多其他现代化的功能,使面向对象编程更加强大和灵活。
下面将详细介绍 Swift 中与面向对象设计相关的核心概念,以及如何在 Swift 中设计和使用面向对象的程序。
1. 基础:类与对象(Class 和 Object) #
- 类(Class) 是面向对象设计的核心,用来定义对象的属性和行为。
- 对象(Object) 是类的实例,表示程序运行时具体的实体。
示例代码 #
class Person {
var name: String
var age: Int
// 构造函数,用于初始化类的属性
init(name: String, age: Int) {
self.name = name
self.age = age
}
// 方法,定义对象的行为
func introduce() -> String {
return "我的名字是 \(name),今年 \(age) 岁。"
}
}
// 使用类创建对象
let person1 = Person(name: "Alice", age: 25)
print(person1.introduce()) // 输出:我的名字是 Alice,今年 25 岁。
关键点 #
- 属性和方法:
- 属性用来存储对象的状态,例如
name
和age
。 - 方法定义类的行为,例如
introduce()
。
- 属性用来存储对象的状态,例如
- 构造函数(Initializer):
- 使用
init
函数初始化对象的属性。
- 使用
- 访问对象属性和方法:
- 通过
.
访问属性(如person1.name
)。 - 调用方法(如
person1.introduce()
)。
- 通过
2. 封装(Encapsulation) #
- 封装 是面向对象设计中的一个重要原则,它允许我们控制对象属性和方法的访问级别(如公开、私有)。
- Swift 提供了强大的访问控制修饰符:
private
、fileprivate
、internal
(默认)、public
和open
。
示例代码 #
class BankAccount {
private var balance: Double
init(initialBalance: Double) {
self.balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) -> Bool {
if amount > balance {
print("余额不足")
return false
}
balance -= amount
return true
}
// 提供只读访问余额的接口
func getBalance() -> Double {
return balance
}
}
let account = BankAccount(initialBalance: 1000)
account.deposit(amount: 500)
print("余额: \(account.getBalance())") // 输出:余额: 1500.0
关键点 #
- 访问控制:
private
:限制属性balance
的直接访问,只有类内部可以访问和修改。
- 通过方法提供访问:
- 使用
getBalance()
提供受控的只读访问,而非直接暴露balance
属性。
- 使用
- 保护数据:
- 使用封装技术可以防止数据被无意修改。
3. 继承与重写(Inheritance 和 Overriding) #
- 继承是面向对象中的重要特性,允许一个类继承另一个类的属性和方法。
- 子类可以扩展或重写父类的方法。
示例代码 #
// 父类
class Animal {
var name: String
init(name: String) {
self.name = name
}
func speak() -> String {
return "\(name) 不能发出声音"
}
}
// 子类继承父类
class Dog: Animal {
// 重写父类的方法
override func speak() -> String {
return "\(name) 说:汪汪!"
}
}
let dog = Dog(name: "旺财")
print(dog.speak()) // 输出:旺财 说:汪汪!
关键点 #
- 子类(Subclass):
- 子类通过继承获取父类的属性和方法。
- 重写(Override):
- 使用
override
关键字重写父类中的方法,使子类有不同的行为。
- 使用
- 多态:
- 父类或子类的对象都可以调用同名方法,根据实际类型执行对应的方法。
4. 多态(Polymorphism) #
- 多态允许相同的方法调用在不同的上下文中表现出不同的行为。
- 通过父类引用调用子类实现,是多态的常见用法。
示例代码 #
class Shape {
func area() -> Double {
return 0 // 默认值
}
}
class Circle: Shape {
var radius: Double
init(radius: Double) {
self.radius = radius
}
override func area() -> Double {
return 3.14 * radius * radius // 圆面积公式
}
}
class Rectangle: Shape {
var width: Double
var height: Double
init(width: Double, height: Double) {
self.width = width
self.height = height
}
override func area() -> Double {
return width * height // 矩形面积公式
}
}
// 多态
let shapes: [Shape] = [Circle(radius: 5), Rectangle(width: 4, height: 6)]
for shape in shapes {
print("面积: \(shape.area())")
}
// 输出:
// 面积: 78.5
// 面积: 24.0
关键点 #
- 父类引用调用子类方法:
shapes
是Shape
类型,但可存储Circle
和Rectangle
类型。
- 动态绑定:
- 调用
area()
方法时,实际执行的是具体子类的实现(Circle
或Rectangle
的实现)。
- 调用
5. 抽象与协议(Abstract 和 Protocol) #
- 在 Swift 中没有
abstract
关键字,但可以通过 协议(protocol) 实现类似抽象类的行为。 - 协议定义属性或方法的要求,具体实现由遵守协议的类型去完成。
示例代码:协议 #
protocol Drawable {
func draw() // 约定方法,但不提供具体实现
}
class Circle: Drawable {
func draw() {
print("画一个圆")
}
}
class Rectangle: Drawable {
func draw() {
print("画一个矩形")
}
}
let shapes: [Drawable] = [Circle(), Rectangle()]
for shape in shapes {
shape.draw()
// 输出:
// 画一个圆
// 画一个矩形
}
关键点 #
- 协议定义接口:
Drawable
规定任意遵循本协议的类都要实现draw()
方法。
- 多态结合协议:
- 协议可用于实现多态特性。
6. 组合(Composition) #
- 组合是另一种设计方法,通过把多个类组合起来形成更复杂的功能。
- 对于某些场景,组合比继承更合适,能够减少类之间的强耦合。
示例:组合 #
class Engine {
func start() {
print("发动机启动")
}
}
class Car {
let engine = Engine() // 组合关系
func startCar() {
engine.start()
print("汽车启动")
}
}
let car = Car()
car.startCar()
// 输出:
// 发动机启动
// 汽车启动
7. 静态属性与方法 #
- 静态属性和方法属于类本身,而不是某个具体的实例。可以通过
static
定义。
示例:静态属性和方法 #
class MathUtils {
static func add(a: Int, b: Int) -> Int {
return a + b
}
}
print(MathUtils.add(a: 3, b: 7)) // 输出:10
总结 #
Swift 是一个现代化的面向对象语言,支持面向对象设计的所有核心特性,且结合了协议编程和函数式编程的思想,使得代码可以更加灵活、易于扩展。
特性 | Swift 实现方式 |
---|---|
类和对象 | 使用 class 定义类,通过实例化创建对象 |
封装 | 使用访问控制(private , internal , 等)保护属性 |
继承 | 子类通过继承扩展父类,使用 override 重写父类方法 |
多态 | 父类引用实现动态方法绑定 |
抽象 | 协议(protocol )定义行为,由遵循协议的类型实现 |
组合 | 通过一类包含另一个类的实例实现 |
静态方法与属性 | 使用 static 定义,属于类而非对象 |
Swift 也结合了协议编程(Protocol-Oriented Programming)和泛型编程(Generic Programming)的范式,提供了更多灵活的设计模式,适合处理现代化复杂应用。
访问控制 #
以下是 Swift 的访问控制关键字及其分门别类的详细表格,通过表格形式更清晰地展示了各种 访问级别 和 访问控制修饰符 的特性、使用范围以及适用场景。
1. 基本访问控制级别分类 #
访问控制 | 关键字 | 描述 | 访问范围 | 适用场景 |
---|---|---|---|---|
开放访问 | open | 最开放的访问权限,可以被其他模块中的代码访问,并支持继承和重写。 | 所有模块(且其他模块中可继承该类或方法并进行重写)。 | 框架开发,常用于需要公共扩展、继承的类和方法(如 UI 组件库中可以被自定义的控件)。 |
公共访问 | public | 允许其他模块访问该类、方法或属性,但不支持子类化或重写。 | 所有模块,但不能被继承。 | 用于对外公开的数据接口,例如 SDK 中暴露的 Model 或公共方法。 |
模块内部访问 | internal | 仅当前模块中的类、方法或属性可以被访问(默认访问级别)。 | 当前模块内部可访问,其他模块无法使用。 | 默认场景,适合模块化开发中使用,例如各模块内部的工具类、控制器、模型等。 |
文件内部访问 | fileprivate | 限制代码的访问范围在当前文件中,文件内的所有类、结构体、扩展均可以使用。 | 仅当前文件内部。 | 用于文件内共享逻辑(如多个扩展共享工具),但不希望暴露给其他文件的代码。 |
类内部访问(完全封装) | private | 限制访问代码只能在当前类或结构体中使用,扩展中也不可直接访问。 | 仅当前类型内部。 | 严格隐藏实现细节,适用于需要保证安全性和封装的逻辑(如敏感数据处理或实现细节)。 |
2. 特殊访问修饰符分类(细粒度控制) #
在以上基础访问控制级别之外,Swift 提供了一些修饰符来进一步细化属性或方法的读写权限:
(1) 属性的 set
权限修饰
#
修饰符 | 作用 | 读权限范围 | 写权限范围 | 适用场景 |
---|---|---|---|---|
private(set) | 属性对外只读,但限制写权限只能在 当前类或结构体内。 | 和属性本身的访问权限一致(可 internal 或 public )。 | 仅限当前类或结构体内部修改。 | 适用于内部修改逻辑封装,但允许外界读取,例如计数器、状态管理器等。 |
fileprivate(set) | 属性对外只读,但限制写权限只能在当前文件内使用。 | 和属性本身的访问权限一致。 | 仅限当前文件内修改。 | 适用于文件内共享修改权限的场景,例如文件内的协作代码(多个扩展共享状态修改)。 |
internal(set) | 属性对外只读,但限制写权限只能在当前模块内使用。 | 和属性本身的访问权限一致。 | 仅限当前模块内修改。 | 用于在模块内部有写权限,但对外模块只暴露为只读状态(典型场景:状态属性如 public internal(set) status )。 |
(2) 方法的动态行为权限(继承与重写) #
修饰符 | 作用 | 继承权限 | 重写权限 | 适用场景 |
---|---|---|---|---|
open | 方法或类允许 任何模块继承,且允许子类重写。 | 所有模块均可继承。 | 所有模块均可重写。 | 适用于需要提供高扩展性的类或方法,例如框架中的自定义视图控件、抽象接口等。 |
public | 方法或类可以被外部模块访问,但不能继承和重写。 | 不能被继承(除非提升为 open )。 | 不允许重写。 | 适用于对外完全封闭,但是需要调用的方法,例如静态工具类的公共方法(Logger.shared.log() )。 |
3. 特殊情况下的访问和限制(扩展与协议) #
修饰符 | 作用 | 适用范围 | 适用场景 |
---|---|---|---|
private extension | 扩展中的方法或属性访问限制为私有,仅限当前类。 | 仅限于当前类型中访问。 | 一般用于隐藏实现细节(如某些工具方法不希望被外界直接访问)。 |
fileprivate extension | 扩展中的方法或属性可以在当前文件中的任何类型内访问。 | 当前文件内任何代码均能访问。 | 适用于文件内的共享逻辑,例如各个扩展中共享的功能(如数据格式化、工具方法)。 |
协议访问控制 (protocol ) | 控制协议的实现范围,可以是 public 、internal 或 private 。 | 当前协议、协议扩展或子类化的模块范围。 | 限制协议的访问,比如定义一组实现的行为,但又不希望外部使用这些实现,只允许在特定作用域生效。 |
4. 场景分类与修饰符应用对比 #
(1) 属性与修饰符对比 #
需求 | 适用修饰符 | 示例代码 |
---|---|---|
属性只能在类内部修改,外部只读 | private(set) | private(set) var counter: Int = 0 |
属性只允许当前文件中的某些代码改写 | fileprivate(set) | fileprivate(set) var configuration: Settings |
属性只允许模块内部修改,但其他模块可访问 | internal(set) | internal(set) var isEnabled: Bool = false |
(2) 方法与类的访问控制场景对比 #
需求 | 适用修饰符 | 示例代码 |
---|---|---|
允许方法被其他模块继承和重写 | open | open func renderView() {} |
只允许模块内继承,但不允许外部模块继承 | internal | internal class ImageProcessor {} |
封闭类,只供内部使用,绝不能继承 | private 或 fileprivate | private class SecretManager {} |
想开放读取,但防止随意修改数据状态 | 属性修饰 private(set) | public private(set) var status: Int = 0 |
5. 权限机制的层次关系及总结 #
以下是 Swift 访问权限控制关键字及其作用范围的完整层级关系,从最严格到最宽松:
修饰符 | 描述 | 控件访问范围 |
---|---|---|
private | 访问完全限制为当前类、结构体、枚举或扩展内部。 | 当前作用域 |
fileprivate | 限制访问为当前文件内。 | 当前文件 |
internal (默认) | 限制访问为当前模块内(模块即 App 或 framework)。 | 当前模块 |
public | 可以被外部模块访问,但不能继承或重写。 | 所有模块 |
open | 可以被外部模块访问、继承且重写(最开放)。 | 所有模块 |
总结 #
通过 Swift 的访问控制机制,开发者可以:
- 精准控制代码的访问范围:
- 结合
private(set)
、fileprivate(set)
等实现灵活的逻辑保护。
- 结合
- 提高代码封装性和安全性:
- 使用
private
或fileprivate
保护实现细节,仅暴露必要的接口。
- 使用
- 提升代码可扩展性:
- 使用
open
对外开放继承能力,让框架更具弹性。
- 使用
如果你需要更深入的讲解或案例展示,欢迎继续提问! 😊
private(set)
是什么?
#
在 Swift 中,private(set)
是一种访问控制修饰符,它用于限制写访问的范围,同时对外开放只读访问。
- 读权限: 允许其他模块或类读取该属性。
- 写权限: 仅限声明属性的作用域(通常是当前类型)进行修改。
语法格式 #
private(set) var propertyName: Type
- private(set):表示属性的写访问是
private
的,也就是说,只有当前类型内部可以修改这个属性的值。 - 默认读访问范围:属性的读访问权限与属性自身的声明修饰符一致(默认是
internal
,也可以是public
或其他)。
作用 #
private(set)
的主要作用是控制属性的可读写访问级别:
- 控制属性安全性: 防止属性被类外的代码意外修改。
- 对外公开只读权限: 属性可以被外部读取,但不允许外部代码直接更改它。
- 封装逻辑: 让外部访问者无法直接改变属性值,而是通过暴露的接口(如方法)进行安全的更改。
示例与使用场景 #
1. 只读对外公开,修改限于类内部 #
class Counter {
private(set) var value: Int = 0 // 外部代码只能读取,不能直接修改
func increment() {
value += 1
}
func reset() {
value = 0
}
}
let counter = Counter()
print(counter.value) // ✅ 可以读取值,输出:0
counter.increment()
print(counter.value) // ✅ 读取更新后的值,输出:1
// counter.value = 10 // ❌ 错误:'value' 的 setter 是私有的,无法直接修改
- 解释:
- 属性
value
可以在外部被读取(比如通过print(counter.value)
),但它的值只能通过类内部的方法increment
或reset
修改,防止代码外部直接篡改属性值。 - 如果没有
private(set)
,外部代码可以随意修改value
,这可能导致不符合逻辑的操作。
- 属性
2. 结合访问控制(public/private) #
可以与其他访问控制修饰符配合使用,例如将属性的读权限公开为 public
,但写权限保留为 private
。
class BankAccount {
public private(set) var balance: Double = 0
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) {
if balance >= amount {
balance -= amount
}
}
}
let account = BankAccount()
print(account.balance) // ✅ 可以读取余额,输出:0.0
account.deposit(amount: 100)
print(account.balance) // ✅ 更新后读取余额,输出:100.0
// account.balance = 200 // ❌ 错误:'balance' 的 setter 是私有的,无法直接设置
- 解释:
public private(set)
:- 外部模块可以读取
balance
,但无法直接修改它的值(只能通过 deposit 或 withdraw 方法)。
- 外部模块可以读取
- 通过这种设计,银行账户的余额只能通过安全方法进行修改,确保业务逻辑可靠性。
3. 防止意外修改(适用于数组/集合) #
private(set)
常用于禁止外部代码修改数组或集合的元素,而将增删操作集中在类内部的逻辑中。
class Team {
private(set) var members: [String] = [] // 外部只读,防止直接修改数组
func addMember(_ name: String) {
members.append(name)
}
func removeMember(_ name: String) {
members.removeAll { $0 == name }
}
}
let team = Team()
team.addMember("Alice")
team.addMember("Bob")
print(team.members) // ✅ 可以读取成员列表,输出:["Alice", "Bob"]
// team.members.append("Charlie") // ❌ 错误:'members' 的写权限是私有的,无法直接插入
- 解释:
- 属性
members
对外是只读的,防止外部代码直接修改数组内容。 - 内部通过
addMember
和removeMember
方法集中管理数组的增删逻辑,实现数据更可靠的封装。
- 属性
private(set)
与其他访问控制修饰符的对比
#
示例 #
Swift 中常用的访问控制符包括:public
、internal
、private
、fileprivate
,而 private(set)
的写权限限制可以结合这些修饰符共同使用。
修饰符 | 读权限 | 写权限 | 示例 |
---|---|---|---|
public | 允许所有模块读取 | 允许所有模块写入 | 对外完全开放 |
public private(set) | 允许所有模块读取 | 仅限当前模块的类或结构体内部写入 | 对外只允许读取,不允许直接修改 |
internal (默认修饰符) | 当前模块内可读写 | 当前模块内可读写 | 默认逻辑,适用于大多数项目 |
internal private(set) | 当前模块内可读 | 当前文件的类或结构体内部写入 | 对外主要公开只读权限,重要的业务逻辑修改集中在模块内部实现 |
private | 仅限当前作用域内读写 | 仅限当前作用域内读写 | 完全封装在当前类或结构体中 |
fileprivate | 当前文件内可读写 | 当前文件内可读写 | 与 internal 类似,但仅作用于同一个文件内 |
适用场景 #
1. 属性封装(防止意外更改) #
通过对外提供只读访问,防止外部代码直接修改某些需要保护的属性值,比如计数器、余额、集合等,增强代码的安全性和稳定性。
2. 集中管理状态修改逻辑 #
使用 private(set)
控制属性,只允许通过类或结构体内的特定方法更改属性值,比如增删逻辑、数据校验、通知等操作。
3. 与 MVC 或 MVVM 模式结合 #
在框架或架构设计中(如仅开放模型的数据读取),private(set)
可用于限制对模型层数据的直接写入,强制通过控制器或 ViewModel 操作模型数据。
关键点总结 #
private(set)
的核心功能:- 限制属性的写权限,仅限当前类型内部修改;
- 允许外部代码只读该属性。
适用于封装设计:
- 防止属性被外部直接修改,保护重要的状态变量。
与其他访问控制符结合:
public private(set)
:属性对外只读,但内部逻辑可以修改。internal private(set)
:模块内只读,但限制文件内或类型内改写权限。
安全性与封装: 提供安全的数据访问方案,让重要的数据逻辑容错性更高。
Swift 的访问控制为开发者在代码封装和安全性设计中提供了丰富的工具,private(set)
是一种非常常用的访问控制策略。