Swift基础11

可空链式调用

可空链式调用是一种可以请求和调用属性、方法以及下标的过程,它的可空性体现于请求或调用的目标当前可能为空(nil)。如果可空的目标有值,那么调用就会成功;如果选择的目标为空(nil),那么这种调用将返回空(nil)。多个连续的调用可以被链接在一起形成一个调用链,如果其中任何一个节点为(nil)将导致整个链调用失败。

note:Swift的可空链式调用和Objective-c的消息为空有些想象,但是Swift可以使用在任意类型中,并且能够检查调用是否成功。

使用可空链式调用来强制展开

通过在想调用非空的属性、方法、或下标的可空值后面放一个问号,可以定义一个可空链。这一点很像在可空值后面放一个叹号(!)来强制展开其值。它们的主要的区别在于当可空值为空时可空链式只是调用失败,然而强制展开会触发运行时错误。

为了反映可空链式调用可以在空对象nil上调用,不论这个调用的属性、方法、下标等返回的值是不是可空值,它的返回结果都是一个可空值。我们可以利用这个返回值来判断我们的可空链式调用是否成功,如果调用有返回值则说明调用成功,返回nil则说明调用失败。

特别地,可空链式调用的返回结果与原本的返回结果具有相同的类型,但是被包装成了可空类型值。当可空链式调用成功时,一个本应该返回Int的类型的结果将会返回Int?类型。


class House {
    var numberOfRooms = 1
}

class Person {
    var house : House?
}

let john = Person()
//let num = john.house!.numberOfRooms

if let roomCount = john.house?.numberOfRooms {
    print("John has \(roomCount) rooms")
}else{
    print("John has no house")
}

为可空链式调用定义模型类

通过使用可空链式调用可以调用多层属性,方法和下标。这样可以通过各种模型向下访问各种子属性。并且判断能否访问子属性,方法或下标。

通过可空链式调用访问属性

可以通过可空链式调用访问属性的可空值,并且判断访问是否成功。

通过可空链式调用来调用方法

可以通过可空链式调用来调用方法,并判断是否调用成功,即使这个方法没有返回值。如果在空值桑调用可空链式调用来调用这个方法,这个方法返回类型为Void?而不是Void。

通过可空链式调用来访问下标

通过可空链式调用,我们可以用下标来对可空值进行读取或写入,并且判断下标调用是否成功。
note:当通过可空链式调用访问可空值的下标的时候,应该将问号放在下标放括号的前面而不是后面。可空链式调用的问号一般直接跟在可控表达式的后面。

多层链接

可以通过多个个链接多个可空链式调用来向下访问属性,方法以及下标。但是多层可空链式调用不会添加返回值的可空性。

也就是说:

  • 如果我们访问的值不是可空的,通过可空链式调用将会返回可空值。
  • 如果我们访问的值已经是可空的,通过可空链式调用不会变得“更”可空。

因此:

  • 通过可空链式调用访问一个Int值,将会返回Int?,不过进行了多次可空链式调用。
  • 类似的,通过可空链式调用访问Int?值,并不会变得更加可空。

对返回可空值的函数进行链接

通过可空链式调用来获取可空属性值。我们还可以通过链式调用来调用返回可空值的方法,并且可以继续对可空值进行链接。

</br></br></br>


错误处理

错误处理是响应错误以及从错误中恢复的过程。Swift提供了在运行时对可恢复错误抛出,捕获,传送和操作的高级支持。

某些操作并不能总是保证执行所有代码都可以执行或总会产出有用的结果。可选类型用来表示值可能为空,但是当执行失败的时候,通常要去了解此次失败是由何引起,你的代码就可以做出与之相应的反应。

note:Swift中的错误处理涉及到错误处理模式,这会用到cocoa和Objecive-c中的NSError。

表示并抛出错误

在Swift中,错误用遵循ErrorType协议类型的值来表示。这个空协议表示一种可以用做错误处理的类型。Swift的枚举类型尤为适合塑造一组相关的错误情形,枚举的关联值还可以提供额外的信息,表示某些错误情形的性质。

enum VendingMachineError: ErrorType {
    case InvalidSelection //选择无效
    case InsufficientFunds(coninsNeeded: Int) //金额不足
    case OutOfStock // 缺货
}

抛出一个错误会让我们对所发生的意外情况作出提示,表示正常的执行流程不能被执行下去。抛出错误使用throw关键字。

throw VedingMachineError.InsufficientFunds(coninsNeeded:5)

处理错误

某个错误被抛出时,那个地方的某部分代码必须要负责处理这个错误(比如纠正这个错误、尝试另外一种方式、或时给用户提示这个错误)。Swift中有4种处理错误的方式。我们可以把函数抛出的错误传递给调用此函数代码、用do-catch语句处理错误、将错误作为可选类型处理、或者断言此错误根本不会发生。

