Swift学习笔记(十)--类型转换和聚合类型
这两章比较简单, 以类型转换这一章更为重要和常见.
类型转换(Type Casting)
秉着没有最简洁只有更简洁的理念, 苹果在Swift里引入了两个关键字来进行类型转换相关的操作, 分别是is和as. 从名字上来看, 前者为是, 后者为当作, 可以稍稍看出其作用的区别. is可以对应为NSObject的isKindOfClass:方法, 但是isMemberOfClass:则没有对应起来.
但是, 其实苹果还是给我们留了后路的, Swift里面的AnyObject类型(其它类型不行)还是有isKindOfClass和isMemberOfClass方法的, 只是从这个态势上来看, 苹果其实不太希望我们做这么细的检查, 一般需要这么做的话, 我们的写法其实是可以优化的. 另外还需要提一下则是isMemberOfClass这2个方法需要传入一个类, 可以这么玩, 下面的代码摘抄自王巍的blog:
class ClassA { }
class ClassB: ClassA { }
let obj1: AnyObject = ClassB()
let obj2: AnyObject = ClassB()
obj1.isKindOfClass(ClassA.self) // true
obj2.isMemberOfClass(ClassA.self) // false
除了类型的检查, 后面还有检查类型是否实现了某个协议, 这个讲到协议再说.
首先, 先把官方定义的几个类引入进来:
// 基类
class MediaItem {
var name: String
init(name: String) {
self.name = name
}
}
class Movie: MediaItem {
var director: String
init(name: String, director: String) {
self.director = director
super.init(name: name)
}
}
class Song: MediaItem {
var artist: String
init(name: String, artist: String) {
self.artist = artist
super.init(name: name)
}
}
// 创建对象...们
let library = [
Movie(name: "Casablanca", director: "Michael Curtiz"),
Song(name: "Blue Suede Shoes", artist: "Elvis Presley"),
Movie(name: "Citizen Kane", director: "Orson Welles"),
Song(name: "The One And Only", artist: "Chesney Hawkes"),
Song(name: "Never Gonna Give You Up", artist: "Rick Astley")
]
// library会被推导为[MediaItem]
之后的几个小节会围绕这3个类进行类型转换的讲解.
检查类型(Checking Type)
类型用is操作符来检查, 如果返回为true则代表成立, 否则不成立. 使用起来是这样的:
var movieCount = 0
var songCount = 0
for item in library {
if item is Movie {
++movieCount
} else if item is Song {
++songCount
}
}
向下转换(DownCasting)
所谓向下转换是指父类对象转换为子类对象. Swift里面用as?或as!操作符来进行这种转换操作. 这种操作是有可能失败的, 如上面library[0]是Movie类型的, 但是library的类型是[MediaItem], 存进数组是按MediaItem来存的, 但是要取出来操作就势必要把MediaItem向下转换为Movie了.
当然, 这种做法是可能会失败的, 如library[1]是Song类型的, 如果用as转换为Movie就会失败. 上面说过, 转换有两种形式, 一种是用as?的条件形式, 将会返回一个目标类型的可选类型值, 也就是说, 成功返回目标类型的对象, 失败就返回nil. 另一种是as!的转换+强制拆包的形式(和之前讲optional的?和!是一样的概念), 这种比较危险的做法稍后会讲.
上面说到转换是会失败的, 返回nil, 所以标准的写法就又要用到if let了, 看下面的例子:
for item in library {
if let movie = item as? Movie {
print("Movie: '\(movie.name)', dir. \(movie.director)")
} else if let song = item as? Song {
print("Song: '\(song.name)', by \(song.artist)")
}
}
Any和AnyObject的类型转换
ObjC里面不指定特定类型的时候用id, 在Swift里面引入了两个特殊的类型, 就是AnyObject和Any, 它们的区别如下:
1). AnyObject可以表示任意类类型的实例
2). Any可以表示任意类型的实例, 包括函数类型(说明函数在Swift里面是一等公民)
注意, 这俩特殊类型虽然有时候很有用, 但是万万确保在确实需要的时候才用, 一般来说最好是能够在代码中用自己期望的类型. (因为这样可以避免很多类型检查很转化, 把代码变得复杂)
AnyObject
[AnyObject]在Cocoa API里面是很常见的, 这是很正常的, 毕竟底层的API必须要兼容上层的业务代码. 虽说数组里面是AnyObject, 但我们大多数情况都是很确定数组里面的类型是什么的, 比如:
let someObjects: [AnyObject] = [ // 如果靠类型推导则是[Movie]类型
Movie(name: "2001: A Space Odyssey", director: "Stanley Kubrick"),
Movie(name: "Moon", director: "Duncan Jones"),
Movie(name: "Alien", director: "Ridley Scott")
]
这个时候someObjects里面的元素肯定都是Movie, 所以类型转换的时候可以用as!, 如:
for object in someObjects {
let movie = object as! Movie // 不需要if let, 因为!是自动拆包的
print("Movie: '\(movie.name)', dir. \(movie.director)")
}
// 秉着只有更简洁的精神, 苹果简化如下:
for movie in someObjects as! [Movie] { // 加中括号是因为这个转换操作是对someObjects做的
print("Movie: '\(movie.name)', dir. \(movie.director)")
}
Any
Any可以往里面塞各种类型的东西, 直接看官网的例子:
var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append(Movie(name: "Ghostbusters", director: "Ivan Reitman"))
things.append({ (name: String) -> String in "Hello, \(name)" })
// 这么多类型, 要拆包就用switch语句比较合适了:
for thing in things {
switch thing {
case 0 as Int:
print("zero as an Int")
case 0 as Double:
print("zero as a Double")
case let someInt as Int:
print("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
print("a positive double value of \(someDouble)")
case is Double:
print("some other double value that I don't want to print")
case let someString as String:
print("a string value of \"\(someString)\"")
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let movie as Movie:
print("a movie called '\(movie.name)', dir. \(movie.director)")
case let stringConverter as String -> String:
print(stringConverter("Michael"))
default:
print("something else")
}
}
所以用Any还是比较麻烦的, 不到万不得已, 还是不要轻易使用, 想想别的解决办法可能会更简洁, 更好维护. 类型转换差不多到这, 具体细节参考官方文档
聚合类型
所谓聚合类型就是在某个类型里面再定义一个类型, 定义在内部的类型外面是无法直接使用的, 需要通过外部的类型间接使用. 看看官网的例子基本上就应该不会有太多问题了.
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)
print("theAceOfSpades: \(theAceOfSpades.description)")
// prints "theAceOfSpades: suit is ♠, value is 1 or 11"
// 间接使用内部类型
let heartsSymbol = BlackjackCard.Suit.Hearts.rawValue
// heartsSymbol is "♡"
这个特性可能对代码的聚合比较好吧, 但是把所有东西都堆在一起可能也不好, 主要还是看体量. 聚合类型差不多就结束了, 我也没有细看文档, 只看了一下代码, 然后尝试了几个写法(例如值类型能不能聚合引用类型, 经测试是可以的), 细节还是查看官方文档走