转载

Protocol-Oriented Programming with Swift 笔记(下)

接着来学习后半部分

第四章 关于协议的一切

本章的主题有:

  • 如何定义协议需要的属性和方法
  • 如何使用协议继承和组合
  • 如何将一个协议作为类型使用
  • 协议如何实现多态
  • 如何在协议中使用相关值(Associated values)
  • 如何在 Swift 中使用 delegation 模式
  • 如何设计协议的类型要求

协议定义

可以定义属性和方法,如果是值类型遵守协议,方法涉及到修改自身属性记得要加 mutating 关键字

protocol FullName {  
     var firstName: String {get set}
     var lastName: String {get set}
     static var typeProperty: String {get}

     mutating func changeName()
}

协议中的可选属性和方法

我们用 @objc 关键字修饰协议和相关的可选属性和方法

@objc protocol Phone {
    var phoneNumber: String {get set}
    optional var emailAddress: String {get set}

    func dialNumber()
    optional func getEmail()
}

注意,只有类 Class 可以遵守用 @objc 修饰了的协议

协议继承

协议不但能够继承,还能够多继承,这是类继承所做不到的,注意实现的时候需要实现所有的协议方法。

protocol ProtocolThree: ProtocolOne, ProtocolTwo {  
       // Add requirements here
}

协议组合

协议组合更像是将单一功能的模块定义为单个协议,然后需要什么功能就部署什么功能,毕竟协议支持多部署(可以组合起来使用)

struct MyStruct: ProtocolOne, ProtocolTwo, Protocolthree {  
    // implementation here
}

将协议作为类型使用

我们可以将协议作为函数参数和返回值使用

protocol PersonProtocol {  
    var firstName: String {get set}
    var lastName: String {get set}
    var birthDate: NSDate {get set}
    var profession: String {get}

    init (firstName: String, lastName: String, birthDate: NSDate)
}

func updatePerson(person: PersonProtocol) -> PersonProtocol {  
    var newPerson: PersonProtocol
    // Code to update person goes here
    return newPerson
}

还可以在集合中当做类型使用:

var personArray = [PersonProtocol]()  
var personDict = [String: PersonProtocol]()

但是协议不能实例化,我们只能使用遵循协议的具体类型来实例化一个对象

var myPerson: PersonProtocol  
myPerson = SwiftProgrammer(firstName: "Jon", lastName: "Hoffman",  
    birthDate: bDateProgrammer)
myPerson = FootballPlayer(firstName: "Dan", lastName: "Marino",  
    birthDate: bDatePlayer)

Swift 并不关心具体类型是什么(类、结构体、枚举),只关心是否遵守了 PersonProtocol 协议。

协议实现多态

多态可以让我们使用单独的一个接口与多种类型进行交互,这个单独统一的接口通常来自于父类或协议中的定义。

在下面的例子中 SwiftProgrammerFootballPlayer 都遵循了 PersonProtocol 协议,所以可以用 people 实例(遵守 PersonProtocol 协议的数组)来添加 append 不同的类型,即所谓单一接口处理多种类型的能力。

var programmer = SwiftProgrammer(firstName: "Jon", lastName:  
    "Hoffman", birthDate: bDateProgrammer)
var player = FootballPlayer(firstName: "Dan", lastName: "Marino",  
    birthDate: bDatePlayer)
var people: [PersonProtocol] = []  
people.append(programmer)  
people.append(player)

协议类型转换

Swift 中自省用 is 判断, as 进行转换,这里注意 is 可以在 switch 中使用

for person in people {  
    switch (person) {
    case is SwiftProgrammer:
        print("/(person.firstName) is a Swift Programmer")
    case is FootballPlayer:
        print("/(person.firstName) is a Football Player")
    default:
        print("/(person.firstName) is an unknown type")
    }
}

可以配合 where 做过滤

for person in people where person is SwiftProgrammer {  
    print("/(person.firstName) is a Swift Programmer")
}

协议的相关值

associated type 实际提供了一个占位符,最终实际类型要等到部署时才确定

protocol QueueProtocol {  
    associatedtype QueueType
    mutating func addItem(item: QueueType)
    mutating func getItem() -> QueueType?
    func count() -> Int
 }

最终实现协议时要指明白 QueueType 的类型,我们也可以用泛型在 runtime 时再确定具体的类型:

