27、Swift 协议与类初始化全解析

Swift 协议与类初始化全解析

在 Swift 编程中,协议和类的初始化是非常重要的概念。协议可以定义方法、属性和初始化器的规范,而类的初始化则确保对象的属性被正确初始化。下面我们将详细探讨这些内容。

协议中的方法和初始化器

在协议中,方法只需要指定方法名、参数和返回类型,不需要实现细节。类型方法总是以 class 关键字开头。如果一个方法会修改调用它的对象,需要在方法声明前加上 mutating 关键字,在符合协议的结构体或枚举类型中定义该方法时也需要使用这个关键字,但在符合协议的类类型中不需要。

协议中也可以指定初始化器。如果定义一个符合协议的类,必须将每个初始化器实现为必需的初始化器,除非该类被声明为 final 。符合协议的类可以将协议的初始化器实现为指定初始化器或便利初始化器。如果协议定义了可失败初始化器,符合协议的类型可以将其实现为可失败或非可失败初始化器。协议中的非可失败初始化器必须在符合协议的类型中实现为非可失败初始化器或隐式解包的可失败初始化器(即 init! )。

创建 Invoice 类

下面我们创建 Invoice 类来表示只包含一种零件计费信息的发票。

// Fig. 10.16: Invoice.java
// Invoice class that adopts the Payable protocol
import Foundation

public class Invoice : Payable, Printable {
    let partNumber: String!
    let partDescription: String!
    var quantity: Int!
    var price: NSDecimalNumber!

    // initializer
    public init?(partNumber: String, partDescription: String,
                 quantity: Int, price: NSDecimalNumber) {
        if partNumber.isEmpty || partDescription.isEmpty ||
            quantity < 0 || (price.compare(NSDecimalNumber.zero()) ==
                             NSComparisonResult.OrderedAscending) {
            return nil
        }
        self.partNumber = partNumber
        self.partDescription = partDescription
        self.quantity = quantity
        self.price = price
    }

    // conform to the Payable protocol
    public var paymentAmount: NSDecimalNumber {
        let quantity = NSDecimalNumber(string: self.quantity.description)
        return quantity.decimalNumberByMultiplyingBy(price)
    }

    // return String representation of Invoice object
    public var description: String {
        let pricePerItem = NSNumberFormatter.localizedStringFromNumber(
            price, numberStyle: .CurrencyStyle)
        return String(format: "%@:\n%@: %@ (%@) \n%@: %d\n%@: %@",
                      "Invoice", "Part number", partNumber, partDescription,
                      "Quantity", quantity, "Price per item",
                      formatAsCurrency(pricePerItem))
    }
} // end class Invoice

Invoice 类采用了 Payable Printable 协议。为了符合 Payable 协议,类定义了 paymentAmount 属性,该属性通过将 quantity price 相乘来计算发票的总付款金额。为了符合 Printable 协议,类定义了 description 属性,该属性返回 Invoice 对象数据的格式化字符串表示。

使用扩展为 Employee 类添加协议一致性

我们可以通过扩展为现有的类添加协议一致性。以下是 Employee 类的示例:

// Fig. 10.17: Employee.swift
// Employee class that conforms to the Payable protocol via extensions
import Foundation

public class Employee {
    public var name: String!

    // failable initializer: if name is empty, return nil
    public init?(name: String) {
        if name.isEmpty {
            return nil
        }
        self.name = name
    }

    // earnings computed property
    public var earnings: NSDecimalNumber {
        return NSDecimalNumber.zero()
    }

    // description computed property
    public var description: String {
        return name
    }
}

// add Payable conformance to entire Employee hierarchy
extension Employee : Payable {
    var paymentAmount: NSDecimalNumber {
        return earnings
    }
}

// add Printable conformance to entire Employee hierarchy; empty
// extension because all Employee’s already have a description property
extension Employee : Printable {}

为了为 Employee 类添加 Payable 协议一致性,我们定义了一个扩展,采用该协议并实现了 paymentAmount 属性,该属性简单地返回员工的收入。由于 Employee 类已经定义了符合 Printable 协议要求的 description 属性,我们可以通过定义一个空的扩展来添加 Printable 协议一致性。

协议一致性在继承层次结构中的应用

当一个类型符合一个协议时,就像继承一样存在 is-a 关系。当一个超类符合一个协议时,它的所有子类也符合该协议,并与该协议类型具有 is-a 关系。因此,我们可以将 SalariedEmployee CommissionEmployee BasePlusCommissionEmployee 对象分配给 Payable 变量,因为它们都是 Payable

使用 Payable 协议多态处理发票和员工

以下代码展示了如何使用 Payable 协议在单个应用程序中多态处理发票和员工:

// Fig. 10.18: main.swift
// Processing Payables (Invoices and Employees) polymorphically
import Foundation

