The Swift Programming Language学习笔记二十三——协议

协议

协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以采纳协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型“符合”这个协议。

除了采纳协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样采纳协议的类型就能够使用这些功能。

协议语法

使用protocol定义协议。协议的定义方式与类、结构体和枚举的定义非常相似。要让自定义类型采纳某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(:)分隔。采纳多个协议时,各协议之间用逗号(,)分隔。拥有父类的类在采纳协议时,应该将父类名放在协议名之前,以逗号分隔。

class A { }

protocol B { }

protocol C { }

struct  D: B,C { }

class E: A,B,C { }

属性要求

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

如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是只读的,那么该属性不仅可以是只读的,如果代码需要的话,还可以是可写的

协议通常用var关键字来声明变量属性,在类型声明后加上 { set get }来表示属性是可读可写的,只读属性则用{ get }来表示。

在协议中定义类型属性时,总是使用static关键字作为前缀。当类类型采纳协议时,除了static关键字,还可以使用class关键字来声明类型属性。

protocol A {
    var a: Int { get set }
    var b: Int { get }  // 如果协议只要求属性是只读的,那么该属性不仅可以是只读的,如果代码需要的话,还可以是可写的。
}

protocol B {
    static var a: Int { get set }
}

/** * FullyNamed协议除了要求采纳协议的类型提供fullName属性外,并没有其他特别的要求。这个协议表示,任何采纳FullyNamed的类型,都必须有一个只读的String类型的实例属性fullName。 */
protocol FullyNamed {
    var fullName: String { get }
}

struct Person: FullyNamed {
    var fullName: String    // Person结构体的每一个实例都有一个String类型的存储型属性fullName。这正好满足了FullyNamed协议的要求,也就意味着Person结构体正确地符合了协议。
}

let p = Person(fullName: "Tim")

class Starship: FullyNamed {
    var prefix: String?
    var name: String
    init(name: String,prefix: String? = nil) {
        self.name = name
        self.prefix = prefix
    }

    var fullName: String {  // Starship类把fullName属性实现为只读的计算型属性。
        return (prefix != nil ? prefix! + " " : "") + name
    }
}

let s = Starship(name: "Enterprise",prefix: "USS")
print(s.fullName)

方法要求

协议可以要求采纳协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认值

正如属性要求中所述,在协议中定义类方法的时候,总是使用static关键字作为前缀。当类类型采纳协议时,除了static关键字,还可以使用class关键字作为前缀。

protocol A {
    static func a()
}

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,1.0)区间内。
}

/** * 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法 */
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = (lastRandom * a + c) % m
        return lastRandom / m
    }
}

let generator = LinearCongruentialGenerator()
print(generator.random())
print(generator.random())
print(generator.random())
/* 0.37464991998171 0.729023776863283 0.636466906721536 */

在上面的代码中,实现了“线性同余生成器(linear congruential generator)”的伪随机数算法

mutating方法要求

有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将mutating关键字作为方法的前缀,写在func关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值

如果你在协议中定义了一个实例方法,该方法会改变采纳该协议的类型的实例,那么在定义协议时需要在方法前加mutating关键字。这使得结构体和枚举能够采纳此协议并满足此方法要求。

注意,实现协议中的mutating方法时,若是类类型,则不用写mutating关键字。而对于结构体和枚举,则必须写mutating关键字。

protocol Togglable {
    mutating func toggle()
}

enum OnOffSwitch: Togglable {
    case On,Off
    mutating func toggle() {    // 当使用枚举或结构体来实现Togglable协议时,需要提供一个带有mutating前缀的toggle() 方法。
        switch self {
        case .On:
            self = .Off
        case .Off:
            self = .On
        }
    }
}

var o = OnOffSwitch.On
o.toggle()
print(o)

构造器要求

协议可以要求采纳协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体。

构造器要求在类中的实现

你可以在采纳协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上required修饰符。使用required修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。

如果类已经被标记为final,那么不需要在协议构造器的实现中使用required修饰符,因为final类不能有子类。