class GenericQueue<T>: QueueProtocol {  
    var items = [T]()

    func addItem(item: T) {
        items.append(item)
    }

    func getItem() -> T? {
        if items.count > 0 {
            return items.removeAtIndex(0)
        } else {
            return nil 
        }
    }

    func count() -> Int {
        return items.count
    } 
}

代理模式

代理模式与 OC 中的代理实现起来是一样的,就不啰嗦了。

第五章 让我们来扩展一些类型

我们可以扩展结构体、类、枚举对象以及协议,扩展里可以添加的类型有以下几种:

  • 计算属性
  • 实例和类方法
  • 便利初始化方法(Convenience initializers)
  • 下标方法(Subscripts)

扩展是用来增加功能,而不是改写其原有功能的。还有不能添加存储属性。扩展一个 String

extension String {  
    func getFirstChar() -> Character? {
        guard self.characters.count > 0 else {
            return nil
        }
        return self[startIndex]
    }
    subscript (r: Range<Int>) -> String {
        get {
            return substringWithRange(Range(
            start: startIndex.advancedBy(r.startIndex),
            end: startIndex.advancedBy(r.endIndex)))
        } 
    }
}

协议扩展

可以为一些协议添加默认的实现,需要确保我们添加的实现满足所有遵循协议的类型。比如我们可以限制扩展方法生效的范围:必须满足 ArrayLiteralConvertible ,就把字典对象排除掉了

extension CollectionType where Self: ArrayLiteralConvertible {  
    //Extension code here
}

第六章 Swift 中的设计模式

《Design Patterns: Elements of Reusable Object-Oriented Software》 这本书提到 23 种设计模式,可以分为三类:创造,结构和行为。

我们需要注意设计模式并不是银弹,要去理解背后所要解决的问题。

Creational patterns

主要处理对象的创建,其背后有两个核心思想:一是封装具体类型信息,二是隐藏实例的创建细节,一共有五种广为人知的创建设计模式,具体介绍三种

  • Abstract factory pattern(抽象工厂模式)
  • Builder pattern(建造者模式)
  • Factory method pattern(工厂方法模式)
  • Prototype pattern(原型模式)
  • Singleton pattern(单例模式)

单例模式

适用于需要集中在一个地方管理内外部资源,维护一个状态,比如保持蓝牙的连接,每个页面都能获取到单例,而不用去重新创建并维护。

单例的生命周期通常和应用是相同的

由于单例的特性,它只能由引用类型(类)实现

class MySingleton {  
    static let sharedInstance = MySingleton()
    var number = 0
    private init() {}
}

上面的例子不需要实例化这个类就能使用静态常量,而且通过 private 限制了其他代码创建这个类的实例

Builder pattern(建造者模式)

不使用设计模式的结构体初始化函数很复杂,调用时需要写很多参数:

struct BurgerOld {  
    var name: String
    var patties: Int
    var bacon: Bool
    var cheese: Bool
    var pickles: Bool
    var ketchup: Bool
    var mustard: Bool
    var lettuce: Bool
    var tomato: Bool
}

// Create Hamburger
var burgerOld = BurgerOld(name: "Hamburger", patties: 1, bacon:  
    false, cheese: false, pickles: false, ketchup: false, mustard:
        false, lettuce: false, tomato: false)

// Create Cheeseburger
var burgerOld = BurgerOld(name: "Cheeseburger", patties: 1, bacon:  
    false, cheese: false, pickles: false, ketchup: false, mustard:
        false, lettuce: false, tomato: false)

我们通过定义一个协议来封装这些初始化需要的参数

protocol BurgerBuilder {  
    var name: String {get}
    var patties: Int {get}
    var bacon: Bool {get}
    var cheese: Bool {get}
    var pickles: Bool {get}
    var ketchup: Bool {get}
    var mustard: Bool {get}
    var lettuce: Bool {get}
    var tomato: Bool {get}
}

改写初始化方法:

struct Burger {  
    var name: String
    var patties: Int
    var bacon: Bool
    var cheese: Bool
    var pickles: Bool
    var ketchup: Bool
    var mustard: Bool
    var lettuce: Bool
    var tomato: Bool

