【《Pro Swift》阅读笔记】02 - 语法

书籍链接:《Pro Swift》 (链接需要梯子才能打得开)。

一、 final关键字

如果我们确定方法或者属性不应该被重写,请使用final标记。这可以让编译器用直接调用替换动态派发调用。

二、 样式匹配

1. switch

// 匹配tuple所有元素
switch authentication {
case ("bilbo", "bagg1n5"):
   print("Hello, Bilbo Baggins!")
case ("twostraws", "fr0st1es"):
   print("Hello, Paul Hudson!")
default:
   print("Who are you?")
}

// 匹配tuple的部分元素,使用`_`代替我们不关心的元素
let authentication = (name: "twostraws", password: "fr0st1es",
ipAddress: "127.0.0.1")
switch authentication {
case ("bilbo", "bagg1n5", _):
   print("Hello, Bilbo Baggins!")
case ("twostraws", "fr0st1es", _):
   print("Hello, Paul Hudson!")
default:
   print("Who are you?")
}

在匹配过程中,Swift只会执行第一个符合条件的case。例如下面这个例子,第一个case符合任何情况,只会执行第一个case。

switch authentication {
case (_, _, _):
   print("You could be anybody!")
case ("bilbo", "bagg1n5", _):
   print("Hello, Bilbo Baggins!")
case ("twostraws", "fr0st1es", _):
   print("Hello, Paul Hudson!")
default:
   print("Who are you?")
}

另外我们还可以使用let来获取tuple的元素值:

switch authentication {
case ("bilbo", "bagg1n5", _):
   print("Hello, Bilbo Baggins!")
case ("twostraws", let password, _):
   print("Hello, Paul Hudson: your password was \(password)!")
default:
   print("Who are you?")
}

同时我们还可以传入一个动态计算的tuple:

func fizzbuzz(number: Int) -> String {
   switch (number % 3 == 0, number % 5 == 0) {
   case (true, false):
      return "Fizz"
   case (false, true):
      return "Buzz"
   case (true, true):
      return "FizzBuzz"
   case (false, false):
      return String(number)
   }
}
print(fizzbuzz(number: 15))

2. 循环

let twostraws = (name: "twostraws", password: "fr0st1es")
let bilbo = (name: "bilbo", password: "bagg1n5")
let taylor = (name: "taylor", password: "fr0st1es")

let users = [twostraws, bilbo, Taylor]

for case ("twostraws", "fr0st1es") in users {
   print("User twostraws has the password fr0st1es")
}

使用let得到当前user的name和password:

for case (let name, let password) in users {
   print("User \(name) has the password \(password)")
}

// 还可以简写成这样:
for case let (name, password) in users {
   print("User \(name) has the password \(password)")
}

还可以和case结合起来:

for case let (name, "fr0st1es") in users {
   print("User \(name) has the password \"fr0st1es\"")
}

只有密码是fr0st1es的元素,才会执行里面的语句。

3. 匹配可选类型

使用.some.none来匹配有值和没有值:

let name: String? = "twostraws"
let password: String? = "fr0st1es"
switch (name, password) {
case let (.some(name), .some(password)):
   print("Hello, \(name)") // 这里的name不是第一行的变量name,而是已经被`.some()`unwrapped了的name
case let (.some(name), .none):
   print("Please enter a password.")
default:
   print("Who are you?")
}

使用问号?

switch (name, password) {
case let (name?, password?):
   print("Hello, \(name)")
case let (username?, nil):
   print("Please enter a password.")
default:
   print("Who are you?")
}

这个效果跟第一个例子一样,.some()等同于?.none等同于nil

.some?同样也可以在for-case-let中使用:

let data: [Any?] = ["Bill", nil, 69, "Ted"]

for case let .some(datum) in data {
   print(datum)
}

for case let datum? in data {
   print(datum)
}

4. 匹配范围

使用switch

let age = 36
switch age {
case 0 ..< 18:
   print("You have the energy and time, but not the money")
case 18 ..< 70:
   print("You have the energy and money, but not the time")
default:
   print("You have the time and money, but not the energy")
}

