Day13 – Swift 基础总结,1

Swift —— 100 天从新手到大师
它的目标是那些想要学习如何构建真正的 iOS 应用程序的开发者
如果说的是你 —— 准备好了吗
我们将花费三天的时间,对之前 12 天的内容做一个总结。
在之前,我们讨论了很多知识点,也挖了一些坑,在这三天,我们将集中解决一下。

字符串

Swift 中的字符串是一个类型为

String
 的值类型结构体的字符集合,并以 

Unicode/UTF-8
 数据格式存储存储字符。

字符串索引

虽然我们将字符串定义为一个字符集合,但是与真正集合不同的是,字符串无法使用简单的数字索引下标来获取位置。
尽管我们可以使用循环遍历

let name = "iOS 成长指北"


for c in name {
print(c) // i, etc.
}

但是我们无法使用下标来获取第

n



个字符

name[3]
// 或者 characterAtIndex 方法
[name characterAtIndex:3]

在 Swift 中我们可以通过

let index = name.index(name.startIndex, offsetBy: 3)
print(name[index])

来获取某个索引位置的字符。


获取 索引位置 

3
 

的字符。

这是为什么呢?

Swift 字符串以 

Unicode/UTF-8
 数据格式存储存储字符,Swift 中的字符串使用多字节编码,还有一种称为




扩展字素簇




的东西,这意味着可以使用



 


可变数量的字节

 来





表示任何字符。



let string  = "\u{59}\u{65}\u{61}\u{68}\u{21}"


if string == "Yeah!" {
print(name) // iOS 成长指北
}

对于使用固定宽度编码,您可以将整个字符串除以字符的字长,以获取字符串中的字符数。而对于可变宽度编码,这是不可能的,因为每个字符的长度可以不同。

字符串插值(string interpolation)

常见的对于字符串的连接方法,我们使用 

+
 或者是 

+=
,当然,还有字符串自带的

.append()


let helloStr = "Hello, "
let worldStr = "World"
var result = helloStr + worldStr


result.append("!")


result += helloStr + worldStr + "!"


但是这种方法只针对 同字符串类型
的值,对于不同类型的值,则用 


\(···)
放在在字符串中插值。

let playerName = "Jack"
let playerScore = 99
let congratsMessage = "Congratulations \(playerName)!. Your highest score is \(playerScore)."

原始字符串

如果想在字符串中使用特殊字符或者使用 

\(...)
 或 

\
,除了转义字符以外,另一个方法是使用原始字符串。

let raw1 = #"Raw "No Escaping" \(no interpolation!). Use all the \ you want!"#
// Raw "No Escaping" \(no interpolation!). Use all the \ you want!

如果你现在字符串中插入一个 

#
 号,你可以这么做

let raw2 = ##"Aren’t we "# clever"##
// Aren’t we "# clever

当然你一样可以使用原始字符串配合字符串插值使用

let can = "can do that too"
let raw3 = #"Yes we \#(can)!"#
// Yes we can do that too

常见的集合对象

数组

数组是一个多个同类型值的集合。你可以使用数字索引进行快速访问。
在Swift中,不能像在其他编程语言中那样创建固定长度的数组。固定长度大小数组意味着,数组不能有多于初始化时定义的数量。

在 Swift 中使用下标语法


访问/修改

数组元素时,必须确保该值位于索引中,否则将导致运行时崩溃。

fatal error: Index out of range

数组元素从 0 开始编号,其下标范围是 

[0..<array.count]
,Swift 中的下标语法相当强大,它允许我们将下标 api 添加到自己的类型中,就像在数组字典中使用一样。

字典

常见的集合类型还有字典,不同于数组,因为字典允许基于指定的键访问值。

@frozen struct Dictionary where Key : Hashable

Swift 中的 Key 可以使指定任意符合 Hashable 的值,如字符串或数字。使用 Key 相应的值,该值可以是任何对象。
在枚举数组和字典索引和值的时候,有一个小技巧。

当我们需要知道数组或字典的索引和对应的值的话,我们可以使用

enumerated()
函数获取

对于集合对象的操作,我们将介绍几个高阶函数来做一些处理。

集合的高阶函数


map()



reduce()
 和 

filter()
 等函数来自函数式编程(FP)领域。它们被称为高阶函数,因为它们接受函数作为输入。在可选项一节中,我们提及了

map()


