Swift — 类(Class)

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 岁。

关键点 #

  1. 属性和方法:
    • 属性用来存储对象的状态,例如 nameage
    • 方法定义类的行为,例如 introduce()
  2. 构造函数(Initializer):
    • 使用 init 函数初始化对象的属性。
  3. 访问对象属性和方法:
    • 通过 . 访问属性(如 person1.name)。
    • 调用方法(如 person1.introduce())。

2. 封装(Encapsulation) #

  • 封装 是面向对象设计中的一个重要原则,它允许我们控制对象属性和方法的访问级别(如公开、私有)。
  • Swift 提供了强大的访问控制修饰符:privatefileprivateinternal(默认)、publicopen

示例代码 #

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

关键点 #

  1. 访问控制:
    • private:限制属性 balance 的直接访问,只有类内部可以访问和修改。
  2. 通过方法提供访问:
    • 使用 getBalance() 提供受控的只读访问,而非直接暴露 balance 属性。
  3. 保护数据:
    • 使用封装技术可以防止数据被无意修改。

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())  // 输出:旺财 说:汪汪!

关键点 #

  1. 子类(Subclass):
    • 子类通过继承获取父类的属性和方法。
  2. 重写(Override):
    • 使用 override 关键字重写父类中的方法,使子类有不同的行为。
  3. 多态:
    • 父类或子类的对象都可以调用同名方法,根据实际类型执行对应的方法。

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

关键点 #

  1. 父类引用调用子类方法:
    • shapesShape 类型,但可存储 CircleRectangle 类型。
  2. 动态绑定:
    • 调用 area() 方法时,实际执行的是具体子类的实现(CircleRectangle 的实现)。

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()
    // 输出:
    // 画一个圆
    // 画一个矩形
}

关键点 #

  1. 协议定义接口:
    • Drawable 规定任意遵循本协议的类都要实现 draw() 方法。
  2. 多态结合协议:
    • 协议可用于实现多态特性。

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)属性对外只读,但限制写权限只能在 当前类或结构体内和属性本身的访问权限一致(可 internalpublic)。仅限当前类或结构体内部修改。适用于内部修改逻辑封装,但允许外界读取,例如计数器、状态管理器等。
fileprivate(set)属性对外只读,但限制写权限只能在当前文件内使用和属性本身的访问权限一致。仅限当前文件内修改。适用于文件内共享修改权限的场景,例如文件内的协作代码(多个扩展共享状态修改)。
internal(set)属性对外只读,但限制写权限只能在当前模块内使用。和属性本身的访问权限一致。仅限当前模块内修改。用于在模块内部有写权限,但对外模块只暴露为只读状态(典型场景:状态属性如 public internal(set) status)。

(2) 方法的动态行为权限(继承与重写) #

修饰符作用继承权限重写权限适用场景
open方法或类允许 任何模块继承,且允许子类重写。所有模块均可继承。所有模块均可重写。适用于需要提供高扩展性的类或方法,例如框架中的自定义视图控件、抽象接口等。
public方法或类可以被外部模块访问,但不能继承和重写。不能被继承(除非提升为 open)。不允许重写。适用于对外完全封闭,但是需要调用的方法,例如静态工具类的公共方法(Logger.shared.log())。

3. 特殊情况下的访问和限制(扩展与协议) #

修饰符作用适用范围适用场景
private extension扩展中的方法或属性访问限制为私有,仅限当前类。仅限于当前类型中访问。一般用于隐藏实现细节(如某些工具方法不希望被外界直接访问)。
fileprivate extension扩展中的方法或属性可以在当前文件中的任何类型内访问。当前文件内任何代码均能访问。适用于文件内的共享逻辑,例如各个扩展中共享的功能(如数据格式化、工具方法)。
协议访问控制 (protocol)控制协议的实现范围,可以是 publicinternalprivate当前协议、协议扩展或子类化的模块范围。限制协议的访问,比如定义一组实现的行为,但又不希望外部使用这些实现,只允许在特定作用域生效。

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) 方法与类的访问控制场景对比 #

需求适用修饰符示例代码
允许方法被其他模块继承和重写openopen func renderView() {}
只允许模块内继承,但不允许外部模块继承internalinternal class ImageProcessor {}
封闭类,只供内部使用,绝不能继承privatefileprivateprivate 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) 等实现灵活的逻辑保护。
  • 提高代码封装性和安全性:
    • 使用 privatefileprivate 保护实现细节,仅暴露必要的接口。
  • 提升代码可扩展性:
    • 使用 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)),但它的值只能通过类内部的方法 incrementreset 修改,防止代码外部直接篡改属性值
    • 如果没有 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 对外是只读的,防止外部代码直接修改数组内容。
    • 内部通过 addMemberremoveMember 方法集中管理数组的增删逻辑,实现数据更可靠的封装。

private(set) 与其他访问控制修饰符的对比 #

示例 #

Swift 中常用的访问控制符包括:publicinternalprivatefileprivate,而 private(set) 的写权限限制可以结合这些修饰符共同使用。

修饰符读权限写权限示例
public允许所有模块读取允许所有模块写入对外完全开放
public private(set)允许所有模块读取仅限当前模块的类或结构体内部写入对外只允许读取,不允许直接修改
internal(默认修饰符)当前模块内可读写当前模块内可读写默认逻辑,适用于大多数项目
internal private(set)当前模块内可读当前文件的类或结构体内部写入对外主要公开只读权限,重要的业务逻辑修改集中在模块内部实现
private仅限当前作用域内读写仅限当前作用域内读写完全封装在当前类或结构体中
fileprivate当前文件内可读写当前文件内可读写internal 类似,但仅作用于同一个文件内

适用场景 #

1. 属性封装(防止意外更改) #

通过对外提供只读访问,防止外部代码直接修改某些需要保护的属性值,比如计数器、余额、集合等,增强代码的安全性和稳定性。

2. 集中管理状态修改逻辑 #

使用 private(set) 控制属性,只允许通过类或结构体内的特定方法更改属性值,比如增删逻辑、数据校验、通知等操作。

3. 与 MVC 或 MVVM 模式结合 #

在框架或架构设计中(如仅开放模型的数据读取),private(set) 可用于限制对模型层数据的直接写入,强制通过控制器或 ViewModel 操作模型数据。


关键点总结 #

  1. private(set) 的核心功能:

    • 限制属性的写权限,仅限当前类型内部修改;
    • 允许外部代码只读该属性。
  2. 适用于封装设计:

    • 防止属性被外部直接修改,保护重要的状态变量。
  3. 与其他访问控制符结合:

    • public private(set):属性对外只读,但内部逻辑可以修改。
    • internal private(set):模块内只读,但限制文件内或类型内改写权限。
  4. 安全性与封装: 提供安全的数据访问方案,让重要的数据逻辑容错性更高。

Swift 的访问控制为开发者在代码封装和安全性设计中提供了丰富的工具,private(set) 是一种非常常用的访问控制策略。

本文共 6139 字,上次修改于 Jan 24, 2025