19Nested Types
枚举类型经常被创建类支持某个类或某个结构体的功能。同样的,可以在一个复杂的类型内部方便的定义实用的类和结构体类使用。为了完成这个功能,swift中可以定义内嵌类型,你可以在类型内部定义枚举,类,结构体。
19.1Nested Types in Action
下面这个例子定义BlackjackCard结构体,这是一种纸牌游戏。Ace可以是1,也可以是11。
struct BlackjackCard {
// nested Suit enumeration
enum Suit: Character {
case Spades = "♠", Hearts ="♡", Diamonds = "♢", Clubs ="♣"
}
// nested Rank enumeration
enum Rank: Int {
case Two = 2, Three, Four, Five, Six, Seven, Eight, Nine, Ten
case Jack, Queen, King, Ace
struct Values {
let first: Int, second:Int?
}
var values: Values {
switch self {
case .Ace:
return Values(first:1, second: 11)
case .Jack, .Queen, .King:
return Values(first:10, second: nil)
default:
return Values(first:self.rawValue, second:nil)
}
}
}
// BlackjackCard properties and methods
let rank: Rank, suit:Suit
var description: String {
var output = "suit is\(suit.rawValue),"
output +=" value is \(rank.values.first)"
if let second =rank.values.second {
output +=" or \(second)"
}
return output
}
}
let theAceOfSpades =BlackjackCard(rank: .Ace, suit: .Spades)
println("theAceOfSpades:\(theAceOfSpades.description)")
// prints "theAceOfSpades: suit is ♠, value is 1 or 11"
19.2Referring to Nested Types
使用内嵌类型
let heartsSymbol =BlackjackCard.Suit.Hearts.rawValue
// heartsSymbol is "♡"
20Extensions
Extensions增加新功能到已有类,结构体,或枚举类型。即使你没有这些类,结构体,枚举的源代码。
Extensions are similar to categories in Objective-C. (Unlike Objective-C categories, Swift extensions do not have names.)
Extension(扩展)的功能:
1)增加计算属性和静态计算属性
2)定义实例方法和类方法
3)提供新的构造器
4)定义下标方法
5)定义和使用新的内嵌类型
6)实现某个协议
注意:扩展可以增加新的函数,但不能覆写已有函数
20.1Extension Syntax
extension SomeType {
// new functionality to add to SomeType goes here
}
extension SomeType:SomeProtocol, AnotherProtocol {
// implementation of protocol requirements goes here
}
注意:一旦定义扩张来增加功能到已有类,这些功能将在所有已有实例中生效,即使它创建时这个扩展还没定义。
20.2Computed Properties
扩展可增加成员计算属性和类型计算属性
extension Double {
var km: Double {return self *1_000.0 }
var m: Double {return self }
var cm: Double {return self /100.0 }
var mm: Double {return self /1_000.0 }
var ft: Double {return self /3.28084 }
}
let oneInch =25.4.mm
println("One inch is\(oneInch) meters")
// prints "One inch is 0.0254 meters"
let threeFeet =3.ft
println("Three feet is\(threeFeet) meters")
// prints "Three feet is 0.914399970739201 meters"
let aMarathon =42.km + 195.m
println("A marathon is\(aMarathon) meters long")
// prints "A marathon is 42195.0 meters long"
注意:扩展是不能增加存储属性的,也不能给已有的属性增加观察者
20.3Initializers
扩展可以增加新的构造器。这是你能够增加自定义构造函数参数。
扩展可以增加便捷构造器,但不能增加设定构造器或析构器。设定构造器和析构器必须在原始类实现那边定义。
//自定义Rect结构体
//定义两个辅助的结构体Size和Point,他们的属性都有默认值
struct Size {
var width = 0.0, height =0.0
}
struct Point {
var x = 0.0, y =0.0
}
struct Rect {
var origin = Point()
var size = Size()
}
//因为Rect结构体的所有属性都有默认值,所以自动有默认构造器和按成员顺序逐个初始化的构造器
let defaultRect =Rect()
let memberwiseRect =Rect(origin: Point(x:2.0, y: 2.0),
size:Size(width: 5.0, height:5.0))
//扩展Rect结构体,增加构造器
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)
}
}
let centerRect =Rect(center: Point(x:4.0, y: 4.0),
size:Size(width: 3.0, height:3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)
20.4Methods
扩展可增加成员方法和类方法。
//下面这个例子在Int类型上增加成员方法repetitions
extension Int {
func repetitions(task: () -> ()) {
for i in 0..<self {
task()
}
}
}
//repetitions接收一个参数,参数类型是()->(),一个没有入参没有返回值的函数
//试一把
3.repetitions({
println("Hello!")
})
// Hello!
// Hello!
// Hello!
//使用trailing closure语法看起来更简单
3.repetitions {
println("Goodbye!")
}
// Goodbye!
// Goodbye!
// Goodbye!
20.4.1Mutating Instance Methods
扩展所增加的方法也可以修改实例本身。
结构体和枚举方法修改实例或属性需要标注mutating,
extension Int {
mutating func square() {
self = self *self
}
}
var someInt =3
someInt.square()
// someInt is now 9
20.5Subscripts
扩展可以增加新的下标函数。
下面例子为整形增加下标[n],返回右数第n位数据。
- 123456789[0]返回9
- 123456789[1]返回8
extension Int {
subscript(var digitIndex:Int) -> Int {
var decimalBase = 1
while digitIndex > 0 {
decimalBase *=10
--digitIndex
}
return (self / decimalBase) %10
}
}
746381295[0]
// returns 5
746381295[1]
// returns 9
746381295[2]
// returns 2
746381295[8]
// returns 7
746381295[9]
// returns 0, as if you had requested:
0746381295[9]
20.6Nested Types
扩展可增加内嵌类型
extension Int {
enum Kind {
case Negative, Zero, Positive
}
var kind: Kind {
switch self {
case 0:
return .Zero
case let x where x > 0:
return .Positive
default:
return .Negative
}
}
}
func printIntegerKinds(numbers: [Int]) {
for number in numbers {
switch number.kind {
case .Negative:
print("- ")
case .Zero:
print("0 ")
case .Positive:
print("+ ")
}
}
print("\n")
}
printIntegerKinds([3,19, -27, 0, -6, 0,7])
// prints "+ + - 0 - 0 +"
21Protocols
协议定义某项任务或功能所需的方法,属性和其他要求的蓝图。
协议不实际提供实现,只描述实现应该是怎么样的。
协议可以被类,结构体,枚举适配。
任何满足协议要求的类型被称为遵从那个协议conform to that protocol.
协议可以要求适配类型有某个成员属性,成员方法,类方法,操作符,和下标。
21.1Protocol Syntax
定义协议
protocol SomeProtocol {
// protocol definition goes here
}
自定义类型遵循某个协议用冒号隔开,多个协议用逗号隔开
struct SomeStructure:FirstProtocol, AnotherProtocol {
// structure definition goes here
}
class SomeClass: SomeSuperclass, FirstProtocol,AnotherProtocol {
// class definition goes here
}
21.2Property Requirements
协议可以要求适配类型提供某个实例属性或类属性。
协议不能指定属性必须是存储属性或是计算属性,只能指定该属性的名称和类型。
协议同时可以指定属性是只读的或是可读可写的。
如果协议要求属性可读可写,那么适配类这个属性就不能是常量存储属性或只读计算属性。
如果协议只要求属性可读,那么适配类这个属性可以是只读的,也可以是可读可写的。
属性要求都是变量,通过get,set指明可读可写性
protocol SomeProtocol {
var mustBeSettable: Int { get set }
var doesNotNeedToBeSettable: Int { get }
}
如果是类属性,则在前面加class关键字,计算这个协议后面被结构体或枚举适配,适配时使用static,这边仍然写class
protocol AnotherProtocol {
class var someTypeProperty:Int { get set }
}
来个例子吧
protocol FullyNamed {
var fullName: String {get }
}
struct Person:FullyNamed {
var fullName: String
}
let john =Person(fullName: "John Appleseed")
// john.fullName is "John Appleseed"
21.3Method Requirements
协议可以要求适配类型有特定的成员方法或类方法。
方法的定义与一般类完全相同,可变参数也是可以的
注意:虽然语法相同,但协议里面不能指定参数的默认值。
与属性那边相同,如果是类方法,那么在前面加class,不管适配类型是类还是结构体,枚举。
protocol SomeProtocol {
class func someTypeMethod()
}
//一个获取随机数的例子
protocol RandomNumberGenerator {
func random() -> Double
}
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()
println("Here's a random number:\(generator.random())")
// prints "Here's a random number: 0.37464991998171"
println("And another one:\(generator.random())")
// prints "And another one: 0.729023776863283"
21.4Mutating Method Requirements
有时候,方法有修改实例的必要。
对于值类型的成员方法,我们在方法前加mutating关键字来指明方法可以修改实例或实例的成员变量。
如果你定义的协议成员方法会修改到实例,那么在方法前面注明mutating
注意:虽然协议注明mutating,但如果是类来实现协议,则不需要写mutating,只有是结构体和枚举类型实现协议时才需要写mutating。
protocol Togglable {
mutating func toggle()
}
enum OnOffSwitch:Togglable {
case Off, On
mutating func toggle() {
switch self {
case Off:
self = On
case On:
self = Off
}
}
}
var lightSwitch =OnOffSwitch.Off
lightSwitch.toggle()
// lightSwitch is now equal to .On
21.5Initializer Requirements
协议可以要求实现特定的构造器
protocol SomeProtocol {
init(someParameter: Int)
}
21.5.1Class Implementations of Protocol Initializer Requirements
实现协议的构造器要求,可以是设定构造器,也可以是便捷构造器。不管哪种情况,都必须在构造器前加required修饰符:
class SomeClass:SomeProtocol {
required init(someParameter:Int) {
// initializer implementation goes here
}
}
The use of the required modifier ensures that you provide an explicit or inherited implementation of the initializer requirement on all subclasses of the conforming class, such that they also conform to the protocol.
required修饰符确保你在适配类的所有子类上都要求显示的或继承的实现那个构造器的要求,以便他们也都是适配类
注意:在final类上就不需要标注required了。
如果一个子类覆写了设定构造器,并实现了协议上的构造器要求,那么这个构造器请标注required 和 override 两个修饰符:
protocol SomeProtocol {
init()
}
class SomeSuperClass {
init() {
// initializer implementation goes here
}
}
class SomeSubClass:SomeSuperClass, SomeProtocol {
// "required" from SomeProtocol conformance; "override" from SomeSuperClass
required override init() {
// initializer implementation goes here
}
}
21.5.2Failable Initializer Requirements
协议可定义可失败构造器。
适配类可用是失败构造器或非可失败构造器来实现其要求。
21.6Protocols as Types
虽然协议没有真正实现任何功能。但是,协议是一个完整的类型。
许多其他类型可以使用的地方,用协议也可以:
1)作为函数,方法,或构造器的参数类型或返回值类型
2)作为常量变量或属性的类型
3)作为数组,字典或其他容器的子项类型
//骰子
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
}
}
var d6 =Dice(sides: 6, generator: LinearCongruentialGenerator())
for_ in 1...5 {
println("Random dice roll is\(d6.roll())")
}
// Random dice roll is 3
// Random dice roll is 5
// Random dice roll is 4
// Random dice roll is 5
// Random dice roll is 4
21.7Delegation
代理是一种设计模式,它使得一个类或结构所该处理的一些事情能交给另一个类。
这种设计模式通过定义协议来封装代理责任来实现。
代理可以用来响应某个特殊的事件,或是获取外部资源
//下面例子定义两个协议,以便一个骰子游戏用
protocol DiceGame {
var dice: Dice {get }
func play()
}
protocol DiceGameDelegate {
func gameDidStart(game: DiceGame)
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll:Int)
func gameDidEnd(game: DiceGame)
}
//下面是蛇与梯子的游戏,实现了骰子游戏协议
class SnakesAndLadders:DiceGame {
let finalSquare = 25
let dice = Dice(sides:6, generator: LinearCongruentialGenerator())
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?
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 newSquarewhere newSquare > finalSquare:
continue gameLoop
default:
square += diceRoll
square += board[square]
}
}
delegate?.gameDidEnd(self)
}
}
//骰子游戏追踪者
class DiceGameTracker:DiceGameDelegate {
var numberOfTurns = 0
func gameDidStart(game: DiceGame) {
numberOfTurns =0
if game is SnakesAndLadders {
println("Started a new game of Snakes and Ladders")
}
println("The game is using a\(game.dice.sides)-sided dice")
}
func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll:Int) {
++numberOfTurns
println("Rolled a \(diceRoll)")
}
func gameDidEnd(game: DiceGame) {
println("The game lasted for\(numberOfTurns) turns")
}
}
//游戏一下
let tracker = DiceGameTracker()
let game = SnakesAndLadders()
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
21.8Adding Protocol Conformance with an Extension
即使没有源代码,你也可以扩展一个类使其适配新的协议。
扩展可以增加属性,方法,下标,因此可以满足协议的任何要求。
//可文本化描述的,类似description
protocol TextRepresentable {
func asText() -> String
}
//扩展Dice使其适配这个协议
extension Dice:TextRepresentable {
func asText() -> String {
return"A \(sides)-sided dice"
}
}
let d12 =Dice(sides: 12, generator:LinearCongruentialGenerator())
println(d12.asText())
// prints "A 12-sided dice"
//扩展蛇和梯的游戏使其适配这个协议
extension SnakesAndLadders:TextRepresentable {
func asText() -> String {
return"A game of Snakes and Ladders with \(finalSquare) squares"
}
}
println(game.asText())
// prints "A game of Snakes and Ladders with 25 squares"
21.8.1Declaring Protocol Adoption with an Extension
如果一个类型已经实现了协议的要求,但还没有标注适配这个协议,那么你可以用空扩展来标注一下。
struct Hamster {
var name: String
func asText() -> String {
return"A hamster named \(name)"
}
}
//标注一下
extension Hamster:TextRepresentable {}
let simonTheHamster =Hamster(name: "Simon")
let somethingTextRepresentable:TextRepresentable = simonTheHamster
println(somethingTextRepresentable.asText())
// prints "A hamster named Simon"
注意:虽然类型已经符合协议,但不会自动适配,你必须手动声明它适配。
21.9Collections of Protocol Types
协议类型的集合
let things: [TextRepresentable] = [game,d12, simonTheHamster]
for thingin things {
println(thing.asText())
}
// A game of Snakes and Ladders with 25 squares
// A 12-sided dice
// A hamster named Simon
21.10Protocol Inheritance
协议可以继承一个或多个协议,并增加新的要求,多个协议用逗号隔开,如下:
protocol InheritingProtocol:SomeProtocol, AnotherProtocol {
// protocol definition goes here
}
来个例子吧
protocol PrettyTextRepresentable:TextRepresentable {
func asPrettyText() -> String
}
extension SnakesAndLadders:PrettyTextRepresentable {
func asPrettyText() -> String {
var output = asText() +":\n"
for index in1...finalSquare {
switch board[index] {
case let ladderwhere ladder > 0:
output +="▲ "
case let snakewhere snake < 0:
output +="▼ "
default:
output +="○ "
}
}
return output
}
}
println(game.asPrettyText())
// A game of Snakes and Ladders with 25 squares:
// ○ ○ ▲ ○ ○ ▲ ○ ○ ▲ ▲ ○ ○ ○ ▼ ○ ○ ○ ○ ▼ ○ ○ ▼ ○ ▼ ○
21.11Class-Only Protocols
你可以限定一个协议只能被类类型适配,结构体或枚举不能适配。做法是在协议的继承列表前加class关键字。
protocol SomeClassOnlyProtocol:class, SomeInheritedProtocol {
// class-only protocol definition goes here
}
21.12Protocol 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>) {
println("Happy birthday\(celebrator.name) - you're\(celebrator.age)!")
}
let birthdayPerson =Person(name: "Malcolm", age:21)
wishHappyBirthday(birthdayPerson)
// prints "Happy birthday Malcolm - you're 21!"
21.13Checking for Protocol Conformance
用is和as来检查和转型到指定的协议。
1)is,如果有适配则返回true,否则返回false
2) as? ,如果转型成功则返回可选协议类型,失败则返回nil
3) as,强制转型到协议类型,失败则触发运行时异常
@objc protocol HasArea {
var area: Double {get }
}
注意:
1)只有标识 @objc ,你才能对其进行协议适配检查。这个属性标明这个协议要暴露给Objective-C。即使你并没有操作Objective-C,如果你想检查协议适配,你就需要加@objc
2) @objc的协议只能被类类型实现,不能被结构体或枚举实现。一旦你标注协议是@objc,你就只能将其应用在类上。
当时学习的时候xcode的版本是6.1,当时就纳闷在swift里面不加@objc的协议如何判断是否适配,如何转型。
昨天在看这些备忘时,又想起这事,于是打开新版本(6.3.1)的文档看了下,发现已经不一样了。
上面一小段在新版本里面是这样的,也没有“注意”部分了。
你可以使用is 和 as 操作符来检查是否适配协议,和转换到特定的协议。
检查和转换到协议的语法和检查和转换到类型的语法相同:
1) 如果实例适配了协议则返回true,否则false
2) as? 向下转型返回协议类型的可选值,如果实例没有适配协议则返回nil
3) as! 强制转型,如果失败则触发异常
//定义一个协议,要求有可读Double属性area
protocol HasArea {
var area: Double { get }
}
//两个实现这个协议的类
class Circle:HasArea {
let pi = 3.1415927
var radius: Double
var area: Double {return pi *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 }
}
//这三个类没有共同的祖先。但是他们都是类类型,所以他们的实例可以初始化到一个AnyObject数组中
let objects: [AnyObject] = [
Circle(radius: 2.0),
Country(area: 243_610),
Animal(legs: 4)
]
//遍历,检查是否适配HasArea协议
for objectin objects {
if let objectWithArea = objectas? HasArea {
println("Area is \(objectWithArea.area)")
}else {
println("Something that doesn't have an area")
}
}
// Area is 12.5663708
// Area is 243610.0
// Something that doesn't have an area
for object in objects {
if object is HasArea{
let hasArea = object as! HasArea
println("\(hasArea.area)")
}else{
let animal = object as! Animal
println("\(animal.legs)")
}
}
//打印 12.5663708
//打印 243610.0
//打印 4
21.14Optional Protocol Requirements
可以定义可选要求,这些要求不一定非得被实现。可选要求在定义前加optional修改符。
可选协议要求可以在可选链中调用。
检查可选要求是否实现的办法是调用时加问号。可选属性,可选方法的返回值返回的也是可选值。
注意:
1)可选协议要求协议必须是@objc的。即使你不操作Objective-C,如果你想表示可选要求,你就必须把协议标注成@objc。
2)@objc协议只能被类类型适配,不能被结构体或枚举适配
//下面例子定义一个计数器,使用外部数据源提供计数幅度
//外部数据源协议定义如下
@objcprotocol CounterDataSource {
optional func incrementForCount(count:Int) -> Int
optional var fixedIncrement:Int { get }
}
//计数器定义如下
@objcclass 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:CounterDataSource {
let fixedIncrement = 3
}
//计数器使用外部数据源计数
var counter =Counter()
counter.dataSource =ThreeSource()
for_ in 1...4 {
counter.increment()
println(counter.count)
}
// 3
// 6
// 9
// 12
//外部数据源实现趋0的功能
class TowardsZeroSource:CounterDataSource {
func incrementForCount(count: Int) -> Int {
if count == 0 {
return 0
}else if count <0 {
return 1
}else {
return -1
}
}
}
//趋0的效果
counter.count = -4
counter.dataSource =TowardsZeroSource()
for_ in 1...5 {
counter.increment()
println(counter.count)
}
// -3
// -2
// -1
// 0
// 0
本人的一个疑问,这边说协议有可选要求是要@objc
可是在开发库中NSObjectProtocol是没有加@objc的,why?
protocol NSObjectProtocol {
func isEqual(object: AnyObject?) -> Bool
var hash: Int {get }
var superclass: AnyClass? {get }
func `self`() -> Self!
func isProxy() -> Bool
func isKindOfClass(aClass: AnyClass) -> Bool
func isMemberOfClass(aClass: AnyClass) -> Bool
func conformsToProtocol(aProtocol: Protocol) -> Bool
func respondsToSelector(aSelector: Selector) -> Bool
var description: String {get }
optional var debugDescription:String { get }
}