compactMap()
 和 

flatMap()
 。




使用高阶函数可以使代码更简洁,可读性和性能更高。对于集合里面 存储子项
的处理,我们倾向于使用高阶函数而不是使用循环语句。
我们将介绍几个常用的集合高阶函数。

map()

函数作用于集合中的所有对象。可以想象成将一组集合转成另一组新的集合。

let celsius:[Float] = [-5.0, 10.0, 21.0, 33.0, 50.0]


let fahrenheit = celsius.map {
$0 * (9/5) + 15
}


let fahrenheitRaw = celsius.map { (num: Float) -> Float in
return num * (9/5) + 15
}
print("fahrenheit = ", fahrenheit)
//原始用法
let fahrenheitRaw = celsius.map

对于字典来说,也可以使用 

map()
 来做一些处理,比如,为了获取字典的首字母大写的键值数组,我们可以这么做

  let amount = ["apple":30, "banana":50]
print(amount.keys.map { key in
return key.capitalized
})
print(amount.map { (key, value) in
return key.capitalized
})


// ["Banana", "Apple"]

注意,

map()
 函数的返回类型总是一个


泛型

数组。可以返回任何类型的数组。

filter()


filter()

函数只有一个参数指定 include 条件。这是一个闭包,它将集合中的元素作为参数,并且必须返回一个Bool,指示该项是否应包含在结果中。

下面我们通过一个例子,获取数组中可以整除2的数字——偶数

let values = [11, 13, 14, 6, 17, 21, 33, 22]


let even = values.filter { $0.isMultiple(of: 2) }


print(even) // 14, 6, 22

对于字典使用 

filter()
 函数将返回一个


元组

数组。

reduce()

函数的作用是:将一个集合转换为一个值。可以把它看作是将多个值组合成一个值,就像对一组数字求平均值一样。

当我们合并一个数组中的多个字符串时,除了使用 

+
 号 或字符串插值,我们还是以使用 

joined
 或 

reduce()


let values = ["a", "b", "c", "d", "e"]
// abcde
print(values.joined(separator: ""))


print(values.reduce("", { $0 + $1 }))


reduce()

