最近看了两本书, 第一本是Objc.io出的Advanced Swift和王巍大大出的Swifter tips. 后者在作者的博客上有更新, 应该已经更新完了吧.
这里列出一些看过了笔记, 也是比较粗糙, 只是大概都过了一遍, 把一些之前遗漏或者有错漏的地方补全了.
一. Collection
1.值类型的赋值虽然是copy, 但是实际上会是"copy on write”, 只有在真正发生写操作的时候才会进行修改;
2.需要用for循环遍历的时候考虑一下map函数, 它可以使得代码更加简洁, 同时可以让原本不得不为var的变量变为let;3.为了避免不必要的copy, 数组的范围下标访问返回的集合是ArraySlice<T>类型的, 它有与数组一样的方法, 所以可以被当做数组, 如果实在需要转换就用Array(slice)来构造一个;
需要注意的是, 切面可以增加数组元素, 但是不能修改, 如下面的代码:
var array = ["1","2","3"]
var slices = array[1..<array.endIndex]
slices[0] = “a” // crash
slices.append("4”) // OK
// 存疑:
array[1] = “22” // 执行后slices并没有响应的变化, 所以, 对内部实现还是有疑问
4.Dictionary的updateValue方法可以返回旧值5.GeneratorType协议由于其只能被访问一次, 因此其实现者最好是class而不是struct6.对集合的访问尽量不要用subscript, 可以用for-in和map, filter等高阶函数实现, 几个”特殊"的情况:1). 对下标的遍历: for idx in collection.indices2). 对下标和元素的遍历: for (idx, element) in collection.enumerate()7.严格说来[1,2,3]这样的值, 它的类型不是Array, 而是ArrayLiteral, 只是因为Array实现了ArrayLiteralConvertible协议, 所以才能通过这样的字面值来构造Array, 同样的还有DictionaryLiteralConvertible, IntegerLiteralConvertible,StringLiteralConvertible等等8.collection的starIndex不一定从0开始的, 所以写C风格循环的时候不要var i = 0, 而应该是var i = collection.startIndex
9.case的模式匹配:
for case let variable? in mayNilCollection{
}
等同于:
for let variable in mayNilCollection where variable != nil {
}
同时
for case nil in mayNilCollection {
}
可以帮你找出集合中有多少个nil
同时case还可以和if一起用:
let j = 5
if case 0..<10 = j {
}
- lazy修饰符:
lazy可以对属性进行修饰, 类似于ObjC里面的手动控制一样. 同时Swift里面的标准库也提供了一套lazy方法, 用起来是这样的:
let data = 1...3
let result = data.lazy.map { (i: Int) -> Int in
print("正在处理 \(i)")
return i * 2
}
print("准备访问结果")
for i in result {
print("操作后结果为 \(i)")
}
print("操作完毕")
//输出结果为:
// 准备访问结果
// 正在处理 1
// 操作后结果为 2
// 正在处理 2
// 操作后结果为 4
// 正在处理 3
// 操作后结果为 6
// 操作完毕
可见, 使用lazy可以把最后的计算拖到真正需要的时刻再进行. 这种优化非常适合在result遍历的时候, 有需要退出的情况.
二. Optional
1.guard比if好的地方: guard let的变量可以被外部访问, 避免了if金字塔结构; guard的语义更加鲜明, 就是为了检查返回失败条件的
- 关于optional chaining的结果也是optional的解释:
let j = Int(“1”)?.successor().successor() // 为什么第二个successor()不需要加?来调用
原因在于结果是optional, 而不是中间状态也是optional, 对于上面的语句来说, optional的状态是已经captured, 所以后续的方法调用只要不是前一个方法本身返回optional, 就不需要写?, 举一个例证:
let j = Int(“1”)?.successor() // 这里把optional传递给了结果, 所以下面的语句需要加?
let k = j?.successor()
- 关于??操作符的解释:
??操作符的作用基本可以等效为ObjC里面的?:操作符. 但是参考以下情况:
let i: Int? = nil
let j: Int? = nil
let k: Int? = 42
let m = i ?? j ?? k ?? 0
let n = i ?? j ?? k
print(“\(m)”) // 说明m的类型是Int
print("\(n!)”) // 说明n的类型是Int?
m和n的区别说明, 对于编译器来说, 不i j k是否为nil, m是可以确定不为nil的, 所以m不会为optional, 但是n则不一定. 这也算是optional的智能之处
另外, 用??来替代||和&&
if let n = i ?? j {
// 类似于 if i != nil || j != nil
}
if let n = i, m = j {
// 类似于 if i != nil && j != nil
}
主要注意的是, ??操作符要和多层optional一起使用的时候要特别注意, 如:
let s1: String?? = nil
(s1 ?? "inner") ?? “outer” // 返回 “inner"
let s2: String?? = .Some(nil)
(s2 ?? "inner") ?? “outer” // 返回 “outer"
4.Optional的map与flatMap
let stringNumbers = ["a","1", "2", "3"]
let x = stringNumbers.first.map{Int($0)} // x的类型为Int??
let y = stringNumbers.first.flatMap{Int($0)} // y的类型为Int?
- Optional类型转换
在Optional值与非Optional值进行比较的时候, Swift会对非Optional值进行类型转换, 以使两者类型匹配, 再进行比较. 这个特性被广泛使用, 例如下面的代码:
let strArray = [“1”, “2”, “3"]
let result = strArray.first.map{$0}
result的类型会是String?, 但是map中返回的类型是String, 所以是由Swift的编译器为我们进行了类型转换.
6.强制解包
无论出于什么样的目的这么干, 一定要反思一下, 是否已经没有更好的选择了? 这么写之后最好能留下注释解释为什么.
7.隐式Optional
隐式Optional本质上还是Optional, 只是在使用的时候不需要再解包而已, 声明如下:
var s :Int! = nil
print(“(s)”) // 不会报错
一般情况下, 会在那些的确有可能为空, 但是一旦初始化完成之后就不会为空的变量使用此类型. 常见于某些提供了可能失败的构造器的情况.
需要注意的是, 如果隐式Optional为nil, 对它进行的任何操作同样会造成运行时error.
最后提一下Monad和Factor吧. 这个唐巧在自己的博客上更新了很多相关内容, 貌似最新的Qcon讲的也是这个. 这个东西理解起来的确很难, 但其实也就是2个概念, 对于这2个概念的解释, 我觉得唐巧的博客里面讲的很细致, 一起分享给大家我认为最核心的部分:
- 主要理解什么叫「封装过的值」, 值被包含在容器中或者被Optional包含, 都可以理解为封装过的值.
- flatMap和map都是处理封装过的值, 前者的闭包接受一个「未封装的值」,返回一个「封装后的值」, 后者闭包接受一个「未封装的值」,返回一个「未封装的值」.
理解起来是比较绕, 但是一般来说看看源码, 知道这2个高级函数有什么用就会容易理解很多.
另外, Advanced Swift这本书还是挺值得看的, 很多内容的确比较新颖, 会看到很多国内的大牛都讲了里面的一部分东西, 这里面算是集大成之作. 后续的几章因为暂时团队还没有用Swift开发, 所以还没看, 对类和结构体的那一章也是老生常谈, 如果发现有新大陆会更新出来.