前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Swift进阶四——流程控制

Swift进阶四——流程控制

作者头像
拉维
发布2020-12-11 11:02:58
8400
发布2020-12-11 11:02:58
举报
文章被收录于专栏:iOS小生活

for-in 分段区间

我们可以使用函数stride(from:, to:, by:)来跳过不想要的标记(开区间);闭区间也同样适用,使用stride(from:, through:, by:)函数即可。

开区间:

闭区间:

更加强大的Switch

Switch语句会将一个值与多个可能的模式进行匹配。

Switch语句一定得是全面的,也就是说,给定类型里面的每一个值都得被考虑到并且匹配到一个case。如果无法提供一个Switch-case所有可能的值,你可以定义一个默认匹配所有的case,来匹配所有未明确出来的值,这个匹配所有的情况使用关键字default来标记,并且必须在所有case的最后出现。

使用switch-case来匹配元组

你可以使用元组来在一个switch语句中测试多个值;使用下划线(_)来表明匹配所有可能的值

代码语言:javascript
复制
let somePoint = (1, 1)
switch somePoint {
case (0, 0):
    print("(0, 0) is at the origin")
case (_, 0):
    print("(\(somePoint.0), 0) is on the x-axis")
case (0, _):
    print("(0, \(somePoint.1)) is on the y-axis")
case (-2...2, -2...2):
    print("(\(somePoint.0), \(somePoint.1)) is inside of the box")
case (_, _):
    print("(\(somePoint.0), \(somePoint.1)) is outside of the box")
}
// (1, 1) is inside of the box

元组匹配的值绑定

switch的case可以将匹配到的值临时绑定为一个常量(let)或者变量(var),来给case的函数体使用。

代码语言:javascript
复制
let somePoint = (3, 1)
switch somePoint {
case (0, 0):
    print("(0, 0) is at the origin")
case (let x, 0):
    print("(\(x), 0) is on the x-axis")
case (0, let y):
    print("(0, \(y)) is on the y-axis")
case (-2...2, -2...2):
    print("(\(somePoint.0), \(somePoint.1)) is inside of the box")
case let (x, y):
    print("(\(x), \(y)) is outside of the box")
}
// (3, 1) is outside of the box

where语句

switch-case可以使用where分句来检查是否符合特定的约束

代码语言:javascript
复制
let somePoint = (1, -1)
switch somePoint {
case let (x, y) where x == y:
    print("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
    print("(\(x), \(y)) is on the line x == -y")
case let (x, y):
    print("(\(x), \(y)) is just some arbiytary point")
}
// (1, -1) is on the line x == -y

复合匹配及其值绑定

如果case的多种情形共享同一个函数体,那么可以在case后面写多个模式来复合,在每个模式之间使用英文逗号来分割。如果任何一个模式匹配了,那么这个情况都会被认为是匹配的。如果模式太长,那么可以把它们写成多行。

符合匹配同样可以包含值绑定的。所有复合匹配的模式都必须包含相同的值绑定集合,并且复合情形中的每一个绑定都得有相同的类型格式。这才能确保无论复合匹配中的哪部分命中了,接下来的函数体中的代码都能访问到绑定的值并且值的类型也是一样的。

代码语言:javascript
复制
let somePoint = (9, 0)
switch somePoint {
case (let distance, 0), (0, let distance):
    print("On an axis, \(distance) from the origin")
default:
    print("Not on an axis")
}
// On an axis, 9 from the origin

控制转移

continue

continue语句会告诉循环停止正在做的事情,并且再次从头开始循环的下一次遍历。也就是说,它是停止当前的遍历,而不是结束整个循环

break

break语句会立即结束整个控制流语句。当你想要提前结束switch或者循环语句的时候,就可以使用break语句。

在循环体中使用break的时候,break会立即结束循环的执行,并将控制转移到循环结束花括号(})之后的第一行代码上。当前遍历中的其他代码都不会被执行,并且余下的遍历循环也不会开始了

当在switch语句里面使用的时候,break导致switch语句立即结束它的执行,并且转移控制到switch语句结束花括号(})之后的第一行代码上。

语句标签

我们可以使用语句标签来给循环语句或者条件语句做标记

在一个条件语句中,你可以使用一个语句标签配合break语句来结束被标记的语句

在循环语句中,你可以使用语句标签来配合break或者continue来结束或者继续执行被标记的语句。

代码语言:javascript
复制
var number = 10
whileLoop : while number > 0 {
    switch number {
    case 9:
        print("9")
    case 10:
        var sum = 0
        for index in 0...10 {
            sum += index
            if index == 9 {
                print(sum)
                break whileLoop
            }
        }
    default:
        break
    }
    number -= 1
}

上面代码中的whileLoop就是一个语句标签。

使用guard来改善条件判断

guard语句,类似于if语句,都是基于布尔值表达式来执行语句的。

guard语句与if语句一样,都是要求条件语句为真才能执行之后的语句。

