Day11 – 协议和拓展

Swift —— 100 天从新手到大师
它的目标是那些想要学习如何构建真正的 iOS 应用程序的开发者
如果说的是你 —— 准备好了吗
面向对象编程思想可以帮助你构建代码。使你能够重用和扩展代码,而不必重复自己。最终,这有助于你避免错误并且更高效地编写代码。与其让代码像流水线一样流动,不如将它们封装在具有其属性和功能的类和对象中。
面向对象编程思想消除了大型,复杂的继承层次结构,并用可以组合在一起的更小,更简单的协议代替了它们。

今天我们将学习一些真正灵活的功能。




协议






拓展

,以及组合使用的




协议拓展









协议

协议是描述某些事物必须具有的 




属性和方法

 



的一种方式。使用协议是 Swift 最基本的功能之一。


通过使用协议,你可以定义采用类必须遵循的



 

规则






这个原则使你可以编写解耦的,模块化的和可扩展的 Swift 代码。


官方文档定义 

Protocols
 为

A protocol defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality.

可以说协议定义了规则或 

要求
,例如功能和属性。然后其他类可以采用这些规则,并提供实际的实现。满足协议规则的任何类都被称为遵守该协议。

创建并使自定义类型遵守协议- 定义

协议的定义方式与类、结构体和枚举的定义非常相似:

protocol SomeProtocol {
// 这里是协议的定义部分
}

要让自定义类型遵循某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(

:
)分隔。

struct SomeStructure: FirstProtocol {
// 这里是结构体的定义部分
}

遵循多个协议时,各协议之间用逗号(

,
)分隔:

struct SomeStructure: FirstProtocol, AnotherProtocol {
// 这里是结构体的定义部分
}

若是一个类拥有父类,应该将父类名放在遵循的协议名之前,以逗号分隔:

class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 这里是类的定义部分
}

当然,协议也可以遵循其他协议:

// 载人
protocol CarriesPassengers {
var passengerCount: Int { get set }
}


// 载货
protocol CarriesCargo {
var cargoCapacity: Int { get set }
}


// 船
protocol Boat: CarriesPassengers, CarriesCargo {
var name: String { get set }
}


// 车
protocol Car: CarriesPassengers, CarriesCargo {
var name: String { get set }
}

与类,结构体等其他不同的是,协议之间的遵循更像是一种抽象,解耦。

在下面的例子中,我们将定义一个可售卖产品的协议 

Purchaseable
 来统一我们 

商场
 中的产品,以此来介绍协议的具体用法。

定义属性

协议可以要求遵循协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储属性还是计算属性,它只指定属性的名称和类型。当然,协议还指定属性读写性,是


只读

 的还是


可读可写的



protocol Purchaseable {
// 商品名称
var name: String { get set }
// 折扣
var discount: Double { get }
}

协议中属性的实现都是基于遵守协议的类、结构体和枚举实现的,所以协议的属性只能是变量而不能是不可变的常量,即便这个属性是 


只读

 的。

struct Book: Purchaseable {
var name: String
// 实现 get 方法
var discount: Double {
return 0.90
}
}

即便我们协议中设定为 


只读

 ,但是,实际上在遵循协议的类实现中,这个值是


可读可写的


struct Coat: Purchaseable {
var name: String
var discount: Double
}


var coat = Coat(name: "100 Days of Swift", discount: 0.78)
coat.discount = 0.90

协议同样支持声明类型属性。我们可以使用 

static
 关键字来修饰类型属性

protocol TypepropertyProtocol {
static var typeProperty: Int { get set }
}

定义方法

协议可以要求遵循协议的类型实现某些指定的实例方法或类方法。

接下来我们为商品协议定义一个

settlement
方法,用来说明售卖方法

protocol Purchaseable {
...
// 结算
func settlement()
...
// 接受参数 和 返回值
func settlement(_ count: Int) -> Bool
}

在 


Day8 – 结构体和对象 上

 里说过,我们可以使用 

mutating
 修饰 

func
 可以修改当前值类型对象的 