如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注requiredoverride修饰符。

protocol A {
    init()
}

class B {
    init() {
        print("B.init() called")
    }
}

class C: B,A {
    required override init() {
        print("C.init() called")
    }
}

可失败构造器要求

协议还可以为采纳协议的类型定义可失败构造器要求。采纳协议的类型可以通过可失败构造器(init?)或非可失败构造器(init来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(init)或隐式解包可失败构造器(init!来满足。

协议作为类型

尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。

协议可以像其他普通类型一样使用,使用场景如下:

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

注意,协议是一种类型,因此协议类型的名称应与其他类型(例如IntDoubleString)的写法相同,使用大写字母开头的驼峰式写法,例如(FullyNamedRandomNumberGenerator)。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,1.0)区间内。
}

/** * 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法 */
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = (lastRandom * a + c) % m
        return lastRandom / m
    }
}

/** * 一个使用了线性同余生成器(linear congruential generator) 的伪随机数算法的骰子。 */
class Dice {
    let sides: Int
    let generator: RandomNumberGenerator
    init(sides: Int,generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -> Int {
        return Int(generator.random() * Double(sides)) + 1
    }
}

let d = Dice(sides: 6,generator: LinearCongruentialGenerator())
for _ in 0..<10 {
    print(d.roll())
}
/* 3 5 4 5 4 1 4 2 1 4 */

委托(代理)模式

委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保采纳协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -> Int {
        return Int(generator.random() * Double(sides)) + 1
    }
}

// 两个基于骰子游戏的协议

/** * DiceGame协议可以被任意涉及骰子的游戏采纳。 */
protocol DiceGame {
    var dice: Dice { get }
    func play()
}

/** * DiceGameDelegate协议可以被任意类型采纳,用来追踪DiceGame的游戏过程。 */
protocol DiceGameDelegate {
    func gameDidStart(game: DiceGame)
    func game(game: DiceGame,didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(game: DiceGame)
}

/** * 蛇梯棋游戏的新版本。新版本使用Dice实例作为骰子,并且实现了DiceGame和DiceGameDelegate协议,后者用来记录游戏的过程。 */
class SnakeAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6,generator: LinearCongruentialGenerator())     // dice属性在构造之后就不再改变,且协议只要求dice为只读的,因此将dice声明为常量属性。
    var square = 0
    var board: [Int]
    init() {
        board = [Int](count: finalSquare + 1,repeatedValue: 0)
        board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
        board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
    }
    var delegate: DiceGameDelegate?     // delegate并不是游戏的必备条件,设置为可选
    func play() {
        square = 0
        delegate?.gameDidStart(self)    // 通过可选链式调用来调用它的方法
        gameLoop: while square != finalSquare {
            let diceRoll = dice.roll()
            delegate?.game(self,didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

class DiceGameTracker: DiceGameDelegate {
    var numberOfTurns = 0
    func gameDidStart(game: DiceGame) {     // game参数是DiceGame类型而不是SnakeAndLadders类型,所以在方法中只能访问DiceGame协议中的内容。
        numberOfTurns = 0
        if game is SnakeAndLadders {
            print("Started a new game of Snakes and Ladders")
        }
        print("The game is using a \(game.dice.sides)-sided dice")
    }
    func game(game: DiceGame,didStartNewTurnWithDiceRoll diceRoll: Int) {
        ++numberOfTurns
        print("Rolled a \(diceRoll)")
    }
    func gameDidEnd(game: DiceGame) {
        print("The game lasted for \(numberOfTurns) turns")
    }
}

let game = SnakeAndLadders()
let tracker = DiceGameTracker()
game.delegate = tracker
game.play()
/*伪随机数,每次运行结果是一样的 Started a new game of Snakes and Ladders The game is using a 6-sided dice Rolled a 3 Rolled a 5 Rolled a 4 Rolled a 5 The game lasted for 4 turns */

通过扩展添加协议一致性

即便无法修改源代码,依然可以通过扩展令已有类型采纳并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。

通过扩展令已有类型采纳并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。

通过扩展采纳并符合协议,和在原始定义中采纳并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

/** * 任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述 */
protocol TextRepresentable {
    var textualDescription: String { get }
}

/** * 可以通过扩展,令先前提到的Dice类采纳并符合TextRepresentable协议 */
extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

let d = Dice(sides: 12,generator: LinearCongruentialGenerator())
print(d.textualDescription)     // A 12-sided dice

extension SnakeAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}

let s = SnakeAndLadders()
print(s.textualDescription)     // A game of Snakes and Ladders with 25 squares

通过扩展采纳协议

当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空扩展体的扩展来采纳该协议

即使满足了协议的所有要求,类型也不会自动采纳协议,必须显式地采纳协议

protocol TextRepresentable {
    var textualDescription: String { get }
}

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}

extension Hamster: TextRepresentable {}

// 现在Hamster实例可以作为TextRepresentable类型使用了!
let h = Hamster(name: "Tim")
let t: TextRepresentable = h
print(t.textualDescription)     // A hamster named Tim

协议类型的集合

协议类型可以在数组或者字典这样的集合中使用。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

/** * 任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述 */
protocol TextRepresentable {
    var textualDescription: String { get }
}

/** * 可以通过扩展,令先前提到的Dice类采纳并符合TextRepresentable协议 */
extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

extension SnakeAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}