    init(builder: BurgerBuilder) {
        self.name = builder.name
        self.patties = builder.patties
        self.bacon = builder.bacon
        self.cheese = builder.cheese
        self.pickles = builder.pickles
        self.ketchup = builder.ketchup
        self.mustard = builder.mustard
        self.lettuce = builder.lettuce
        self.tomato = builder.tomato
    }

早先初始化需要 9 个参数,现在只需要一个遵守 BurgerBuilder 协议的对象就好了,我们可以创建这个对象,然后在 Burger 初始化时当做参数传递进去

struct CheeseBurgerBuilder: BurgerBuilder {  
    let name = "CheeseBurger"
    let patties = 1
    let bacon = false
    let cheese = true
    let pickles = true
    let ketchup = true
    let mustard = true
    let lettuce = false
    let tomato = false
}

// Create Cheeseburger with tomatos
var myCheeseBurgerBuilder = CheeseBurgerBuilder()  
var myCheeseBurger = Burger(builder: myCheeseBurgerBuilder)

除了使用协议,创建多个 builder types,我们还可以不使用协议,只用一个 builder type 来实现。首先创建一个 builder type,然后为所有的属性设置默认值,然后一一添加 set 方法,方便随后进行配置修改:

struct BurgerBuilder {  
    var name = "Burger"
    var patties = 1
    var bacon = false
    var cheese = false
    var pickles = true
    var ketchup = true
    var mustard = true
    var lettuce = false
    var tomato = false

    mutating func setPatties(choice: Int) {self.patties = choice}
    mutating func setBacon(choice: Bool) {self.bacon = choice}
    mutating func setCheese(choice: Bool) {self.cheese = choice}
    mutating func setPickles(choice: Bool) {self.pickles = choice}
    mutating func setKetchup(choice: Bool) {self.ketchup = choice}
    mutating func setMustard(choice: Bool) {self.mustard = choice}
    mutating func setLettuce(choice: Bool) {self.lettuce = choice}
    mutating func setTomato(choice: Bool) {self.tomato = choice}

    func buildBurgerOld(name: String) -> BurgerOld {
        return BurgerOld(name: name, patties: self.patties,
            bacon: self.bacon, cheese: self.cheese,
            pickles: self.pickles, ketchup: self.ketchup,
            mustard: self.mustard, lettuce: self.lettuce,
            tomato: self.tomato)
    } 
}

使用起来也就很方便,随时可以修改相关属性

var burgerBuilder = BurgerBuilder()  
burgerBuilder.setCheese(true)  
burgerBuilder.setBacon(true)  
var jonBurger = burgerBuilder.buildBurgerOld("Jon's Burger")

如果你自定义一个类型的初始化方法太繁琐,可以考虑 builder pattern 模式

工厂方法模式(factory method pattern)

工厂方法模式是指在实例化对象时不指定具体类型,而在等到 runtime 阶段才指定。这种模式只暴露接口或基本的类给调用者,并不提供具体的细节。比如很多类型都遵守同一个协议,我们需要在运行时选择一个合适的类型。

protocol Person {  
    var name: String {get}
    var age: Int {get}
}

class Man: Person {  
    static let sharedInstance = Man()
    private init(){}

    let name = "way"
    let age = 18
}

class Woman: Person {  
    static let sharedInstance = Woman()
    private init(){}

    let name = "TuanTuan"
    let age = 17
}

我们可以使用一个工厂方法来返回一个遵守 Person 协议的对象,具体是 Man 还是 Woman 对象,我们可以通过工厂方法的参数进行控制:

func getPeople(isMan: Bool) -> Person {  
    if isMan {
        retrun Man.sharedInstance
    } else {
        return Woman.sharedInstance
    }
}

我们只需要指定相关参数即可,具体的初始化细节交给工厂方法去做

Structural design patterns

结构型模式主要有以下七种,还是详细介绍其中三种