与if语句不同的是,guard语句总是有一个else分句——else分句里的代码会在条件不为真的时候执行

我们在编写代码的时候,应该遵循的一个原则是:尽量不要嵌套if语句,而多个return语句则是OK的,这能够增强代码的可读性,因为你的重要代码没有嵌套在分支上,这样可以很清楚地找到相关代码

比如下面的例子:

代码语言:javascript
复制
func isIpAddress(ipString: String) -> (Int, String) {
    let components = ipString.split(separator: ".")
    
    if components.count == 4 {
        if let first = Int(components[0]), first >= 0 && first <= 255 {
            if let second = Int(components[1]), second >= 0 && second <= 255 {
                if let third = Int(components[2]), third >= 0 && third <= 255 {
                    if let fourth = Int(components[3]), fourth >= 0 && fourth <= 255 {
                        return (200, "正确的IP地址")
                    } else {
                        return (4, "IP地址第四部分不对")
                    }
                } else {
                    return (3, "IP地址第三部分不对")
                }
            } else {
                return (2, "IP地址第二部分不对")
            }
        } else {
            return (1, "IP地址第一部分不对")
        }
    } else {
        return (100, "ip地址必须有4部分")
    }
}

print(isIpAddress(ipString: "127.0.0.1")) // (200, "正确的IP地址")

这个方法可以用来判断IP地址是否合法,返回的结果是一个元组,第一个元素是错误码,第二个元素是错误描述。

我们可以看到,一共是嵌套了5个if分支,我们才可以得到检测正确的结果。这样的话,作为最重要的那部分代码,就被嵌套在了最深的层次里面,这是违背我们的代码规范原则的。我们对之进行改造如下:

代码语言:javascript
复制
func isIpAddress(ipString: String) -> (Int, String) {
    let components = ipString.split(separator: ".")
    
    guard components.count == 4 else {
        return (100, "ip地址必须有4部分")
    }
    
    guard let first = Int(components[0]), first >= 0 && first <= 255 else {
        return (1, "IP地址第一部分不对")
    }
    
    guard let second = Int(components[1]), second >= 0 && second <= 255 else {
        return (2, "IP地址第二部分不对")
    }
    
    guard let third = Int(components[2]), third >= 0 && third <= 255 else {
        return (3, "IP地址第三部分不对")
    }
    
    guard let fourth = Int(components[3]), fourth >= 0 && fourth <= 255 else {
        return (4, "IP地址第四部分不对")
    }
    
    return (200, "正确的IP地址")
}

print(isIpAddress(ipString: "1277.0.0.1")) // (1, "IP地址第一部分不对")

看,这样写的话,代码是不是清爽了很多,可读性是不是就很强了。

模式匹配

什么是模式

模式代表单个值或者复合值的结构

例如,元组(1,2)的结构是由逗号分隔的、包含两个元素的列表。因为模式代表的是一种值的结构,而不是某个特定的值,因此你可以利用模式来匹配各种各样的值。比如,(x,y)可以匹配元组(1,2),以及任何含两个元素的元组。除了利用模式来匹配一个值以外,你还可以从复合值中提取出部分或者全部值,然后分别把各个部分的值和一个常量或者变量绑定起来

模式的分类

Swift中的模式分为两类:一类是能够成功匹配到任意类型的值,另一类在运行时匹配某个特定值时可能会失败

第一类模式用于解构简单变量、常量和可选绑定中的值。此类模式包括通配符模式、标识符模式,以及包含前两种模式的值绑定模式和元租模式。你可以为这类模式指定一个类型标注,从而限制他们只能匹配某种特定类型的值。

第二类模式用于全模式匹配,这种情况下你试图匹配的值在运行时可能不存在。此类模式包括枚举用例模式、可选模式、表达式模式和类型转换模式。你在Switch语句中的case标签中,do语句的catch子句中,或者在if、while、guard和for-in语句的case条件句中使用这类模式。

通配符模式(Wildcard Pattern)

通配符模式由一个下划线构成,用于匹配并忽略任意值。当你想忽略被匹配的值时可以使用该模式。

代码语言:javascript
复制
for _ in 1...3 {
    print("早上好")
}

标识符模式(Identifier Pattern)

标识符模式可以匹配任何值,并且将匹配的值和一个常量或者变量绑定起来。

代码语言:javascript
复制
let someValue = "norman"

值绑定模式(Value-Binding Pattern)

值绑定模式会把匹配到的值绑定给一个变量或者常量。把匹配到的值绑定给常量时使用关键字let,绑定给变量时使用关键字var。

代码语言:javascript
复制
let point = (3, 6)
switch point {
// 将point中的元素绑定到 x 和 y
case let (x, y):
    print("The point is at (\(x), \(y)).")
}

元组模式(Tuple Pattern)

元组模式是由逗号分割的、具有0个或多个模式的列表,并由一对圆括号括起来。元组模式匹配相应元组类型的值。