// create Array of Payables
var payableObjects: [Payable] = [
    SalariedEmployee(name: "John Smith",
                     weeklySalary: NSDecimalNumber(string: "800.00"))!,
    Invoice(partNumber: "01234", partDescription: "seat",
            quantity: 2, price: NSDecimalNumber(string: "375.00"))!,
    CommissionEmployee(name: "Sue Jones",
                       grossSales: NSDecimalNumber(string: "10000.00"),
                       commissionRate: NSDecimalNumber(string: "0.06"))!,
    Invoice(partNumber: "56789", partDescription: "tire",
            quantity: 4, price: NSDecimalNumber(string: "79.95"))!,
    BasePlusCommissionEmployee(name: "Bob Lewis",
                               grossSales:NSDecimalNumber(string: "5000.00"),
                               commissionRate: NSDecimalNumber(string: "0.04"),
                               baseSalary: NSDecimalNumber(string: "300.0"))!
]

println("INVOICES AND EMPLOYEES PROCESSED POLYMORPHICALLY\n")

// display each Payable’s description and paymentAmount properties
for currentPayable in payableObjects {
    println(currentPayable) // implicitly uses description property
    let paymentAmount = formatAsCurrency(currentPayable.paymentAmount)
    println("Payment Due: \(paymentAmount)\n")
}

上述代码定义了一个 Payable 数组 payableObjects ,并使用五个 Payable 对象进行初始化。通过循环遍历数组,我们可以多态地处理每个 Payable 对象,使用 description paymentAmount 属性获取对象的信息和付款金额。

协议继承

协议可以从一个或多个其他协议继承。协议继承的语法如下:

protocol ProtocolName : InheritedProtocol1, InheritedProtocol2, … {
    // additional protocol member descriptions
}
类专用协议

如果一个协议仅用于引用类型(即类),可以使用 class 关键字将其指定为类专用协议:

protocol ProtocolName : class {
    // protocol member descriptions
}

如果类专用协议还继承自其他协议,它们应放在 class 关键字后面的逗号分隔列表中:

protocol ProtocolName : class, InheritedProtocol1, InheritedProtocol2, … {
    // additional protocol member descriptions
}
定义具有可选功能的协议

在 Swift 中,类专用协议可以包含可选成员。要包含可选成员,协议必须以 @objc 关键字开头,并且每个可选功能必须以 optional 关键字开头。在访问可选功能时,必须使用可选链来确保不会发生运行时错误。

协议组合

可以使用协议组合来指定一个对象在给定上下文中必须符合多个协议。例如,如果一个函数参数应同时符合 Payable Printable 协议,可以这样指定参数类型:

parameterName : protocol<Payable, Printable>
总结

本文介绍了 Swift 中协议和类初始化的相关知识,包括协议中的方法和初始化器、创建符合协议的类、使用扩展添加协议一致性、协议继承、类专用协议、可选功能协议和协议组合等。通过这些特性,我们可以编写更加灵活和可维护的代码。

下面是一个简单的 mermaid 流程图,展示了 Invoice 类的初始化过程:

graph TD;
    A[开始] --> B{参数验证};
    B -- 不通过 --> C[返回 nil];
    B -- 通过 --> D[初始化属性];
    D --> E[结束];

同时,我们可以用表格总结协议的不同特性:
| 特性 | 描述 |
| ---- | ---- |
| 协议中的方法 | 只指定方法名、参数和返回类型,无实现细节 |
| 协议中的初始化器 | 符合协议的类需实现,可指定为必需或可失败初始化器 |
| 协议继承 | 一个协议可继承多个其他协议 |
| 类专用协议 | 仅用于类,用 class 关键字指定 |
| 可选功能协议 | 类专用协议可包含可选成员,用 @objc optional 关键字 |
| 协议组合 | 指定对象需符合多个协议 |

通过这些知识和示例,我们可以更好地理解和应用 Swift 中的协议和类初始化机制。

Swift 协议与类初始化全解析

使用 final 关键字

在 Swift 中, final 关键字有着重要的作用。 final 可以修饰属性、方法、下标和类。
- final 属性、方法和下标 :当一个超类中的属性、方法或下标被声明为 final 时,子类无法对其进行重写。这保证了所有子类都使用超类中该属性、方法或下标的实现,因为其实现不会被改变。
- final 类 :如果一个类被声明为 final ,那么它不能作为超类被继承。并且, final 类的所有成员都隐式地是 final 的。将一个类声明为 final 可以防止程序员创建可能绕过安全限制的子类,确保程序依赖该类提供的功能。

类层次结构中的初始化和反初始化

类实例的初始化在 Swift 中有其独特的规则和流程。

基本类实例初始化规则

在深入了解之前,我们先回顾一些基本规则:
- 类的所有存储属性都必须被初始化,可以通过初始化器提供的值,也可以在属性定义中指定默认值。
- 每个类至少要有一个指定初始化器,并且可以有任意数量的便利初始化器。
- 如果类的所有存储属性都在定义中指定了默认值,且没有定义任何初始化器,编译器会提供一个无参数的指定初始化器,将存储属性设置为指定的默认值。
- 每个便利初始化器最终都必须调用自己类的一个指定初始化器,可以直接调用,也可以通过另一个便利初始化器间接调用。

