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 "♡"

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容