  • Adapter
  • Bridge
  • Composite
  • Decorator
  • Façade
  • Flyweight
  • Proxy

桥接模式(bridge pattern)

桥接模式顾名思义,主要用来解耦,做中间的抽象层。如果有这么两个协议,一个用来存储和准备消息 MessageProtocol ,另一个用来发送消息 SenderProtocol ,后者会在发送消息前进行验证

protocol MessageProtocol {  
    var messageString: String {get set}
    init(messageString: String)
    func prepareMessage()
}

protocol SenderProtocol {  
    var message: MessageProtocol? {get set}
    func sendMessage(message: MessageProtocol)
    func verifyMessage()
}

class PlainTextMessage: MessageProtocol {  
    var messageString: String
    required init(messageString: String) {
        self.messageString = messageString
    }
    func prepareMessage() {
        //  Nothing to do
    }
}

class AESEncryptedMessage: MessageProtocol {  
    var messageString: String
    required init(messageString: String) {
        self.messageString = messageString
    }
    func prepareMessage() {
        self.messageString = "AES: " + self.messageString
    }
}

class EmailSender: SenderProtocol {  
    var message: MessageProtocol?
    func sendMessage() {
        print("Sending through E-Mail:")
        print("     /(message!.messageString)")
    }
    func verifyMessage() {
        print("Verifying E-Mail message")
    }
}

class SMSSender: SenderProtocol {  
    var message: MessageProtocol?
    func sendMessage() {
        print("Sending through SMS:")
        print("     /(message!.messageString)")
    }
    func verifyMessage() {
        print("Verifying SMS message")
    }
}

观察到上面的协议 SenderProtocol 中包含了 MessageProtocol ,如果要手动调用的话需要注意调用的顺序

var message = PlainTextMessage(messageString: "Plain Text Message")  
message.prepareMessage()  
var sender = SMSSender()  
sender.message = message  
sender.verifyMessage()  
sender.sendMessage()

我们可以把内部的交互逻辑封装起来统一放到桥接类型中

struct MessageingBridge {  
    static func sendMessage(message: MessageProtocol, var sender: SenderProtocol) {
        message.prepareMessage()
        sender.message = message
        sender.verifyMessage()
        sender.sendMessage()
    }

}

如此以来调用就变得很简单

var myMessage = PlainTextMessage(messageString: "Plain Text Message")  
var myEncMessage = AESEncryptedMessage(messageString: "Encrypted Message")

MessageingBridge.sendMessage(myMessage, sender: SMSSender())  
MessageingBridge.sendMessage(myMessage, sender: EmailSender())  
MessageingBridge.sendMessage(myEncMessage, sender: EmailSender())

外观模式(Facade Pattern)

如果你有一组 API 要紧密地结合起来完成一项工作,可以考虑使用外观模式,它隐藏了与这些 APIs 进行交互的复杂性,并对外提供了一个简单的接口。

举例有三个 APIs: HotelBooking , FlightBooking , RentalCarBooks (订酒店、订机票、租车)我们创建一个 TravelFacade 结构来集中封装这些 API 的调用。首先构造数据模型和模拟操作

struct Hotel {  
    //Information about hotel room
}

struct HotelBooking {  
    static func getHotelNameForDates(to: NSDate, from: NSDate) -> [Hotel]? {
        let hotels = [Hotel]()
        //logic to get hotels
        return hotels
    }

    static func bookHotel(hotel: Hotel) {
        // logic to reserve hotel room
    }
}

struct Flight {  
    //Information about flights
}

struct FlightBooking {  
    static func getFlightNameForDates(to: NSDate, from: NSDate) -> [Flight]? {
        let flights = [Flight]()
        //logic to get flights
        return flights
    }

    static func bookFlight(fight: Flight) {
        // logic to reserve flight
    }
}


struct RentalCar {  
    //Information about rental cars
}

struct RentalCarBooking {  
    static func getRentalCarNameForDates(to: NSDate, from: NSDate) -> [RentalCar]? {
        let cars = [RentalCar]()
        //logic to get flights
        return cars
    }

    static func bookRentalCar(rentalCar: RentalCar) {
        // logic to reserve rental car
    }
}

接着用 TravelFacade 来封装这些 APIs,今后只需通过 TravelFacade 就可以实现订酒店、订机票、租车的操作

class TravelFacade {

    var hotels: [Hotel]?
    var flights: [Flight]?
    var cars: [RentalCar]?

    init(to: NSDate, from: NSDate) {
        hotels = HotelBooking.getHotelNameForDates(to, from: from)
        flights = FlightBooking.getFlightNameForDates(to, from: from)
        cars = RentalCarBooking.getRentalCarNameForDates(to, from: from)
    }