函数有两个参数

  • 一个是


    初始值

    ,用于存储初始值或闭包从每次迭代返回的值或结果。

  • 另一个是带有两个参数的闭包,一个是初始值或闭包的上一次执行的结果,另一个是集合中的下一个对象。
    @inlinable public func reduce(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result


reduce()

 函数需要注意的是 


初始值


let values = [7, 3, 10]


let sum = values.reduce(0) { $0 + $1 }

初始值
 0 就是初始计算时的第一个参数 

$0
,所以与闭包内返回值的属性是一致的。整个流程如下

0 + 7
(0 + 7) + 3
((0 + 7) + 3) + 10

我们还可以使用其他基本操作符函数,例如 

+
 ,

-


*
 和 

/
来处理结果。



values.reduce(0,+) 

values.reduce(0) { $0 + $1 }


values.reduce(0,-)

values.reduce(0) { $0 - $1 }


values.reduce(1,*)

values.reduce(1) { $0 * $1 }


...

返回值的类型,同 


初始值

的类型是一致的。

flatmap()

flatmap()
函数用于扁平化集合的集合。但是在扁平化集合之前,我们可以对每个元素应用map。

官方文档对 flatmap()
是这么定义的

Returns an array containing the concatenated results of calling the given transformation with each element of this sequence.
返回一个数组,其中包含用该序列的每个元素调用给定转换的连接结果。


可以简单理解为是在
map() 函数加上



扁平化

操作。

let codes = [["abc", "def", "ghi"], ["abc", "def", "ghi"]]
let flat = codes.flatMap { $0 }
//["abc", "def", "ghi", "abc", "def", "ghi"]
let flat1 = codes.flatMap { $0.map { $0.uppercased() }}
// ["ABC", "DEF", "GHI", "ABC", "DEF", "GHI"]

如果是一个字符串数组,我们可以获取其中每个字符

let flat2 = codes.flatMap { $0.flatMap {$0.uppercased()}}
//["A", "B", "C", "D", "E", "F", "G", "H", "I", "A", "B", "C", "D", "E", "F", "G", "H", "I"]

使用 

flatmap()
 完成 

[[T]] -> [T]
 的合并操作


flatmap()

一些常见用法:

删除集合中的可选项


flatmap()

可以将集合中的可选项剔除。

当我们使用,编辑器或给我们提醒,推荐我们使用 

compactMap()
 函数

'flatMap' is deprecated: Please use compactMap(_:) for the case where closure returns an optional value

合并多个数组

使用 

flatmap()
 可以实现从 

[[T]]
 到 

[T]
 。


let onlyEven = collections.flatMap { $0.filter { $0 % 2 == 0 } }

compactMap()

在剔除集合中的可选项时,推荐使用 

compactMap()
 函数

let possibleNumbers = ["1", "2", "three", "///4///", "5"]


let mapped: [Int?] = possibleNumbers.map { str in Int(str) }
// [1, 2, nil, nil, 5]
let flatmapped = possibleNumbers.flatMap { ¥0 }
// [1, 2, 5]
let compactMapped: [Int] = possibleNumbers.compactMap { str in Int(str) }
// [1, 2, 5]

总结

使用高阶函数的目标是写


更少更有意义

的代码!!

我们可以使用函数链,组合这些函数。

Switch 语句

在 


《Day3 – 运算符和条件语句》

 一节中我们叙述了除了对枚举的以外的 Switch 语句的用法,值匹配,区间匹配以及元组和值绑定。今天我们将介绍一个新的 Switch 语句的用法。

具有关联值的枚举

Swift 最强大的特性之一是枚举可以关联任何定义的值。

enum Response {
case error(Int, String)
case success
}
let httpResponse = Response.error(300, "Network Error")
switch httpResponse {
case .error(let code, let status):
print("HTTP Error: \(code) \(status)")
case .success:
print("HTTP Request was successful")
}
// HTTP Error: 300 Network Error

我们可以通过枚举这种带有关联值的 

case
,来根据关联值值的不同进行不同的处理。当然,你也可以枚举 

case .error
 来处理错误项的枚举,比如你做数据埋点时,你只想知道网络的成功与否,而不像其他的。

Switch 模式匹配 —— where 关键字

当我们使用这种具有关联值的枚举时,我们可以同时定义多个同名的 

case
 —— 使用 

where
 关键字来处理当关联值符合某个条件时,执行某个

case



let httpResponse = Response.success
switch httpResponse {
case .error(let code, let status) where code > 399:
print("HTTP Error: \(code) \(status)")
case .error:
print("HTTP Request failed")
case .success:
print("HTTP Request was successful")
}


// Output: HTTP Request was successful

在使用时请注意 Swift 从上到下评估 

switch/case
,找到匹配项后立即停止。如果你使用 

case .error:
 或 

case .error(let code, let status):
 在 

where
 之上,则匹配行不会走到。



Swift 中的 where

除了在 Switch 语句使用 where 关键字以外,我们还可以在以下场景中使用。
where 是 Swift中一个强大的关键字,可以轻松过滤出值。它可以在许多不同的变体中使用。

在循环语句中使用 where

let primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]


for prime in primes where prime < 10 {
print(prime)
}

我们可以将 where 语句理解为一个语法糖,用来简化含有一个 if 的条件语句

for prime in primes {
if prime < 10 {
print(prime)
}
}

在拓展和协议中使用 where

用来表明当符合某种条件时

extension Array where Element == String {
func reverseAll() -> [String] {
return self.map { String($0.reversed()) }
}
}

高阶函数的 where

在 Swift 使用 where 的函数,还有以下这些
根据条件获取第一个元素。

let names = ["Henk", "John", "Jack"]
let firstJname = names.first(where: { (name) -> Bool in
return name.first == "J"
}) // Returns John

确定数组是否包含条件匹配元素。

let fruits = ["Banana", "Apple", "Kiwi"]
let containsBanana = fruits.contains(where: { (fruit) in
return fruit == "Banana"
}) // Returns true

在初始化的时候,可以为我们的类添加特定的方法

extension String {
init(collection: T) where T.Element == String {
self = collection.joined(separator: ",")
}
}


let clubs = String(collection: ["AJAX", "Barcelona", "PSG"])
print(clubs) // prints "AJAX, Barcelona, PSG"

感谢你阅读本文!:rocket:

本文的提纲来自于 hackingwithswift 的 《100 Days of Swift》,然后根据每一个知识点进行总结

欢迎点赞、转发、评论、在看。
如果有任何问题,欢迎留言交流