extension Hamster: TextRepresentable {}

let s = SnakeAndLadders()
let d = Dice(sides: 6,generator: LinearCongruentialGenerator())
let h = Hamster(name: "Tim")
let ts: [TextRepresentable] = [s,h,d]     // 必须显式声明类型!?否则报出现歧义的错误
for item in ts {
    print(item.textualDescription)  // 任何TextRepresentable的实例都有一个textualDescription属性,所以在每次循环中可以安全地访问textualDescription属性
}
/* A game of Snakes and Ladders with 25 squares A hamster named Tim A 6-sided dice */

协议的继承

协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

protocol TextRepresentable {
    var textualDescription: String { get }
}

extension SnakeAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}

protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}

extension SnakeAndLadders: PrettyTextRepresentable {
    var prettyTextualDescription: String {
        var output = self.textualDescription + ":\n"
        for i in 1...finalSquare {
            switch board[i] {
            case let ladder where ladder > 0:
                output += "▲、"
            case let ladder where ladder < 0:
                output += "▼、"
            default:
                output += "○、"
            }
        }
        return output
    }
}

let s = SnakeAndLadders()
print(s.prettyTextualDescription)
/* A game of Snakes and Ladders with 25 squares: ○、○、▲、○、○、▲、○、○、▲、▲、○、○、○、▼、○、○、○、○、▼、○、○、▼、○、▼、○、 */

类类型专属协议

可以在协议的继承列表中,通过添加class关键字来限制协议只能被类类型采纳,而结构体或枚举不能采纳该协议class关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前。

当协议定义的要求需要采纳协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。

protocol A {

}

protocol B: class,A {

}

协议合成

有时候需要同时采纳多个协议,你可以将多个协议采用protocol<SomeProtocol,AnotherProtocol>这样的格式进行组合,称为协议合成(protocol composition)。你可以在<>中罗列任意多个你想要采纳的协议,以逗号分隔。

协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中

protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

struct Person: Named,Aged {
    var name: String
    var age: Int
}

func wishHappyBirthday(celebrator: protocol<Named,Aged>) {     // 不关心参数的具体类型,只要参数符合这两个协议即可
    print("Happy birthday \(celebrator.name) -- you are \(celebrator.age)")
}

let p = Person(name: "TIm",age: 23)
wishHappyBirthday(p)    // Happy birthday TIm -- you are 23

检查协议一致性

可以使用类型转换中描述的isas操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查转换到某个协议类型在语法上和类型的检查和转换完全相同:

  • is用来检查实例是否符合某个协议,若符合则返回true,否则返回false
  • as?返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回nil
  • as!将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误