    func bookTrip(hotel: Hotel, flight: Flight, rentalCar: RentalCar) {
        HotelBooking.bookHotel(hotel)
        FlightBooking.bookFlight(flight)
        RentalCarBooking.bookRentalCar(rentalCar)
    }
}

代理模式(proxy design pattern)

代理模式中,通常一个类型需要扮演接口的角色。比如在 API 和自定义代码间创建一个抽象层。以后即使修改 API 也不需要重构我们的代码,修改抽象层就可以了。还有种可能,我们需要修改 API,但没有代码或没有权限,也只能通过这种方式来搞定。

举例,我们有一栋楼房 House,每层都有不同的房间 FloorPlanProtocol

protocol FloorPlanProtocol {  
    var bedRooms: Int {get set}
    var utilityRooms: Int {get set}
    var bathRooms: Int {get set}
    var kitchen: Int {get set}
    var livingRooms: Int {get set}
}


struct FloorPlan: FloorPlanProtocol {  
    var bedRooms = 0
    var utilityRooms = 0
    var bathRooms = 0
    var kitchen = 0
    var livingRooms = 0
}

class House {  
    var stories = [FloorPlanProtocol]()

    func addStory(floorPlan: FloorPlanProtocol) {
        stories.append(floorPlan)
    }
}

var ourHouse = House()  
var basement = FloorPlan(bedRooms: 0, utilityRooms: 1, bathRooms: 1, kitchen: 0, livingRooms: 1)  
var firstStory = FloorPlan(bedRooms: 1, utilityRooms: 0, bathRooms: 2, kitchen: 1, livingRooms: 1)  
var secondStory = FloorPlan(bedRooms: 2, utilityRooms: 0, bathRooms: 1, kitchen: 0, livingRooms: 1)  
var additionalStory = FloorPlan(bedRooms: 1, utilityRooms: 0, bathRooms: 1, kitchen: 1, livingRooms: 1)

print(ourHouse.addStory(basement))  
print(ourHouse.addStory(firstStory))  
print(ourHouse.addStory(secondStory))  
print(ourHouse.addStory(additionalStory))

但是如果我们想要限制每层楼房的高度,比如三层,这时候除了修改 House,我们还可以将 addStory 逻辑抽离出来封装到一个代理对象中

class HouseProxy {  
    var house = House()

    func addStory(floorPlan: FloorPlanProtocol) -> Bool {
        if house.stories.count < 3 {
            house.addStory(floorPlan)
            return true
        } else {
            return false
        }
    }
}

这样只需要修改 ourHouseHouseProxy 对象即可

var ourHouse = HouseProxy()

代理模式非常适用于当你不能改变类本身的情况下,向类添加一些功能或进行错误检查。

Behavioral design patterns

Behavioral design patterns 主要处理类型之间的交互,有下面几种模式:

  • Chain of responsibility
  • Command
  • Iterator
  • Mediator
  • Memento
  • Observer
  • State
  • Strategy
  • Visitor

同样是挑三种来详细介绍下

command design pattern(命令模式)

将需要执行的命令封装到一个类型中

使用命令模式创建各种开关灯的命令,然后在 Light 类型中使用这些命令,同样是利用协议

protocol Command {  
    func execute()
}

struct RockerSwitchLightOnCommand: Command {  
    func execute() {
        print("Rocker Switch:  Turning Light On")
    }
}

struct RockerSwitchLightOffCommand: Command {  
    func execute() {
        print("ocker Switch:  Turning Light Off")
    }
}

struct PullSwitchLightOnCommand: Command {  
    func execute() {
        print("Pull Switch:  Turning Light On")
    }
}

struct PullSwitchLightOffCommand: Command {  
    func execute() {
        print("Pull Switch:  Turning Light Off")
    }
}

这四条命令都遵守 Command 协议,然后我们就能构造 Light 类使用这些命令

class Light {  
    private var lightOnCommand: Command
    private var lightOffCommand: Command

    init(lightOnCommand: Command, lightOffCommand: Command) {
        self.lightOnCommand = lightOnCommand
        self.lightOffCommand = lightOffCommand
    }

    func turnOnLight() {
        self.lightOnCommand.execute()
    }