note:Swift中的错误处理和其他语言中的try,catch和throw的异常处理很像。和其他语言(包括OC)的异常处理不同的是,Swift中的错误处理并不涉及堆栈解退,这是一个计算昂贵的过程。就此而言,throw语句的性能特性是可以和return语句相当的。

1.用throwing函数传递错误

用throws关键字来标识一个可抛出错误的函数,方法或是构造器。在函数声明中的参数列表之后加上throws。一个标识了throws的函数被称作throwing函数。如果这个函数还有返回类型,throws关键字需要写在箭头(->)的前面。

func canThrowErrors() throws -> String
func cannotThrowErrors() -> String

throwing函数从其内部抛出错误,并传递到函数被调用时所在的区域中。

note:只有throwing函数可以传递错误。任何在某个非throwing函数内部抛出的错误只能在此函数内部处理。

enum VendingMachineError: ErrorType {
    case InvalidSelection //选择无效
    case InsufficientFunds(coninsNeeded: Int) //金额不足
    case OutOfStock // 缺货
}

struct Item {
    var price : Int
    var count : Int
}

class Vendingmachine {
    
    var inventory = ["water": Item(price:12,count: 7),
                     "coco": Item(price: 10, count: 4),
                     "orange":Item(price: 7, count: 11)
                     ]
    
    var coinsDeposited = 0
    
    func dispenseSnack(snack: String){
        print("Dispensing \(snack)")
    }
    
    func vend(itemnamed name : String) throws {
        
        guard var item = inventory[name] else{
            
            throw VendingMachineError.InvalidSelection
        }
        
        guard item.count > 0 else{
            throw VendingMachineError.OutOfStock
        }
        
        guard item.price <= coinsDeposited else{
            throw VendingMachineError.InsufficientFunds(coninsNeeded: item.price - coinsDeposited)
        }
        
        coinsDeposited -= item.price
        item.count -= 1
        inventory[name] = item
        dispenseSnack(name)
        
    }
    
    
}

因为vend(itemNamed:)方法会传递出它抛出的任何错误,在我们代码中调用它的地方必须要直接处理这些错误---使用do-catch语句,try?或try!,要么继续将这些错误传递下去。

func buyMostSnack(name:String,machine:Vendingmachine) throws {
    try machine.vend(itemnamed: name)
}

因为vend(itemNamed:)方法能抛出错误,所以在调用它的时候在它前面加了try关键字。

2.用Do-Catch处理错误

可以使用一个do-catch语句运行一段闭包代码来做错误处理。如果在do语句中的代码抛出了一个错误,则这个错误会与catch语句做匹配来决定哪条语句能处理它。

do {

try expression 

}catch pattern1{

}catch pattern2 where condition {

}

在catch后面写一个模式(pattern)来标示这个语句能处理什么样的错误。如果一条catch语句没带一个模式,那么这条语句可以和任何错误相匹配,并且把错误和一个名字为name的局部常量做绑定。

catch语句不必将do语句中代码所抛出的每个可能的错误都处理。如果没有一条catch字句来处理错误,错误就会传播到周围的作用域。然而错误还是必须要被某个周围饿作用域处理的---要么是一个外围的do-catch错误处理,要么是一个throwing函数的内部。

var machine = Vendingmachine()
machine.coinsDeposited = 8

do {
    try  buyMostSnack("coco", machine: machine)
}catch VendingMachineError.InvalidSelection{
    print("InvalidSelection")
}catch VendingMachineError.OutOfStock {
    print("OutOfStock")
}catch VendingMachineError.InsufficientFunds(let coinsNeeded){
    print("InsufficientFunds please insert \(coinsNeeded)")
}

将错误转换成可选值

可以使用try?通过将其转换成一个可选值来处理错误。如果在评估try?表达式时一个错误被抛出,哪么这个表达式的值就是nil。

func someThrowingFunction() throws -> Int {
    //.....
}

let x = try? someThrowingFunction()

let y: Int?

do {
    y = try someThrowingFunction()
} catch {
    y = nil
}   

如果someThrowFunction()抛出一个错误,x和y的值是nil。否则 x 和 y 的值就是该函数的返回值。注意无论someThrowFuction()的返回值类型是什么类型,x 和 y 都是这个类型的可选类型。

使错误传递失败

有时候我知道某个throwing函数实际上在运行时是不会抛出错误的。在这种条件下,我们可以在表达式前面写try!来使错误传递失败,并把调用包装在一个运行时断言中来判定会不会有错误抛出。如果实际上确实抛出了错误,我们就会得到一个运行时错误。

let photo = try! loadImage("./Resources/John.png")

指定清理操作

可以使用defer语句在代码执行到要离开当前的代码段之前去执行一套语句。该语句让我们能够作一些应该被执行的必要清理工作,不管是以何种方式离开当前的代码段的 ---无论时由于抛出错误而离开,或是因为一条return或者break的类似语句。defer语句将代码的执行延迟到当前的作用域退出之前。该语句由defer关键字和要被延时执行的语句组成。延时执行的语句不会包含任何会将控制权移交语句外面的代码,例如一条break或是return语句,或是抛出一个错误。延迟执行的操作是按照它们被指定的相反顺序执行---意思是第一条defer语句中的代码执行是在第二条defer语句中代码被执行之后,以此类推