protocol HasArea {
    var area: Double { get }
}

class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { return radius * radius }     // 计算型属性
    init(radius: Double) { self.radius = radius }
}

class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }         // 存储型属性
}

class Animal{
    var legs: Int
    init(legs: Int) { self.legs = legs }
}

let objects: [AnyObject] = [
    Circle(radius: 3.2),Country(area: 123456.0),Animal(legs: 4)
]

for o in objects {
    if let oo = o as? HasArea {
        print(oo.area)
    } else {
        print("area没有值")
    }
}

可选的协议要求

协议可以定义可选要求,采纳协议的类型可以选择是否实现这些要求。在协议中使用optional关键字作为前缀来定义可选要求。使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为(Int) -> String的方法会变成((Int) -> String)?。需要注意的是整个函数类型是可选的,而不是函数的返回值。

协议中的可选要求可通过可选链式调用来使用,因为采纳协议的类型可能没有实现这些可选要求。类似someOptionalMethod?(someArgument)这样,你可以在可选方法名称后加上?来调用可选方法。

注意,可选的协议要求只能用在标记@objc特性的协议中。该特性表示协议将暴露给Objective-C代码,详情参见Using Swift with Cocoa and Objective-C(Swift 2.1)。即使你不打算和Objective-C有什么交互,如果你想要指定可选的协议要求,那么还是要为协议加上@obj特性。

还需要注意的是,标记@objc特性的协议只能被继承自Objective-C类的类或者@objc类采纳,其他类以及结构体和枚举均不能采纳这种协议。

import Foundation   // @objc需要导入Foundation框架
@objc protocol CounterDataSource {
    optional func incrementForCount(count: Int) -> Int  // 可选的函数类型:(Int -> Int)?
    optional var fixedIncrement: Int { get }            // 可选的Int:Int?
}

class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.incrementForCount?(count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}

class ThreeSource: NSObject,CounterDataSource {
    let  fixedIncrement = 3
}

let counter = Counter()
counter.dataSource = ThreeSource()
for _ in 0..<3 {
    counter.increment()
    print(counter.count)
}
/* 3 6 9 */

print("")

@objc class TowardsZeroSource: NSObject,CounterDataSource {
    func incrementForCount(count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}

let a = Counter()
a.count = -4
a.dataSource = TowardsZeroSource()
for _ in 0..<5 {
    a.increment()
    print(a.count)
}
/* -3 -2 -1 0 0 */

严格来讲,CounterDataSource协议中的方法和属性都是可选的,因此采纳协议的类可以不实现这些要求,尽管技术上允许这样做,不过最好不要这样写。

协议扩展

协议可以通过扩展来为采纳协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个采纳协议的类型中都重复同样的实现,也无需使用全局函数。

protocol RandomNumberGenerator {
    func random() -> Double
}

/** * 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法 */
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = (lastRandom * a + c) % m
        return lastRandom / m
    }
}

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}



let l = LinearCongruentialGenerator()
print(l.random())       // 0.37464991998171
print(l.randomBool())   // true,即使扩展是在LinearCongruentialGenerator定义之后也可以正常调用

提供默认实现

可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果采纳协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。

注意,通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,采纳协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。

protocol TextRepresentable {
    var textualDescription: String { get }
}

protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}

extension PrettyTextRepresentable {
    var prettyTextualDescription: String {
        return textualDescription   // 默认的prettyTextualDescription属性,只是简单地返回textualDescription属性的值
    }
}

为协议扩展添加限制条件

在扩展协议的时候,可以指定一些限制条件,只有采纳协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用where子句来描述。

如果多个协议扩展都为同一个协议要求提供了默认实现,而采纳协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。

protocol TextRepresentable {
    var textualDescription: String { get }
}

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}

extension Hamster: TextRepresentable {}