使用if-else:

if case 0 ..< 18 = age {
   print("You have the energy and time, but not the money")
} else if case 18 ..< 70 = age {
   print("You have the energy and money, but not the time")
} else {
   print("You have the time and money, but not the energy")
}

我们还可以用~=if-case改写成:

if 0 ..< 18 ~= age {
   print("You have the energy and time, but not the money")
} else if 18 ..< 70 ~= age {
   print("You have the energy and money, but not the time")
} else {
   print("You have the time and money, but not the energy")
}

再者,0 ..< 18其实是一个Range实例,可以调用contains()方法,改为这样可以更容易理解:

if (0 ..< 18).contains(age) {
   print("You have the energy and time, but not the money")
} else if (18 ..< 70).contains(age) {
   print("You have the energy and money, but not the time")
} else {
   print("You have the time and money, but not the energy")
}

把之前提到的例子结合起来:

let user = (name: "twostraws", password: "fr0st1es", age: 36)

switch user {
case let (name, _, 0 ..< 18):
   print("\(name) has the energy and time, but no money")
case let (name, _, 18 ..< 70):
   print("\(name) has the money and energy, but no time")
case let (name, _, _):
   print("\(name) has the time and money, but no energy")
}

5. 匹配枚举及其关联值

匹配枚举及其关联值:

enum WeatherType {
   case cloudy(coverage: Int)
   case sunny
   case windy
}
let today = WeatherType.cloudy(coverage: 100)
switch today {
case .cloudy(let coverage):
   print("It's cloudy with \(coverage)% coverage")
case .windy:
   print("It's windy")
default:
   print("It's sunny")
}

使用where来匹配关联值的范围:

switch today {
case .cloudy(let coverage) where coverage < 100:
   print("It's cloudy with \(coverage)% coverage")
case .cloudy(let coverage) where coverage == 100:
   print("You must live in the UK")
case .windy:
   print("It's windy")
default:
   print("It's sunny")
}

还可以用Range来匹配范围:

switch today {
case .cloudy(let coverage) where coverage == 0:
   print("You must live in Death Valley")
case .cloudy(let coverage) where (1...50).contains(coverage):
   print("It's a bit cloudy, with \(coverage)% coverage")
case .cloudy(let coverage) where (51...99).contains(coverage):
   print("It's very cloudy, with \(coverage)% coverage")
case .cloudy(let coverage) where coverage == 100:
   print("You must live in the UK")
case .windy:
   print("It's windy")
default:
   print("It's sunny")
}

在for循环中匹配枚举:

let forecast: [WeatherType] = [.cloudy(coverage: 40), .sunny, .windy, .cloudy(coverage: 100), .sunny]
for case let .cloudy(coverage) in forecast {
   print("It's cloudy with \(coverage)% coverage")
}

// 或者

let forecast: [WeatherType] = [.cloudy(coverage: 40), .sunny, .windy, .cloudy(coverage: 100), .sunny]
for case .cloudy(40) in forecast {
   print("It's cloudy with 40% coverage")
www.hackingwithswift.com 23
}

6. 匹配类型

使用is进行类型匹配:

let view: AnyObject = UIButton()
switch view {
case is UIButton:
   print("Found a button")
case is UILabel:
   print("Found a label")
case is UISwitch:
   print("Found a switch")
case is UIView:
   print("Found a view")
default:
   print("Found something else")
}

在一个view数组中查找某个类型的view:

for label in view.subviews where label is UILabel {
   print("Found a label with frame \(label.frame)")
}

上面这个例子没有把label转换成UILabel,我们可以使用下面这种方式:

for case let label as UILabel in view.subviews {
   print("Found a label with text \(label.text)")
}

三、 where关键字的使用

where后面可以接条件语句:

