Swift学习笔记(十)--类型转换和聚合类型

2016-01-30  本文已影响210人  MD5Ryan

这两章比较简单, 以类型转换这一章更为重要和常见.

类型转换(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 "♡"

这个特性可能对代码的聚合比较好吧, 但是把所有东西都堆在一起可能也不好, 主要还是看体量. 聚合类型差不多就结束了, 我也没有细看文档, 只看了一下代码, 然后尝试了几个写法(例如值类型能不能聚合引用类型, 经测试是可以的), 细节还是查看官方文档

上一篇 下一篇

猜你喜欢

热点阅读