/** * 扩展CollectionType协议,但是只适用于集合中的元素采纳了TextRepresentable协议的情况 */
extension CollectionType where Generator.Element: TextRepresentable {
    var textualDescription: String {
        let itemAsText = self.map { $0.textualDescription }
        return "[" + itemAsText.joinWithSeparator(",") + "]"
    }
}

let tim = Hamster(name: "Tim")
let kate = Hamster(name: "Kate")
let jack = Hamster(name: "Jack")
let hs = [tim,kate,jack] print(hs.textualDescription) // [A hamster named Tim,A hamster named Kate,A hamster named Jack],Array符合CollectionType协议,而数组中的元素又符合TextRepresentable协议

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


软件简介:蓝湖辅助工具,减少移动端开发中控件属性的复制和粘贴.待开发的功能:1.支持自动生成约束2.开发设置页面3.做一个浏览器插件,支持不需要下载整个工程,可即时操作当前蓝湖浏览页面4.支持Flutter语言模板生成5.支持更多平台,如Sketch等6.支持用户自定义语言模板
现实生活中,我们听到的声音都是时间连续的,我们称为这种信号叫模拟信号。模拟信号需要进行数字化以后才能在计算机中使用。目前我们在计算机上进行音频播放都需要依赖于音频文件。那么音频文件如何生成的呢?音频文件的生成过程是将声音信息采样、量化和编码产生的数字信号的过程,我们人耳所能听到的声音频率范围为(20Hz~20KHz),因此音频文件格式的最大带宽是20KHZ。根据奈奎斯特的理论,音频文件的采样率一般在40~50KHZ之间。奈奎斯特采样定律,又称香农采样定律。...............
前言最近在B站上看到一个漂亮的仙女姐姐跳舞视频,循环看了亿遍又亿遍,久久不能离开!看着小仙紫姐姐的蹦迪视频,除了一键三连还能做什么?突发奇想,能不能把舞蹈视频转成代码舞呢?说干就干,今天就手把手教大家如何把跳舞视频转成代码舞,跟着仙女姐姐一起蹦起来~视频来源:【紫颜】见过仙女蹦迪吗 【千盏】一、核心功能设计总体来说,我们需要分为以下几步完成:从B站上把小姐姐的视频下载下来对视频进行截取GIF,把截取的GIF通过ASCII Animator进行ASCII字符转换把转换的字符gif根据每
【Android App】实战项目之仿抖音的短视频分享App(附源码和演示视频 超详细必看)
前言这一篇博客应该是我花时间最多的一次了,从2022年1月底至2022年4月底。我已经将这篇博客的内容写为论文,上传至arxiv:https://arxiv.org/pdf/2204.10160.pdf欢迎大家指出我论文中的问题,特别是语法与用词问题在github上,我也上传了完整的项目:https://github.com/Whiffe/Custom-ava-dataset_Custom-Spatio-Temporally-Action-Video-Dataset关于自定义ava数据集,也是后台
因为我既对接过session、cookie,也对接过JWT,今年因为工作需要也对接了gtoken的2个版本,对这方面的理解还算深入。尤其是看到官方文档评论区又小伙伴表示看不懂,所以做了这期视频内容出来:视频在这里:本期内容对应B站的开源视频因为涉及的知识点比较多,视频内容比较长。如果你觉得看视频浪费时间,可以直接阅读源码:goframe v2版本集成gtokengoframe v1版本集成gtokengoframe v2版本集成jwtgoframe v2版本session登录官方调用示例文档jwt和sess
【Android App】实战项目之仿微信的私信和群聊App(附源码和演示视频 超详细必看)
用Android Studio的VideoView组件实现简单的本地视频播放器。本文将讲解如何使用Android视频播放器VideoView组件来播放本地视频和网络视频,实现起来还是比较简单的。VideoView组件的作用与ImageView类似,只是ImageView用于显示图片,VideoView用于播放视频。...
采用MATLAB对正弦信号,语音信号进行生成、采样和内插恢复,利用MATLAB工具箱对混杂噪声的音频信号进行滤波
随着移动互联网、云端存储等技术的快速发展,包含丰富信息的音频数据呈现几何级速率增长。这些海量数据在为人工分析带来困难的同时,也为音频认知、创新学习研究提供了数据基础。在本节中,我们通过构建生成模型来生成音频序列文件,从而进一步加深对序列数据处理问题的了解。
基于yolov5+deepsort+slowfast算法的视频实时行为检测。1. yolov5实现目标检测,确定目标坐标 2. deepsort实现目标跟踪,持续标注目标坐标 3. slowfast实现动作识别,并给出置信率 4. 用框持续框住目标,并将动作类别以及置信度显示在框上
数字电子钟设计本文主要完成数字电子钟的以下功能1、计时功能(24小时)2、秒表功能(一个按键实现开始暂停,另一个按键实现清零功能)3、闹钟功能(设置闹钟以及到时响10秒)4、校时功能5、其他功能(清零、加速、星期、八位数码管显示等)前排提示:前面几篇文章介绍过的内容就不详细介绍了,可以看我专栏的前几篇文章。PS.工程文件放在最后面总体设计本次设计主要是在前一篇文章 数字电子钟基本功能的实现 的基础上改编而成的,主要结构不变,分频器将50MHz分为较低的频率备用;dig_select
1.进入官网下载OBS stdioOpen Broadcaster Software | OBS (obsproject.com)2.下载一个插件,拓展OBS的虚拟摄像头功能链接:OBS 虚拟摄像头插件.zip_免费高速下载|百度网盘-分享无限制 (baidu.com)提取码:6656--来自百度网盘超级会员V1的分享**注意**该插件必须下载但OBS的根目录(应该是自动匹配了的)3.打开OBS,选中虚拟摄像头选择启用在底部添加一段视频录制选择下面,进行录制.
Meta公司在9月29日首次推出一款人工智能系统模型:Make-A-Video,可以从给定的文字提示生成短视频。基于**文本到图像生成技术的最新进展**,该技术旨在实现文本到视频的生成,可以仅用几个单词或几行文本生成异想天开、独一无二的视频,将无限的想象力带入生活
音频信号叠加噪声及滤波一、前言二、信号分析及加噪三、滤波去噪四、总结一、前言之前一直对硬件上的内容比较关注,但是可能是因为硬件方面的东西可能真的是比较杂,而且需要渗透的东西太多了,所以学习进展比较缓慢。因为也很少有单纯的硬件学习研究,总是会伴随着各种理论需要硬件做支撑,所以还是想要慢慢接触理论学习。但是之前总找不到切入点,不知道从哪里开始,就一直拖着。最近稍微接触了一点信号处理,就用这个当作切入点,开始接触理论学习。二、信号分析及加噪信号处理选用了matlab做工具,选了一个最简单的语音信号处理方
腾讯云 TRTC 实时音视频服务体验,从认识 TRTC 到 TRTC 的开发实践,Demo 演示& IM 服务搭建。
音乐音频分类技术能够基于音乐内容为音乐添加类别标签,在音乐资源的高效组织、检索和推荐等相关方面的研究和应用具有重要意义。传统的音乐分类方法大量使用了人工设计的声学特征,特征的设计需要音乐领域的知识,不同分类任务的特征往往并不通用。深度学习的出现给更好地解决音乐分类问题提供了新的思路,本文对基于深度学习的音乐音频分类方法进行了研究。首先将音乐的音频信号转换成声谱作为统一表示,避免了手工选取特征存在的问题,然后基于一维卷积构建了一种音乐分类模型。
C++知识精讲16 | 井字棋游戏(配资源+视频)【赋源码,双人对战】
本文主要讲解如何在Java中,使用FFmpeg进行视频的帧读取,并最终合并成Gif动态图。
在本篇博文中,我们谈及了 Swift 中 some、any 关键字以及主关联类型(primary associated types)的前世今生,并由浅及深用简明的示例向大家讲解了它们之间的奥秘玄机。