let celebrities: [String?] = ["Michael Jackson", nil, "Michael
Caine", nil, "Michael Jordan"]
for name in celebrities where name != nil {
   print(name)
}

// 结果
Optional("Michael Jackson")
Optional("Michael Caine")
Optional("Michael Jordan")

// 可以使用下面这种形式直接在for语句中unwrap:
for case let name? in celebrities {
   print(name)
}

// 结果
Michael Jackson
Michael Caine
Michael Jordan

四、 nil的处理

使用??来unwrapp一个可选类型:

let name: String? = "Taylor"
let unwrappedName = name ?? "Anonymous" // 如果name是nil,则unwrappedName为Anonymous;如果不为nil,则unwrappedName为name被unwrapped后的值
print(unwrappedName)

使用try?对可能抛出错误进行处理:

//如果使用do-catch
let savedText: String
do {
   savedText = try String(contentsOfFile: "saved.txt")
} catch {
   print("Failed to load saved text.")
   savedText = "Hello, world!"
}

//使用try?可以让代码更简洁
let savedText = (try? String(contentsOfFile: "saved.txt")) ??
"Hello, world!"
print(savedText)

五、 guard的使用

guard一般用于要确保满足一定的条件下才能继续向下执行的情况:

func giveAward(to name: String) {
   guard name == "Taylor Swift" else { // 这个guard语句我们可以这么读:确保name是"Taylor Swift",否则返回
      print("No way!")
      return
}
   print("Congratulations, \(name)!")
}

// 如果使用if-else,代码没那么简洁
func giveAward(to name: String) -> String {
   let message: String
   if name == "Taylor Swift" {
      message = "Congratulations, \(name)!"
   } else {
      message = "No way!"
}
   return message
}

很明显,使用guard会简洁易懂。

使用guard来unwrap可选类型:

func giveAward(to name: String?) {
   guard let winner = name else {
      print("No one won the award")
      return
}   
  print("Congratulations, \(winner)!")
}

六、 lazy的使用

lazy修饰的代码,只有在用到的时候才会被执行,并且只执行一次。

  1. 使用closure和lazy初始化属性:
lazy var yourVariableName: SomeType = {
   return SomeType(whatever: "foobar")
}()

这种写法不仅可以提高应用的性能,而且把初始化的相关代码都组织在了一起,这样代码不会很乱。如果在closure里面使用了self,也不会造成循环引用的问题,我们可以放心使用。注意:如果这个属性没有被lazy修饰,在closure里面是不能使用self的,因为这时self还没有初始化完成,不能调用self

如果我们觉得用closure的形式初始化属性把属性和closure放在了一起,属性看起来不够简洁,我们可以使用下面这种方式:

class Singer {
   let name: String
   init(name: String) {
      self.name = name
  }
   lazy var reversedName: String = self.getReversedName()
   
   private func getReversedName() -> String {
      return "\(name.uppercased()) backwards is \(String(name.uppercased().reversed()))!"
  } 
}
  1. lazy在序列中的应用

举个例子,我们写一个函数来计算斐波那契数列中某个位置的数字是多少。什么是斐波那契数列?简单的说就是后面一个数是前面两个数的和,大家可以点击查看。

函数如下图,fibonacci(of num: Int)是求某个位置的数字的函数,fibonacciSequence是斐波那契数列中第0到21的数字组成的数组,然后打印第11个数字。

红色框的两个数字的和,就是运行下面两行代码时fibonacci(of num: Int)的总运行次数。

let fibonacciSequence = (0...20).map(fibonacci)
print(fibonacciSequence[10])
没有使用lazy

如果我们使用了lazy,只需要(89 + 88)次,可说是大大提高了性能。

使用了lazy

使用了lazy之后,当我们执行let lazyFibonacciSequence = Array(0...199).lazy.map(fibonacci)时,实际上是没有进行运算的,而是调用了print(lazyFibonacciSequence[10])之后,才会进行真正的运算,并且只会计算第11个数。

但是我们要注意的一点时,在序列中使用lazy时,不会有记忆的功能,也就是说,我们重复调用print(lazyFibonacciSequence[10])时,fibonacci(of num: Int)的总运行次数会翻倍。

重复调用

七、 解构 (Destructing)

何为解构?熟悉JavaScript的同学应该知道这个词的含义。在JavaScript中,如果一个Person类有nameage两个属性,我们可以用解构来获取Person实例的属性:

const { name, age } = lebronJames;

类似地,在Swift中,可用解构来获取tuple中的值:

let data = ("one", "two", "three")
let (one, two, three) = data

如果我们不想要其中的某个值,用_代替:

let (_, two, three) = data

tuple的解构在交换两个值的时候非常有用。在面试中,有可能出现过这样的问题:在不使用第三个变量的情况下,如果交换两个数字?

第一种方法:

var a = 10
var b = 20

a=a+b
b=a-b 
a=a-b

print(a)
print(b)

第二种方法,在Swift中,利用tuple解构一行代码搞定:

(b, a) = (a, b)

八、 标签

在Swift中,我们可以给for循环或者if语句加标签。

1. for循环

举个例子,我们要在一个二维字符串数组中找到一个字符串x,我们可能会使用下面的代码:

// 初始化二维数组
var board = [[String]](repeating: [String](repeating: "", count: 10), count: 5)

// 把board[3][5]设置为我们要找的字符串`x`
board[3][5] = "x"

for (rowIndex, cols) in board.enumerated() {
   for (colIndex, col) in cols.enumerated() {
      if col == "x" {
         print("Found the `x` at row \(rowIndex) col \(colIndex)!")
      }
  } 
}

我们都知道,上面的代码不够好,因为当找到x后,两个for循环还会继续运行,我们可以加个break:

for (rowIndex, cols) in board.enumerated() {
   for (colIndex, col) in cols.enumerated() {
      if col == "x" {
         print("Found the `x` at row \(rowIndex) col \(colIndex)!")
         break
      }
  } 
}

但是代码还是有问题,找到x之后,里面的循环停止,但是外面的循环还是会继续执行。通常的做法,我们会想到加个临时变量,记录是否找到了x,将代码改为:

var hasFoundX = false

for (rowIndex, cols) in board.enumerated() {
  guard !hasFoundX else { break }
  
   for (colIndex, col) in cols.enumerated() {
      if col == "x" {
         print("Found the `x` at row \(rowIndex) col \(colIndex)!")
         hasFoundX = true
         break
      }
  } 
}

当我们使用Swift的标签特性之后,代码变得简单很多:

rowLoop: for (rowIndex, cols) in board.enumerated() {
  for (colIndex, col) in cols.enumerated() {
      if col == "x" {
         print("Found the treasure at row \(rowIndex) col \
(colIndex)!")
         break rowLoop
      } 
  }
}

2. if语句

有这么一个例子,需要满足很多个条件之后,才能运行print("Printed successfully!")

if userRequestedPrint() {
    if documentSaved() {
        if userAuthenticated() {
            if connectToNetwork() {
                if uploadDocument("resignation.doc") {
                    if printDocument() {
                        print("Printed successfully!")
                    }
                } }
        } }
}

上面的代码非常难看,使用了标签之后,清晰易懂:

printing: if userRequestedPrint() {
    if !documentSaved() { break printing }
    if !userAuthenticated() { break printing }
    if !connectToNetwork() { break printing }
    www.hackingwithswift.com 48
    if !uploadDocument("work.doc") { break printing }
    if !printDocument() { break printing }
    print("Printed successfully!")
}

// 我们还可以用guard
printing: if userRequestedPrint() {
    guard documentSaved() else { break printing }
    guard userAuthenticated() else { break printing }
    guard connectToNetwork() else { break printing }
    guard uploadDocument("work.doc") else { break printing }
    guard printDocument() else { break printing }
    print("Printed successfully!")
}

对于上面两种写法,我个人更喜欢用guard这种形式,因为guard后面接的是肯定的语句,而if后面接的是取反的语句,没那么好理解。

有任何问题,欢迎大家留言!

欢迎加入我管理的Swift开发群:536353151,本群只讨论Swift相关内容。

原创文章,转载请注明出处。谢谢!

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