    func turnOffLight() {
        self.lightOffCommand.execute()
    }
}

这样在执行时就可以随时切换命令

var on = PullSwitchLightOnCommand()  
var off = PullSwitchLightOffCommand()  
var light = Light(lightOnCommand: on, lightOffCommand: off)

light.turnOnLight()  
light.turnOffLight()

我们把执行命令的细节都提前封装到了具体的命令类型中

策略模式(strategy pattern)

这种模式主要针对算法,它定义了一系列算法,将每一个算法封装起来,并让它们可以相互替换。 策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。

还是来看例子,设计一个压缩算法协议

protocol CompressionStrategy {  
    func compressFiles(filePaths: [String])
}

可以处理 Zip 和 Rar,具体的压缩算法封装在这里

struct ZipCompressionStrategy: CompressionStrategy {  
    func compressFiles(filePaths: [String]) {
        print("Using Zip Compression")
    }
}
struct RarCompressionStrategy: CompressionStrategy {  
    func compressFiles(filePaths: [String]) {
        print("Using RAR Compression")
    }
}

我们接着使用 CompressContent 对算法进行二次封装

class CompressContent {  
    var strategy: CompressionStrategy

    init(strategy: CompressionStrategy) {
        self.strategy = strategy
    }

    func compressFiles(filePaths: [String]) {
        self.strategy.compressFiles(filePaths)
    }
}

这样就能根据实际需求动态地配置压缩算法

var filePaths = ["file1.txt", "file2.txt"]  
var zip = ZipCompressionStrategy()  
var rar = RarCompressionStrategy()

var compress = CompressContent(strategy: zip)  
compress.compressFiles(filePaths)

compress.strategy = rar  
compress.compressFiles(filePaths)

观察者模式(observer pattern)

可以使用 Cocoa 和 Cocoa-Touch 提供的 NSNotificationCenter 来实现。但本章的主题是面向协议编程,我们可以用协议来实现。首先定义一个协议,想要收到通知必须遵守该协议。

protocol ZombieObserverProtocol {  
    func turnLeft()
    func turnRight()
    func seesUs()
}

接着定义观察者

class MyObserver: ZombieObserverProtocol {  
    func turnLeft() {
        print("Zombie turned left, we move right")
    }
    func turnRight() {
        print("Zombie turned right, we move left")
    }
    func seesUs() {
        print("Zombie sees us, RUN!!!!")
    }
}

最后实现 Zombie 类型,当僵尸转向或咬人时,它会给观察者发送通知

struct Zombie {  
    var observer: ZombieObserverProtocol

    func turnZombieLeft() {
        //Zombie turns left
        //Notify observer
        observer.turnLeft()
    }
    func turnZombieRight() {
        //Zombie turns right
        //Notify observer
        observer.turnRight()
    }
    func spotHuman() {
        //Zombie sees us
        //Notify observer
        observer.seesUs()
    }
}

可以测试一下效果

var observer = MyObserver()  
var zombie = Zombie(observer: observer)

zombie.turnZombieLeft()  
zombie.spotHuman()

观察者模式通常用在 UI 发生变化时,如果有多个观察者,我们可以把这些观察者放入一个数组,发生变化时,需要遍历数组去通知这些观察者。所以还是使用 NSNotificationCenter 来的方便,它替我们完成了这些工作。

最后我们来实现一个属性变化的订阅,当属性发生变化时,我们需要收到一个通知。还是先定义一个协议:

protocol PropertyObserverProtocol {  
    func propertyChanged(propertyName: String, newValue: Any)
}

然后定义我们的观察者

class MyObserverType: PropertyObserverProtocol {  
    func propertyChanged(propertyName: String, newValue: Any) {
        print("----changed----")
        print("Property Name: /(propertyName)")
        print("New Value:  /(newValue)")
    }
}

接着定义一个类型,当属性发生变化时通知观察者并传递相关参数

struct PropertyObserver {  
    var observer: PropertyObserverProtocol
    var property1: String {
        didSet{
            observer.propertyChanged("property1", newValue: property1)
        }
        willSet(newValue) {
            print("Property Changing")
        }
    }
}

最后搞几个实例对象验证一下:

var myObserver = MyObserverType()  
var p = PropertyObserver(observer: myObserver, property1: "Initial String")  
p.property1 = "My String"

-EOF-
Protocol-Oriented Programming with Swift 笔记(下)
如果感觉此文对你有帮助,请随意打赏支持作者 :kissing_heart:

原文  https://chengway.in/protocol-oriented-programming-with-swift-bi-ji-2/
正文到此结束
Loading...