读写

 属性



protocol Purchaseable {
...
mutating func settlement(_ count: Int)
}


struct Coat: Clothes, Purchaseable {
...
mutating func settlement(_ count: Int) {
self.discount = 0.7
}
}




定义构造器

协议可以要求遵循协议的类型实现指定的构造器。

protocol Clothes {
init(_ count: Int, _ limit: Int)
}

这就要求遵循协议的类型是类,则必须要实现一个 


required

 修饰的构造器称为 


必要构造器



class Pants: Clothes {
required init(_ count: Int, _ limit: Int) {

}

func settlement() {

}
}

子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么需要同时标注 

required
 和 

override
修饰符:

class Pants {
init(_ count: Int, _ limit: Int) {

}
}


class Jeans: Pants, Clothes {
required override init(_ count: Int, _ limit: Int) {
super.init(count, limit)
}
func settlement() {

}
}

当我们的类需要遵循 

NSCoding protoaol
 协议时,我们需要在代码中添加

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

如果在协议中实现构造器的话,建议实现一个 


可失败构造器


为什么要使用协议?

协议是一种类型

尽管协议本身并未实现任何功能,但是协议可以被当做一个


功能完备的类型

(fully-fledged 


types

)来使用。例如

  • 作为函数、方法或构造器中的参数类型或返回值类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器中的元素类型

在我们的产品例子中,我们实现了一个结算的方法,我们可以为我们的收银员对象添加一个结算的操作,而为我们的顾客对象添加一个购买的操作

struct Cashier {
// 结算
func settlement(_ list: [Purchaseable]) {
var shoppingList = list
while !shoppingList.isEmpty {
let product = shoppingList.removeFirst()
product.settlement()
}
}
}


struct Customer {
var shoppingList : [Purchaseable] = []
// 购买
mutating func buy(_ product: Purchaseable) {
self.shoppingList.append(product)
}
}

除了在挑选商品时,我们的顾客可能需要知道产品的具体信息,但是在结算时,收银员只需要知道他是一个商品就行了

具体使用案例

在 Swift 中协议主要被用来做以下几件事,在这里我们简单的介绍一下



  • 代理


    可能是在 Swift 中协议的最常见的用法了。代理的工作方式是将功能从基类转移到另一个委托类。这个基类通常不受开发人员的控制,但是通过使用代理,仍然可以影响它的功能。在 Swift 中通过协议来定义那些可以被传递的功能。



  • 依赖注入


    是一种使代码组件更易于测试的技术。它使用协议来创建特定类型的浅层实现(称为存根)和某些对象的伪实现(称为mocks)。



  • Protocol-Oriented Programming


     (POP) 面向协议编程。这可能是从 Swift 开始时就是一直被提及的模式了。你可以把面向协议编程看作是面向对象编程的扩展。使用 POP,通过组合不同的协议来创建应用程序中的功能,而不是通过使用继承来定义一个严格的分层类结构。面向协议的编程倾向于通过更精简的单个组件来增加代码的模块性,同时它对功能的精确实现提供了更多的控制。我们可以从 

    Protocol-Oriented Programming in Swift
     开始我们的面向协议编程

拓展


扩展

 可以给一个现有的类,结构体,枚举,还有协议添加 




新的功能





它还拥有不需要访问被扩展类型源代码就能完成扩展的能力(即

逆向建模
)。扩展和 Objective-C 的 Category 很相似。与 Objective-C Category 不同的是,Swift 扩展是没有名字的。


在使用拓展时,我们可以给拓展添加




计算属性





不过 Swift不允许你在扩展中添加







存储属性









,或向现有的属性添加属性观察者





扩展



最大的优势是 可以向无权访问源代码的类添加新功能

下面是 Swift 中扩展的功能:

  • 添加函数和计算属性
  • 提供新的构造器
  • 利用 

    subscript()
     函数定义下标

  • 定义和使用新的嵌套类型,例如,向类型添加子类型
  • 使当前类型遵循某个协议,


    非常有用!

  • 向具有





    协议扩展 







    的协议添加默认实现。后面会详细