func test(){
    print("test begin")
    defer{
        print("1")
    }
    defer{
        print("2")
    }
    defer{
        print("3")
    }
    defer{
        print("4")
    }
     print("test end")
}

test()

类型转换

类型转换可以判断实例的类型,也可以将实例看做是其父类或子类的实例。

类型转换在Swift中使用 is 和 as 操作符实现。这两个操作提供了一种简单达意的方式去检查值的类型或者转换它的类型。

我们也可以用它来检查一个类是否实现了某个协议

定义一个类层次作为例子

我们可以将类型转换用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中其他类型。

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 arr = [
    Movie(name: "one", director: "1"),
    Song(name: "one", artist: "1"),
    Movie(name: "two", director: "2"),
    Movie(name: "three", director: "3"),
    Song(name: "three", artist: "3")
]

检查类型

用类型检查操作符(is)来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回true , 否则返回false 。

for item in arr {
    if item is Movie {
        print("\(item) is movie")
    }
    
    if item is Song {
        print("\(item) is song")
    }
    
    //总是true
    if item is MediaItem {
        print("\(item) is MediaItem")
    }
}

向下转型

某类型的一个常量或变量可能在幕后实际上属于一个子类。当确定这种情况下,我们可以尝试向下转到它的子类型,用类型转换符(as?或 as!)

因为向下转型可能会失败,类型转型操作符带有两种不同形式。

  • 条件形式 as?返回一个你试图向下转成的类型的可选值。
  • 强制式 as!把试图向下转型和强制解包结果作为一个混合动作。

只有当可以确定向下转型一定会成功时,才使用强制形式(as!)。当试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。

note:转换没有真的改变实例或它的值。潜在的根本的实例保持不变;只是简单地把它作为它被转成的类来使用。


for item in arr {
    
    if let movie = item as? Movie{
        print("movie:\(movie.name) and \(movie.director)")
    }else if let song = item as? Song {
         print("song:\(song.name) and \(song.artist)")
    }
}

Any和AnyObject的类型转换

Swift为不确定类型提供了两种特殊类型别名:

  • AnyObject可以代表任何class类型的实例。
  • Any可以表示任何类型,包括方法类型(function types)

note:只有当我们明确的需要它的行为和功能时才使用Any和AnyObject。在我们的代码里使用我们期望的明确的类型总是更好的。

AnyObject类型

当在工作中使用Cocoa APIs,我们一般会接收一个[AnyObject]类型的数组,或者说“一个任何对象类型的数组”。这是因为Objective-c没有明确的类型化数组。但是,我们常常可以从API提供的信息中清晰地确定数组中对象的类型 。

在这些情况下,我们可以使用强制形式类型转换(as)来下转在数组中的每一项到比AnyObject更明确的类型,不需要可选解析。

let someObjects: [AnyObject] = [
    Movie(name: "one", director: "1"),
    Movie(name: "two", director: "2"),
    Movie(name: "three", director: "3"),
    Movie(name: "four", director: "4"),
    Movie(name: "five", director: "5")
]

for object in someObjects {
    let movie = object as! Movie
    print("Movie:\(movie.name) and \(movie.director)")
}

为了变成一个更短的形式,下面转someObjects数组为[Movie]类型来替代下转数组中的每一项的方式。

for movie in someObjects as! [Movie] {
     print("Movie:\(movie.name) and \(movie.director)")
}

Any类型

这里有个例子,使用Any类型来和混合的不同类型一起工作。包括方法类型和非class类型。它们创建了一个可以存储Any类型的数组things。

var things = [Any]()

things.append(0)
things.append(0.0)
things.append(42)
things.append(3.1415926)
things.append("hello")
things.append({(name:String) -> String in "hello \(name)"})

嵌套类型

枚举类型常被用于特定类或结构体的功能。也能够在多种变量类型的环境中,方便的定义通用类或结构体来使用,为了实现这种功能,Swift允许你定义嵌套类型,可以在枚举类型、类和结构体种定义支持嵌套的类型。

要在一个类型中嵌套另一个类型,将需要嵌套的类型的定义写在被嵌套类型的区域{}内,而且可以根据需要定义多级嵌套。

嵌套类型实例

在外部对嵌套类型的引用,以被嵌套类型的名字为前缀,加上所要引用的属性,方法等。

struct Student {
    
    struct PersonInfo {
        var name:String
        var age = 0
        var sex = 0
    }
    
    
    struct School {
        var name:String
        var address:String
    }
    
    var info:PersonInfo
    var school:School
    
    
}

let stu = Student(info: Student.PersonInfo(name:"hello",age:10,sex:1), school: Student.School(name:"school",address:"address")

stu.school.hello()
stu.school.address

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

推荐阅读更多精彩内容