类层次结构中的初始化流程

Swift 使用两阶段初始化过程来确保子类对象的所有存储属性在使用前都被正确初始化:
1. 阶段 1 :当创建一个子类对象时,会触发一系列指定初始化器的调用,确保每个存储属性都由最初定义该属性的类的指定初始化器赋予一个初始值。这一阶段保证了每个存储属性在使用前都有值,并且子类初始化器设置的值不会被超类初始化器覆盖。
2. 阶段 2 :当初始化器调用链返回到子类初始化器时,所有存储属性都已经有了初始值。此时,子类初始化器可以更改子类继承的存储属性的初始值,确保每个子类可以根据需要自定义其继承存储属性的初始化。

除了上述两阶段初始化过程,Swift 还有一些额外的规则:
- 如果子类的所有存储属性都在定义中指定了默认值,并且没有在子类中定义任何初始化器,子类将继承其超类的初始化器。
- 每个子类指定初始化器必须先初始化子类的存储属性,然后调用超类的指定初始化器来为继承的存储属性赋值。
- 只有在超类指定初始化器初始化了继承的存储属性之后,子类初始化器才能修改这些属性的值。

下面是一个子类指定初始化器的执行顺序列表:
1. 初始化子类的存储属性,可以通过显式语句,也可以设置为属性定义中指定的默认值(注意,可选类型默认初始化为 nil )。
2. 调用超类的指定初始化器,可以是隐式调用默认初始化器(如果有),也可以是显式调用指定初始化器。
3. 对继承的超类存储属性进行子类特定的自定义。

为了防止错误,Swift 编译器会强制执行初始化规则,如果出现以下情况会生成错误:
1. 子类指定初始化器在调用超类指定初始化器之前没有初始化所有子类的存储属性。
2. 子类初始化器在调用超类指定初始化器之前尝试为继承的存储属性赋值。
3. 子类便利初始化器在调用子类指定初始化器或委托给另一个便利初始化器之前尝试为子类存储属性赋值。
4. 初始化器在两阶段初始化过程的第一阶段完成之前尝试使用对象( self )或其任何属性(直接或通过调用类的方法)。

BasePlusCommissionEmployee 对象的初始化示例

我们以 BasePlusCommissionEmployee 对象的初始化为例,详细说明上述规则的应用。

graph TD;
    A[创建 BasePlusCommissionEmployee 对象] --> B[调用 BasePlusCommissionEmployee 初始化器];
    B --> C[初始化 baseSalary 为默认值 nil];
    C --> D[调用 CommissionEmployee 初始化器];
    D --> E[验证和初始化 CommissionEmployee 属性];
    E --> F{CommissionEmployee 初始化器返回};
    F -- 成功 --> G[验证和初始化 baseSalary];
    F -- 失败 --> H[BasePlusCommissionEmployee 初始化器失败并返回 nil];
    G --> I[初始化完成];

当创建一个 BasePlusCommissionEmployee 对象时, BasePlusCommissionEmployee 初始化器首先将 baseSalary 属性初始化为默认值 nil ,然后显式调用 CommissionEmployee 的初始化器。 CommissionEmployee 的初始化器验证并初始化在 CommissionEmployee 类中定义并继承到 BasePlusCommissionEmployee 中的属性,这完成了两阶段初始化过程的第一阶段。

CommissionEmployee 的初始化器返回控制给 BasePlusCommissionEmployee 的初始化器时,会验证并初始化子类存储属性 baseSalary 。如果超类的可失败初始化器返回 nil ,子类可失败初始化器会立即失败并返回 nil ,此时无需设置 baseSalary

重写初始化器和必需初始化器

与子类重写属性或方法类似,当子类定义一个与超类指定初始化器具有相同签名的初始化器时,必须使用 override 关键字。而对于与超类便利初始化器具有相同签名的子类初始化器,不需要 override 关键字。只有当子类继承其超类的初始化器时,超类的便利初始化器才对子类可用。

总结

通过本文,我们全面了解了 Swift 中协议和类初始化的各种特性和规则。以下表格总结了类初始化的关键规则:
| 规则类型 | 规则描述 |
| ---- | ---- |
| 基本初始化规则 | 存储属性必须初始化,类至少有一个指定初始化器,便利初始化器需调用指定初始化器 |
| 两阶段初始化 | 阶段 1 确保属性初始值,阶段 2 允许子类自定义继承属性 |
| 子类初始化器规则 | 先初始化子类属性,再调用超类指定初始化器,之后可修改继承属性值 |
| 重写初始化器 | 指定初始化器重写需 override 关键字,便利初始化器不需要 |

在实践中,我们可以根据这些规则和特性编写更加健壮、可维护的代码。无论是使用协议来定义类型的行为,还是使用初始化规则来确保对象的正确初始化,都能让我们的 Swift 编程更加得心应手。同时,合理使用 final 关键字可以增强代码的安全性和稳定性。希望本文能帮助你更好地掌握 Swift 中的协议和类初始化机制。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值