如何定义一个拓展

使用 extension 
关键字声明扩展:

extension Int {
func cubed() -> Int {
return self * self * self
}
}

扩展可以给现有类型添加计算型实例属性和计算型类属性

extension Double {
var isNegative: Bool {
return self < 0
}
}

扩展可以给现有的类型添加新的构造器。

对于 

Class
 来说,扩展只可以添加便利构造器,而不可以添加指定构造器

class Rect {
var origin = Point()
var size = Size()
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
}


extension Rect {
convenience init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}

而对于值类型的结构体来说, 如果为所有的存储属性都提供了默认值,且没有定义任何构造器,那么你可在拓展中使用默认的


逐一成员构造器


struct Rect {
var origin = Point()
var size = Size()
}


extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}

如果结构体定义了构造器,则拓展中实现的成员构造器

struct Rect {
var origin = Point()
var size = Size()
init(_ origin: Point, _ size: Size) {
self.origin = origin
self.size = size
}
}


extension Rect {
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(Point(x: originX, y: originY), size)
}
}

为什么要使用拓展?

笔者认为充分利用扩展,能够更好地


组织代码

。在实际生产环境中,我们一般使用拓展来

  • 拆分代码
  • 遵守协议
  • 添加类型常量和增加嵌套类型
  • 为类新增函数和计算属性
  • 协议拓展

拆分代码

使用扩展的将类的不同部分进行拆分。对于基类来说仅需提供基本信息(例如,属性),然后我们利用拓展来添加不同的函数

struct Airplane {
var speed: Double = 0.0
var altitude: Double = 0.0
var bearing: Double = 0.0
}
// 一些可变方法,修改本身的属性
extension Airplane {
mutating func changeAltitude(altitude: Double) {
self.altitude = altitude
}
mutating func changeBearing(degrees: Double) {
self.bearing = degrees
}
}
// 一些实例方法,定义操作
extension Airplane {
func takeOff() {

}
func land() {

}
}

当我们在定义类时,不要将所有代码都写在基类的大括号中,可以通过使用拓展来进行拆分,增加代码的可读性

遵守协议

我们可以在拓展中让我们的类遵守协议 ,例如,遵守 UITableView 的 Delegate Protocol

class DetailViewController: UIViewController {

}


extension DetailViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell(style: UITableViewCell.CellStyle.value1, reuseIdentifier: "reuseIdentifier")
}

}

添加静态属性和增加嵌套类型

虽然我们无法在扩展中添加存储属性,但可以在扩展中添加静态属性和子类型。

// 静态属性
extension Notification.Name {
static let statusUpdated = Notification.Name("status_updated")
}
// 添加嵌套类型
extension UserDefaults {
struct Keys {
static let useSync = "use_sync"
static let lastSync = "last_sync"
}
}

为类新增函数和计算属性

这就是我们一直说的一个功能,就不再解释了。

协议拓展

与遵守协议不同的是,协议扩展允许你
直接扩展协议
。真正令人难以置信的是,你可以通过使用扩展提供协议的默认实现。

协议可以通过扩展来为遵循协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个遵循协议的类型中都重复同样的实现,也无需使用全局函数。
协议扩展可以为遵循协议的类型增加实现,但不能声明该协议继承自另一个协议。协议的继承只能在协议声明处进行指定。

提供默认实现

protocol Politician {
var isDirty: Bool { get set }
func takeBribe()
}


extension Politician {
func takeBribe() {
if isDirty {
print("Thank you very much!")
} else {
print("Someone call the police!")
}
}
}

直接扩展协议

可以使用 

where
 关键字将扩展约束到特定类型或协议。

extension Sequence where Element: Hashable {
func unique() -> Set<Element> {
var uniques = Set<Element>()
for item in self {
uniques.insert(item)
}
return uniques
}
}

本文的提纲来自于 hackingwithswift 的 《100 Days of Swift》,然后根据每一个知识点进行总结

欢迎点赞、转发、评论、在看。
如果有任何问题,欢迎留言交流