你可以使用类型标注去限制一个元组模式能匹配哪种元组类型。例如,在常量声明 let (x, y): (Int, Int) = (1, 2) 中的元组模式(x, y): (Int, Int)只会匹配两个元素都是Int类型的元组。

当元组模式被用在for-in语句或者变量和常量声明的时候,它仅可以包含通配符模式、标识符模式、可选模式或者其他包含这些模式的元组模式。

代码语言:javascript
复制
let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
for (x, 0) in points {
    /* ... */
}

let points = [(0, 0), (1, 0), (1, 1), (2, 0), (2, 1)]
for (x, y) in points where y == 0 {
    print("\(x) and \(y)")
}

枚举用例模式(Enumeration Case Pattern)

枚举用例模式匹配现有的某个枚举类型的某个用例。枚举用例模式出现在switch语句中的case标签中,以及if、while、guard和for-in语句的case条件中。

可选项模式(Optional Pattern)

可选项模式会匹配 Optional<Wrapped> 枚举在 some(Wrapped) 中包装的值

代码语言:javascript
复制
var a: Int? = 42

if case .some(let x) = a {
    print(x) // 42
}

a = nil
if case let x? = a {
    print(x) // 不会走到这里
}

可选项模式会为for-in语句提供一种迭代数组的便捷方式,可以只为数组中的非nil元素执行循环体

代码语言:javascript
复制
let arrayOfOptionalInts: [Int?] = [nil, 2, 3, nil, 5]
for case let number? in arrayOfOptionalInts {
    print(number)
}

打印结果为:
2
3
5

类型转换模式(Type-Casting Pattern)

类型转换模式有两种:is模式和as模式。

is模式只出现在switch语句中的case标签中

is模式和as模式的形式如下:

is 类型

模式 as 类型

is模式仅当一个值的类型在运行时和is模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。is模式和is运算符有相似表现,他们都进行类型转换,但是is模式没有返回类型。

as模式仅当一个值的类型在运行时和as模式右边的指定类型一致,或者是其子类的情况下,才会匹配这个值。如果匹配成功,被匹配的值的类型会被转换成as模式右边的指定类型。

代码语言:javascript
复制
protocol Animal {
    var name: String { get }
}

struct Dog: Animal {
    var name: String {
        return "dog"
    }
    var runSpeed: Int
}

struct Bird: Animal {
    var name: String {
        return "bird"
    }
    var flightHeight: Int
}

struct Fish: Animal {
    var name: String {
        return "fish"
    }
    var depth: Int
}

let animals: [Any] = [Dog(runSpeed: 33), Bird(flightHeight: 44), Fish(depth: 55)]
for animal in animals {
    switch animal {
    case let dog as Dog: // as 模式
        print("\(dog.name) can run \(dog.runSpeed)")
    case let bird as Bird: // as 模式
        print("\(bird.name) can fly \(bird.flightHeight)")
    case is Fish: // is 模式
        print("fish can swim")
    default:
        print("unknowen animal!")
    }
}

表达式模式(Expression Pattern)

表达式模式代表表达式的值。表达式模式只出现在switch语句的case标签中

表达式模式代表的表达式会使用Swift标准库中的 ~= 运算符与输入表达式的值进行比较。如果 ~= 运算符返回true,则匹配成功。默认情况下, ~=运算符使用==运算符来比较两个相同类型的值。他也可以将一个整型数值与一个Range实例中的一段整数区间做匹配

代码语言:javascript
复制
let point = (1, 2)
switch point {
case (0, 0):
    print("(0, 0) is an the origin")
case (-2...2, -2...2):
    print("(\(point.0), \(point.1)) is near the origin")
default:
    print("the point is at (\(point.0), \(point.1))")
}

前面我们提到,默认情况下,~= 运算符会使用==运算符来比较两个相同类型的值。接下来我们看一下如何通过重载~=运算符来提供自定义的表达式匹配行为

代码语言:javascript
复制
func ~= (pattern: String, value: Int) -> Bool {
    return pattern == "\(value)"
} // 第一个参数是模式Pattern,第二个参数是匹配的值

let point = (0, 0)
switch point {
case ("0", "0"):
    print("(0, 0) is an the origin")
case (-2...2, -2...2):
    print("(\(point.0), \(point.1)) is near the origin")
default:
    print("the point is at (\(point.0), \(point.1))")
}

自定义的类型默认是如法通过表达式模式进行匹配的,因此需要重载~=运算符

代码语言:javascript
复制
struct Employee {
    var salary: Float
}

let employee = Employee(salary: 15000)

// 重载~=运算符。第一个参数是模式,第二个参数是需要匹配的值
func ~=(pattern: Range<Float>, value: Employee) -> Bool {
    return pattern.contains(value.salary)
}
switch employee {
case 0.0..<5000:
    print("只够温饱")
case 5000..<10000:
    print("小康生活")
case 10000..<20000:
    print("比较滋润")
default:
    break
}
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-12-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 iOS小生活 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档