美文网首页iOS精品文章
Swift学习笔记--为代码的执行做个决定

Swift学习笔记--为代码的执行做个决定

作者: Jesmine阳 | 来源:发表于2017-08-18 16:05 被阅读82次

    为代码的执行做个决定

    [TOC]

    和其他的编程语言一样,为了能够控制程序的执行路径,Swift提供了我们熟悉的循环和分之判断语句。首先我们先快速的过一遍他们的基本用法。

    条件分支语句

    第一个要介绍的,是if...else if...else...。这是几乎每种语言都支持的分支表达方式,其中else ifelse都是可选的部分,它们可以单独和if搭配形式各种分支条件的判断。基本上,看到代码,我们就可以直接了解这类判断的含义了。

    var light = "red"
    var action = ""
    
    if light == "red" {
        action = "stop"
    }
    else if light == "yellow" {
        action = "caution"
    }
    else if light == "green" {
        action = "go"
    }
    else {
        action = "invalid"
    }
    

    在上面这个红绿灯的代码里,我们不断根据light的值,设置了变量action的值,它很简单。但通常,我们还是更多会使用if...else...表示非黑即白这样的简单关系。

    对于上面这种存在多种可能性的情况,在Swift里,我们通常还是会使用switch...case...来表示,它比if...else...更安全,也更有更好的表意:

    switch light {
        case "red":
            action = "stop"
        case "yellow":
            action = "caution"
        case "green":
            action = "go"
        default:
            action = "invalid"
    }
    

    这里,我们使用switch...case...表达了和之前的if...else...相同的语义。但是,它更明确的表达了当light的值(switch)为各种情况(case)时,我们应该采取哪些措施,这样的概念。

    但和C++/Java这样语言相比,Swift中的switch...case...也有一些自己独特的地方:

    首先,case语句必须exhausitive,也就是说,必须覆盖switch后面出现的表达式的所有情况,否则会导致编译错误.

    当你不需要对列出case的其他情况作出处理时,你也要在default分支写上一句break,明确表示你考虑到了其他的情况,只是你不需要更多额外处理而已。

    其次,每个case语句不会自动“贯通”到下一个case,因此我们也无需在每个case最后一行写break表示结束;

    最后,当我们要在一个case里匹配多个条件的时候,可以使用逗号把多个条件分开,以上,就是和分支条件相关的两个最基本的场景和用法,接下来,我们了解循环。

    循环控制语句

    第一个要介绍的,是for element in collection/range,我们可以用它来方便的遍历一个集合类型或者范围:

    let vowel = ["a", "e", "i", "o", "u"]
    
    for char in vowel {
        print(char)
    }
    // aeiou
    
    for number in 1...10 {
        print(number)
    }
    // 12345678910
    

    第二个循环的方式是while,它有前置判断和后置判断两种形式,基本上保留了原汁原味的C用法:

    // while
    var i  = 0
    while i < 10 {
        print(i)
        i += 1
    }
    
    // do ... while
    repeat {
        print(i)
        i -= 1
    } while i > 0
    

    在这两类循环里,我们都可以用continue来停止执行当前循环中的语句,立即开始下一次循环。例如,打印所有的偶数:

    for number in 1...10 {
        if number % 2 != 0 { continue }
        print(number)
    }
    // 2 4 6 8 10
    

    在这个例子里,如果number是奇数,就会执行到continue,当前循环就停止并自动进入下一次循环了。

    或者,我们也可以使用break来终止整个循环。例如,值大于8时,就终止循环:

    for number in 1...10 {
        if number > 8 { break }
        print(number)
    }
    // 1 2 3 4 5 6 7 8
    

    使用简单的样式匹配

    现实环境中,我们需要的判断条件可能比那些演示的例子复杂的多。为此,Swift从函数式编程中借鉴了一些样式匹配的方式,帮助我们构建表意丰富又易于维护的代码。

    匹配值的方式

    为了演示各种样式匹配的方式,我们先定义一个tuple,表示平面直角坐标系中的原点:

    let origin = (x: 0, y: 0)
    

    当我们要判断某个点是否是原点的时候,最原始的方式,是这样的:

    let pt1 = (x: 0, y: 0)
    if pt1.x == 0 && pt1.y == 0 {
        print("@Origin")
    }
    

    当然,这样判断xy坐标是否相等并不能让人满意,写起来非常麻烦。实际上,我们还可以这样:

    if case (0, 0) = pt1 {
        print("@Origin")
    }
    

    我们可以用case 匹配的值 = 要检查的对象的方式,对要检查的对象进行判断。在我们的例子里,判断的就是pt1是否等于原点。

    除了用在if中匹配值,我们当然也可以在switch的case分支里,匹配特定形式的值:

    switch pt1 {
    case (0, 0):
        print("@Origin")
    case (_, 0):
        print("on x axis")
    case (0, _):
        print("on y axis")
    case (-1...1, -1...1):
        print("inside 2x2 square")
    default:
        break;
    }
    

    在上面这个例子里,除了用case (0, 0)表示匹配原点值之外,还可以用(_, 0)(0, _)表示忽略掉_的部分,仅对tuple中某一部分的值进行匹配,或者,在tuple的每一个成员位置,使用range operator匹配值的某个范围。

    除了把case用于条件分支语句,我们还可以用于循环语句,用于进一步控制循环条件,例如:

    let array1 = [1,1,2,2,2]
    
    for case 2 in array1 {
        print("found two")
    }
    

    在上面这个例子里,当遇到数组中值为2的元素时,我们向控制台打印了一行话,因此,print一共会打印3次。

    把匹配的内容绑定(value binding)到变量

    除了在 case中使用各种形式的具体值之外,我们还可以把匹配到的内容直接绑定到变量上,这样我们就可以再相应的处理代码中直接使用它们,例如:

    switch pt1 {
        case (let x, 0):
            print("(\(x),0) is on x axis")
        case (0, let y):
            print("(0,\(y)) is on y axis")
        default:
            break;
    }
    

    在上面这个例子里,我们把之前_的部分换成了let xlet y,这样,同样是匹配在坐标轴上的点,这次,我们就可以在对应的case中,直接访问匹配到的值了。我们管这样的形式,叫做value binding

    除了直接绑定变量自身的值之外,我们还可以用类似的形式绑定enum中的关联值。例如,我们先定义一个表示方向的enum

    enum Direction {
        case north, south, east, west(abbr: String)
    }
    
    let west = Direction.west(abbr: "W")
    

    为了演示,我们给.west添加个了一个associated value,表示方向的缩写。然后,我们既可以像这样来判断enum值自身:

    if case .west = west {
        print(west) // west("W")
    }
    

    此时,print打印的就是enum case的值。我们也可以这样来直接绑定westassociated value

    if case .west(let direction) = west {
        print(direction) //W
    }
    

    此时,print打印出来的值,就直接是字符“W”了。当然,case这样的用法,在switch的分支中,也是完全可以的。

    自动提取optional的值

    除了绑定enum的associated value之外,我们还可以使用case来自动提取optional类型的非空值:

    let skill: [String?] = ["Swift", nil,"PHP","JavaScript",nil]
    
    for case let skill? in skills {
        print(skill) // Swift PHP JavaScript
    }
    

    在我们的例子里,skills包含了5个元素,其中两个是nil,当我们用case let skill?这样的形式来绑定optional值的时候,Swift就会自动提取每一个非nil的元素,因此,print会输出“Swift PHP JavaScript”。

    自动绑定类型转换的结果

    最后一类基本的样式匹配规则是自动绑定类型转换的结果。首先,我们创建一个[Any]

    let someValue: [Any] = [1, 1.0, "One"]
    

    当我们遍历someValues,并且要根据不同类型的数组元素分别做一些操作的时候,可以这样:

    for value in someValues {
        switch value {
        case let v as Int:
            print(Interger \(v))
        case let v as Double:
            print(Double \(v))
        case let v as String:
            print(String \(v))
        default:
            print("Invalid value")
        }
    }
    // Integer 1
    // Double 1.0
    // String One
    

    在上面的例子中,我们使用了case let Variable as Type的方式,把类型转换成功的结果,绑定在了变量V上。这样,我们就可以在对应的case里,访问到转换成功的值了。

    或者,如果你仅仅想判断类型,而不需要知道具体内容的话,还可以使用更简单的is操作符:

    for value in someValues {
        switch value {
        case is Int:
            print("Integer")
        // omit for simplicity...
    }
    

    使用高级样式匹配方式

    使用where约束条件

    除了使用具体的数值对循环或分支条件进行约束外,我们还可以使用where进行更复杂的约束。先来看一个简单的例子:

    for i in 1...10 where i % 2 == 0 {
        print(i)
    }
    

    这里我们在for循环里使用了where限定了进入循环的值必须是偶数。我们还可以把where用在更复杂的value binding语句里。例如,我们假设定义下面的enum表示手机电量

    enum Power {
        case fullyCharges
        case normal(percentage: Double)
        case outOfPower
    }
    

    然后在定义一个battery表示手机电池

    let battery = Power.normal(percentage: 0.1)
    

    这样,我们就可以在绑定.normalassociated value的同时,使用where进一步约束它的关联值:

    switch battery {
        case .normal(let precentage) where percentage <= 0.1":
            print("almost out of power")
        case .normal(let percentage) where percentage >= 0.8:
            print("almost fully power")
        case .fullyCharges, .outOfPower:
            print("Fully charged or out of power")
        default:
            break
    }
    

    上面的case语句中,batteryfullyChargesoutOfPower我们使用逗号分隔,表示逻辑或的概念,逗号也可以用在if中表示逻辑与的概念,例如为了处理之前电量低的情况,我们还可以用if这样来实现:

    if case .normal(let percentage) == battery, case 0...0.1 = percentage {
        print("Almost out of power")    
    }
    

    在上面的代码里,第一个if case使用value binding读取了battery中.normal的associated value。接下来,第二个case进一步约束了第一个case中关联到的值小于10%的情况。

    使用tuple简化多个条件的比较

    有时,我们需要在if中同时比较多个条件。假设,我们有一对用户名和密码:

    let username = "11@boxue.io"
    let password = 11111111
    

    当我们要同时比较这两个值时,最普通的写法,就是在if中使用逻辑与(&&)操作符:

    if username == "11@boxue.io" && password == 11111111 {
        print("correct")
    }
    

    如果你不喜欢在if中并列多个比较语句,我们还可以用case临时生成一个tuple,并且,让它和username / password进行比较:

    if case ("11@boxue.io", 11111111) = (username,password) {
        print("correct")
    }
    

    理解样式匹配的实现方式

    首先,当要匹配的变量和样式的类型相同,并且对应的类型实现了Equatable protocol时,就直接使用对应类型的==操作符进行匹配。例如我们之前比较用户名和密码的例子:

    let username = "11@boxue.io"
    let password = 11111111
    
    if case ("11@boxue.io", 11111111) = (username, password) {
        print("correct")
    }
    

    这里,就直接使用了Tuple类型的比较操作符。

    其次,如果样式和要匹配的变量类型不同,或对应类型没有实现Equaltable protocol时,Swift会使用~=操作符进行比较。当这个操作符返回true时,就认为条件匹配,否则就认为不匹配。因此,为了判断某个数值是否在某个范围里,Swift标准库中实现了Range ~= Value这种形式的比较,但是,却没有实现Value ~= Range这样的版本。

    因此,当我们写成case percentage = 0...0.1时,编译器就会因为找不到对应的操作符而报错了。不过,由于这个操作符是可以自定义的,因此,我们可以通过重载它,来实现上面的功能:

    func ~=<T>(value: T, pattern: ClosedRange<T>) -> Bool {
            return pattern.contains(value)
    }
    

    为了能匹配不同的类型,我们把~=定义为了一个泛型函数。它的第一个参数表示~=的左操作数,按照我们的例子,应该是一个值;第二个参数表示~=的右操作数,它是一个ClosedRange<T>。在它的实现里,我们只要调用ClosedRangecontains方法,就可以实现“是否包含某个值”这样的语义了。

    而当我们重载了这个~=方法之后,之前发生编译错误的语句就可以正常工作了。我们也可以通过类似的方法,为自定义类型添加各种样式匹配规则。

    相关文章

      网友评论

        本文标题:Swift学习笔记--为代码的执行做个决定

        本文链接:https://www.haomeiwen.com/subject/vruarxtx.html