├── .DS_Store ├── .gitignore ├── DataStructureAlgorithm ├── Datastructure │ └── 初识链表.md ├── 排序算法.md ├── 每日算法练习.md └── 链表算法总结.md ├── Interview ├── bytedance.md ├── interview-network.md ├── interview1.md └── 新东方.md ├── Network ├── 网络是怎样连接的.md └── 计算机网络自顶向下.md ├── OperationSystem ├── 对递归的理解.md ├── 移动端内存管理机制的理解.md └── 除法与右移.md ├── README.md ├── ReadBookNote ├── .DS_Store ├── 一生自在.md ├── 代码大全.md ├── 浪潮之巅.md ├── 程序是怎样跑起来的.md ├── 计算机是怎样跑起来的.md └── 重构.md ├── iOS ├── .DS_Store ├── Objective-C │ ├── Objective-C 高级编程:iOS 与 OS X 多线程和内存管理.md │ └── 如何使用断点来判断ViewController内存泄漏.md ├── Swift │ ├── SHA256-Swift.md │ ├── Swift 中的 Struct 与 Class.md │ ├── Swift 什么情况会发生内存访问冲突.md │ ├── Swift 基本类型所占字节数.md │ ├── Swift 性能 │ │ └── Copy On Write.md │ ├── Swift 的属性观察者.md │ ├── Type Erase in Swift.md │ ├── final 关键字的理解.md │ ├── 关于 Optional 的一些实践.md │ ├── 关于 Swift 访问控制的总结.md │ ├── 初识 Scanner.md │ ├── 如何迭代 Swift 中枚举的所有case.md │ ├── 快速了解 Swift.md │ ├── 读 Swift 源码系列 │ │ ├── Algorithm.md │ │ ├── Array.md │ │ ├── ArrayBody.md │ │ ├── ArrayBuffer.md │ │ ├── BetterCodable 源码阅读.md │ │ ├── Comparable.md │ │ ├── Delegated 源码阅读.md │ │ ├── NSCache 从入门到源码.md │ │ ├── Print.md │ │ ├── Reverse.md │ │ ├── Zip.md │ │ └── 图片下载 - Kingfisher.md │ └── 通过 Property Wrappers 简化代码.md ├── UI │ └── 通过functionBuilder 简化 UIAlertController 的调用.md └── WWDC │ ├── WWDC2019 │ ├── Advances in Foundation.md │ └── Introduce Combine.md │ └── WWDC2020 │ ├── Eliminate animation hitches with XCTest.md │ ├── Embrace Swift Type Inference.md │ ├── Lists in UICollectionView.md │ ├── unsafe_swift.md │ └── why is my app getting killed.md └── images ├── Array-capacity.png ├── c-swift-map.png ├── embrace_swift_type_inference_after.png ├── embrace_swift_type_inference_before.png ├── new_api.png └── new_metrickit_api.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengzhihao123/FZHBlog/23c8cc51470ad7981dc0154ce7bddac332513ba1/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | -------------------------------------------------------------------------------- /DataStructureAlgorithm/Datastructure/初识链表.md: -------------------------------------------------------------------------------- 1 | ### 初识链表 2 | 链表:链式结构,使用节点来存储内容,通过当前节点的 next 指针指向下一个节点。可以通过分散的内存来存储节点,因此内存利用率更高,占用内存空间更小。但因为只有通过 next 指针才能找到下一个节点,所以每次查找节点都需要从头结点开始。 3 | 4 | 因为 next 指针的关系,它的查找效率和修改的效率不如数组,但添加和删除的效率要高于数组。 5 | 6 | ``` 7 | 结构图: 8 | 9 | (Node) (Node) (Node) 10 | ----------- ----------- ----------- 11 | | | | | | | 12 | head--> | value | ---> | value | ---> | value | --> nil 13 | | | | | | | 14 | ----------- ----------- ----------- 15 | ``` 16 | #### 定义一个链表 17 | ``` 18 | class Node { 19 | var val: Int 20 | var next: Node? 21 | 22 | init(val: Int, next: Node?) { 23 | self.val = val 24 | self.next = next 25 | } 26 | } 27 | 28 | class LinkList { 29 | var dummy: Node? 30 | var size: Int 31 | 32 | init() { 33 | dummy = Node(val: 0, next: nil) 34 | size = 0 35 | } 36 | 37 | private func node(_ index: Int) -> Node? { 38 | var curNode = dummy?.next 39 | var curIndex = index 40 | 41 | while curIndex > 0 { 42 | curNode = curNode?.next 43 | curIndex -= 1 44 | } 45 | return curNode 46 | } 47 | } 48 | ``` 49 | 首先声明一个包含 val 和 next 的 Node 类。val 用来存储值,而 next 用来指向下一个节点。 50 | 51 | 然后再声明一个名 LinkList 类来代表链表。dummy 的 next 用来指向头结点,size 用来存储链表的当前长度。 52 | 53 | 这里需要说明的是,为什么要设置 dummy 节点。因为这样做是可以避免在添加第一个节点时的很多逻辑判断。 54 | 55 | 最后,创建一个 node(_:) 的私有函数,用来获取当前 index 的节点。 56 | 57 | #### 添加 58 | ``` 59 | public func append(val: Int) { 60 | let newNode = Node(val: val, next: nil) 61 | 62 | if size == 0 { 63 | dummy?.next = newNode 64 | } else { 65 | var curSize = size 66 | var curNode = dummy 67 | 68 | while curSize > 0 { 69 | curNode = curNode?.next 70 | curSize -= 1 71 | } 72 | curNode?.next = newNode 73 | } 74 | 75 | size += 1 76 | } 77 | ``` 78 | 在尾部添加节点有以下两种情况: 79 | * condition 1:size == 0,说明没有链表未保存节点。 80 | * condition 2:size > 0,链表已经保存节点,只需要找到尾结点即可。 81 | 在 condition 1 的情况下,将 dummy 的 next 指向 newNode 即可,而在 condition 2 的情况下,寻找到尾结点,将尾结点的 next 指向 newNode。 82 | 83 | `最后,不要忘记将 size + 1。` 84 | 85 | #### 删除 86 | ``` 87 | public func remove(_ index: Int) -> Int? { 88 | guard index > -1 && index < size else { return nil } 89 | let val: Int 90 | if index == 0 { 91 | val = dummy?.next?.val ?? 0 92 | dummy?.next = dummy?.next?.next 93 | } else { 94 | let prev = node(index - 1) 95 | val = prev?.next?.val ?? 0 96 | prev?.next = prev?.next?.next 97 | } 98 | size -= 1 99 | return val 100 | } 101 | ``` 102 | 进行移除操作时,需要对 index 的`合法性`进行校验。 103 | 104 | 移除操作同添加操作也是有以下两种情况: 105 | * condition 1:移除头结点。 106 | * condition 2:移除其它节点。 107 | 108 | 移除操作的核心就是找到需要移除节点的前一个节点 prev(通过 node(_:) 可以获得),将 prev 的 next 指针指向它 next 的 next 即可。如:`prev?.next = prev?.next?.next`。 109 | 110 | 最后不要忘记 size - 1 。 111 | 112 | #### 插入 113 | ``` 114 | public func insert(_ val: Int, atIndex index: Int) { 115 | guard index > -1 && index <= size else { return } 116 | let newNode = Node(val: val, next: nil) 117 | 118 | if index == 0 { 119 | newNode.next = dummy?.next 120 | dummy?.next = newNode 121 | } else { 122 | let pre = node(index - 1) 123 | newNode.next = pre?.next 124 | pre?.next = newNode 125 | } 126 | 127 | size += 1 128 | } 129 | ``` 130 | 插入操作相对来说是比较复杂的一个操作,要考虑好 index == 0,index == size 等条件的情况。 131 | 132 | 插入操作的核心操作分两步: 133 | * 将新建节点 newNode 的 next 指向当前 index 的节点。 134 | * 将当前节点的 prev 节点的 next 指向新建节点 newNode。 135 | 136 | 以上两步的顺序很重要,切记不要搞错。 137 | 138 | 通过上面的步骤来分析插入操作的代码。当 index == 0 时,说明要在头结点的位置进行插入,首先进行第一步:将 newNode 的 next 指向当前 index 的节点 - `newNode.next = dummy?.next`;接着进行第二步:将当前节点的 prev 节点的 next 指向新建节点 newNode - `dummy?.next = newNode`。 139 | 140 | 当 index > 0 时同上。最后不要忘记 size + 1 。 141 | 142 | #### 查询 143 | ``` 144 | public func get(_ index: Int) -> Int? { 145 | guard index > -1 && index < size - 1 else { return nil } 146 | return node(index)?.val 147 | } 148 | ``` 149 | 150 | 查询操作是最简单的一个。只需要判断下 index 的有效性,然后通过 node(_:) 获取相应的节点即可。 151 | 152 | 以上,就是链表基本操作的实现。 153 | 154 | ### 扩展 155 | #### 支持泛型 156 | 上面的示例代码只支持存储 Int 类型的数据,而在项目中可能需要支持多种类型,所以将其改成支持泛型还是很有必要的。 157 | 158 | ``` 159 | class Node { 160 | var val: Element? 161 | var next: Node? 162 | 163 | init(val: Element?, next: Node?) { 164 | self.val = val 165 | self.next = next 166 | } 167 | } 168 | 169 | class LinkList { 170 | var dummy: Node? 171 | var size: Int 172 | 173 | public init() { 174 | dummy = Node(val: nil, next: nil) 175 | size = 0 176 | } 177 | 178 | public func append(val: Element) { 179 | let newNode = Node(val: val, next: nil) 180 | 181 | if size == 0 { 182 | dummy?.next = newNode 183 | } else { 184 | // 查找尾结点 185 | var curSize = size 186 | var curNode = dummy 187 | 188 | while curSize > 0 { 189 | curNode = curNode?.next 190 | curSize -= 1 191 | } 192 | curNode?.next = newNode 193 | } 194 | 195 | size += 1 196 | } 197 | 198 | public func remove(_ index: Int) -> Element? { 199 | guard index > -1 && index < size else { return nil } 200 | let val: Element? 201 | if index == 0 { 202 | val = dummy?.next?.val 203 | dummy?.next = dummy?.next?.next 204 | } else { 205 | let prev = node(index - 1) 206 | val = prev?.next?.val 207 | prev?.next = prev?.next?.next 208 | } 209 | size -= 1 210 | return val 211 | } 212 | 213 | public func get(_ index: Int) -> Element? { 214 | guard index > -1 && index < size - 1 else { return nil } 215 | return node(index)?.val 216 | } 217 | 218 | public func insert(_ val: Element, atIndex index: Int) { 219 | guard index > -1 && index <= size else { return } 220 | let newNode = Node(val: val, next: nil) 221 | 222 | if index == 0 { 223 | newNode.next = dummy?.next 224 | dummy?.next = newNode 225 | } else { 226 | let pre = node(index - 1) 227 | newNode.next = pre?.next 228 | pre?.next = newNode 229 | } 230 | 231 | size += 1 232 | } 233 | 234 | private func node(_ index: Int) -> Node? { 235 | var curNode = dummy?.next 236 | var curIndex = index 237 | 238 | while curIndex > 0 { 239 | curNode = curNode?.next 240 | curIndex -= 1 241 | } 242 | return curNode 243 | } 244 | } 245 | 246 | ``` 247 | 248 | #### 支持索引 249 | 通过重写 subscript 也可以支持索引操作。 250 | ``` 251 | extension LinkList { 252 | public subscript(index: Int) -> Element? { 253 | return node(index)?.val 254 | } 255 | } 256 | ``` -------------------------------------------------------------------------------- /DataStructureAlgorithm/排序算法.md: -------------------------------------------------------------------------------- 1 | 本文共介绍以下几种排序:冒泡排序、插入排序、选择排序、快速排序、堆排序、计数排序、桶排序。 2 | 3 | ### 冒泡排序 4 | 思路:两个循环,一个外循环遍历集合中的所有元素,内循环从集合的头部开始,两两比较元素大小,降序将小的值后置,升序将大的值后置。一次遍历则可确定集合中最小值或者最大值。然后再通过外循环重复上述步骤从而得到有序的集合。因类似水中冒泡,故叫做冒泡排序。 5 | ``` 6 | func bubbleSort(arr: [Int]) -> [Int] { 7 | var result = arr 8 | 9 | for outIdx in stride(from: result.count - 1, through: 0, by: -1) { 10 | for inIdx in 0.. result[inIdx + 1] { 12 | result.swapAt(inIdx, inIdx + 1) 13 | } 14 | } 15 | } 16 | return result 17 | } 18 | 19 | var arr = [Int]() 20 | for _ in 0...10 { 21 | arr.append(Int.random(in: 1...10)) 22 | } 23 | print(arr) // [3, 8, 5, 7, 3, 4, 10, 1, 9, 3, 7] 24 | let res = bubbleSort(arr: arr) 25 | print(res) // [1, 3, 3, 3, 4, 5, 7, 7, 8, 9, 10] 26 | ``` 27 | `优化1:`在遍历过程中,如果集合已经全部元素有序,则没必要再进行后面的遍历。 28 | ``` 29 | func bubbleSort1(arr: [Int]) -> [Int] { 30 | var result = arr 31 | 32 | for outIdx in stride(from: result.count - 1, through: 0, by: -1) { 33 | var alreadySorted = true 34 | 35 | for inIdx in 0.. result[inIdx + 1] { 37 | alreadySorted = false 38 | result.swapAt(inIdx, inIdx + 1) 39 | } 40 | } 41 | if alreadySorted { 42 | break 43 | } 44 | } 45 | return result 46 | } 47 | ``` 48 | `优化2:`在遍历过程中,集合中部分有序的元素也没必要再进行比较。 49 | ``` 50 | func bubbleSort2(arr: [Int]) -> [Int] { 51 | var result = arr 52 | var lastExchangeIndex = 0 53 | var sortedBorder = result.count - 1 54 | 55 | for _ in stride(from: result.count - 1, through: 0, by: -1) { 56 | var alreadySorted = true 57 | for inIdx in 0.. result[inIdx + 1] { 59 | alreadySorted = false 60 | lastExchangeIndex = inIdx 61 | result.swapAt(inIdx, inIdx + 1) 62 | } 63 | } 64 | sortedBorder = lastExchangeIndex 65 | if alreadySorted { 66 | break 67 | } 68 | } 69 | return result 70 | } 71 | ``` 72 | 73 | 测试:测试标本为10000个随机数的数组,测试环境为 MAC 上模拟器。测试结果如下: 74 | * 未优化版本耗时为37-45s之间。 75 | * 优化1 比未优化版本节省 1-2s,优化2 比优化1 节省 0.5-1s。 76 | 77 | 未优化版本 - 优点代码少,出错率低;缺点:耗时长。 78 | 优化1、2 - 优点: 耗时短;缺点:代码量大,出错率高。 79 | 80 | 总结:个人感觉若数据量在1000及以下,对相应时间要求不敏感的话,推荐使用未优化版本。因为《任何程序员都能写出计算机能懂的代码,而优秀程序员能写出人能读懂的代码 - 来自代码大全》。 -------------------------------------------------------------------------------- /DataStructureAlgorithm/每日算法练习.md: -------------------------------------------------------------------------------- 1 | 一天三道题。 2 | 3 | * 2021.05.17 4 | * 两数之和。利用哈希表存储。 5 | * 两数相加。 6 | * 无重复字符的最长子串。滑动窗口,利用哈希表存储。 7 | 8 | 9 | * 2021.05.18 10 | * 盛最多水的容器。双指针。 11 | * 下一个序列。不会。 12 | 13 | * 2021.05.19 14 | * 删除倒数第 N 个节点,双指针。 15 | * 有效括号,dict + stack,注意判断奇偶数,和 stack 最后是否为空。 16 | * 合并两个有序链表。 17 | 18 | 19 | * 2021.05.20 20 | * 旋转数组,二分法 21 | 22 | * 2021.05.21 23 | * 在排序树组中查找元素,二分法。 24 | * 最大子序和。 25 | * 爬楼梯。 26 | 27 | * 2021.05.25 28 | * 二叉树中序遍历。递归需创建辅助函数,左-主-右;遍历无需辅助函数,需借助栈。 29 | * 对称二叉树,创建辅助函数,无递归遍历需借助队列。 30 | 31 | * 2021.05.31 32 | * 二叉树最大深度。dfs(递归),bfs(层序遍历)。 33 | * 买卖股票的最佳时机。遍历。 34 | * 只出现一次的数字.异或。 35 | * 环形链表。快慢指针。 36 | 37 | * 2021.06.01 38 | * 反转链表 39 | * 相交链表 40 | * 多数元素 41 | * 最小栈 42 | * 2021.06.02 43 | * 反转二叉树 44 | * 回文链表:快慢指针分段,反转后半段链表,依次比较。恢复链表。 45 | * 移动零 46 | * 2021.06.03 47 | * 比特位计数 48 | * 消失的数字 49 | * 汉明距离:1.根据异或运算求出不同位的值 - s;2.根据 s & s - 1 来移除最右边的 1. 50 | * 2021.06.04 51 | * 二叉树的直径 52 | * 合并二叉树 53 | * 2021.06.07 54 | * Valid Parentheses,Thinking: Implement with Hashtable and Stack. 55 | * Merge Two Sorted Lists, Thinking:Implement with two pointer. 56 | * Maximum Subarray, Thinking: Implement with dynamic programming. 57 | * 2021.06.08 58 | * Climbing Stairs, Thinking: if n <= 3 return n, else first = 1, second = 2 ,for 3...n return second. 59 | * Binary Tree Inorder Traversal, Thinking: Recursive(with helper function) or Iterate(with Stack). 60 | * Symmetic Tree, Thinking: Recursive with helper function. 61 | * 2021.06.09 62 | * Maximum Depth Binary Tree. 63 | * Single Number. 64 | * Buy And Sell Stock. 65 | * 2021.06.10 66 | * Linked List Cycle 67 | * Min Stack 68 | * Intersection of Two Linked Lists 69 | 70 | * 2021.06.11 71 | * Majority Element 72 | * Invert Binary Tree 73 | * Palindrome Linked List 74 | 75 | * 2021.06.15 76 | * Move Zeroes 77 | * Counting Bits 78 | * Merge Two Binary Trees 79 | 80 | * 2021.06.18 81 | * Diameter of Binary Tree 82 | * Find All Numbers Disappeared in an Array 83 | 84 | * 2021.06.23 85 | * Partition Labels 86 | * Permutations 87 | -------------------------------------------------------------------------------- /DataStructureAlgorithm/链表算法总结.md: -------------------------------------------------------------------------------- 1 | ## 基础部分 2 | ### 反转链表 3 | ### 快慢指针 4 | ### 双指针 5 | ### 前哨节点 -------------------------------------------------------------------------------- /Interview/bytedance.md: -------------------------------------------------------------------------------- 1 | 1、如何高效的切圆角? 2 | 3 | 切圆角共有以下三种方案: 4 | * cornerRadius + masksToBounds:适用于单个视图或视图不在列表上且量级较小的情况,会导致离屏渲染。 5 | * CAShapeLayer+UIBezierPath:会导致离屏渲染,性能消耗严重,不推荐使用。 6 | * Core Graphics:不会导致离屏渲染,推荐使用。 7 | 8 | 2、什么是隐式动画和显式动画? 9 | 10 | 隐式动画指的是改变属性值而产生的默认的过渡动画(如background、cornerRadius等),不需要初始化任何类,系统自己处理的动画属性;显式动画是指自己创建一个动画对象并附加到layer上,如 `CAAnimation、CABasicAnimation、CAKeyframeAnimation`。 11 | 12 | 3、UIView 和 CALayer 的区别? 13 | 14 | UIView 是 CALayer 的 delegate,UIView 可以响应事件,而 CALayer 则不能。 15 | 16 | 4、离屏渲染? 17 | 18 | iOS 在不进行预合成的情况下不会直接在屏幕上绘制该图层,这意味着 CPU 和 GPU 必须先准备好屏幕外上下文,然后才能在屏幕上渲染,这会造成更多时间时间和更多的内存的消耗。 19 | 20 | 5、Objective - C 是否支持方法重载(overloading)? 21 | 22 | 不支持。方法重载(overloading):允许创建多项名称相同但输入输出类型或个数不同的方法。 23 | 24 | ``` 25 | // 这两个方法名字是不一样的,虽然都是writeToFile开头 26 | -(void) writeToFile:(NSString *)path fromInt:(int)anInt; 27 | -(void) writeToFile:(NSString *)path fromString:(NSString *)aString; 28 | ``` 29 | 注:Swift 是支持的。 30 | ``` 31 | func testFunc() {} 32 | func testFunc(num: Int) {} 33 | ``` 34 | 35 | 6、KVC 的应用场景及注意事项 36 | 37 | KVC(key-Value coding) 键值编码,指iOS开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。不需要调用明确的存取方法,这样就可以在运行时动态访问和修改对象的属性,而不是在编译时确定。 38 | 39 | 它的四个主要方法: 40 | ``` 41 | - (nullable id)valueForKey:(NSString *)key; //直接通过Key来取值 42 | - (void)setValue:(nullable id)value forKey:(NSString *)key; //通过Key来设值 43 | - (nullable id)valueForKeyPath:(NSString *)keyPath; //通过KeyPath来取值 44 | - (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath; //通过KeyPath来设值 45 | ``` 46 | 应用场景: 47 | * 动态取值和设值 48 | * 访问和改变私有变量 49 | * 修改控件的内部属性 50 | 51 | 注意事项: 52 | * key 不要传 nil,会导致崩溃,可以通过重写`setNilValueForKey:`来避免。 53 | * 传入不存在的 key 也会导致崩溃,可以通过重写`valueForUndefinedKey:`来避免。 54 | 55 | 7、如何异步下载多张小图最后合成一张大图? 56 | 57 | 使用Dispatch Group追加block到Global Group Queue,这些block如果全部执行完毕,就会执行Main Dispatch Queue中的结束处理的block。 58 | ``` 59 | dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 60 | dispatch_group_t group = dispatch_group_create(); 61 | dispatch_group_async(group, queue, ^{ /*加载图片1 */ }); 62 | dispatch_group_async(group, queue, ^{ /*加载图片2 */ }); 63 | dispatch_group_async(group, queue, ^{ /*加载图片3 */ }); 64 | dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 65 | // 合并图片 66 | }); 67 | ``` 68 | 69 | 8、NSTimer 有什么注意事项?在 dealloc 中调用 `[timer invalidate];`会避免循环引用吗? 70 | * 时间延后。如果 timer 处于耗时较长的 runloop 中,或者当前 runloop 处于不监视 timer 的 mode 时(如 scrollView 滑动时)。它在下次 runloop 才会触发,所以可能会导致比预期时间要晚。 71 | * 循环引用。target 强引用 timer,timer 强引用 target。 72 | 73 | 时间延后 74 | 75 | 使用 `dispatch_source_t` 来提高时间精度。 76 | ``` 77 | dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); 78 | if (timer) { 79 | dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, (1ull * NSEC_PER_SEC) / 10); 80 | dispatch_source_set_event_handler(timer, block); 81 | dispatch_resume(timer); 82 | } 83 | ``` 84 | 85 | 循环引用 86 | 87 | 在 dealloc 中调用 `[timer invalidate];`不会避免循环引用。因为 timer 会对 target 进行强引用,所以在 timer 没被释放之前,根本不会走 target 的 dealloc 方法。 88 | 89 | 可以通过以下几种方法来避免: 90 | * 如果 iOS 10 及以上,可以使用`init(timeInterval:repeats:block:)`。target 不再强引用 timer。记得在 dealloc 中调用 `[timer invalidate];`,否则会造成内存泄漏。 91 | ``` 92 | timer = Timer(timeInterval: 1.0, repeats: true, block: { [weak self] (timer) in 93 | self?.timerFunc() 94 | }) 95 | ``` 96 | * 使用中间件的方式来避免循环引用。 97 | ``` 98 | // 定义 99 | @implementation WeakTimerTarget 100 | { 101 | __weak target; 102 | SEL selector; 103 | } 104 | 105 | - (void)timerDidFire:(NSTimer *)timer { 106 | if(target) { 107 | [target performSelector:selector withObject:timer]; 108 | } else{ 109 | [timer invalidate]; 110 | } 111 | } 112 | @end 113 | 114 | // 使用 115 | WeakTimerTarget *target = [[WeakTimerTarget alloc] initWithTarget:self selector:@selector(tick)]; 116 | timer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:target selector:@selector(timerDidFire:) ...]; 117 | ``` 118 | 119 | 9、对 property 的理解 120 | ``` 121 | @property = ivar + getter + setter; 122 | ``` 123 | 124 | 10、Notification 的注意事项 125 | 126 | 在哪个线程发送通知,就在哪个线程接受通知。 127 | 128 | 11、Runloop的理解 129 | 130 | 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑 是这样的: 131 | ``` 132 | function loop() { 133 | initialize(); 134 | do { 135 | var message = get_next_message(); 136 | process_message(message); 137 | } while (message != quit); 138 | } 139 | ``` 140 | 141 | 12、对 OC 中 Class 的源码理解?其中 cache 的理解? 142 | 143 | Class 的底层用 struct 实现,源码如下: 144 | ``` 145 | struct _class_t { 146 | struct _class_t *isa; 147 | struct _class_t *superclass; 148 | void *cache; 149 | void *vtable; 150 | struct _class_ro_t *ro; 151 | }; 152 | ``` 153 | 154 | Cache用于缓存最近使用的方法。一个类只有一部分方法是常用的,每次调用一个方法之后,这个方法就被缓存到cache中,下次调用时 runtime 会先在 cache 中查找,如果 cache 中没有,才会去 methodList 中查找。以此提升性能。 155 | 156 | 157 | 13、项目优化做了哪些方面? 158 | * 删除无用资源文件及代码 159 | * 在合适的地方加缓存 160 | * 耗时长的代码异步执行 161 | 162 | 14、如何一劳永逸的检测包的裂变(检测包的大小)? 163 | 164 | 这个不知道,希望了解的朋友可以在评论区指出来。 165 | 166 | 167 | 15、实现一个判断 IP 地址是否合法的方法 168 | ``` 169 | func isIPAddress(str: String) -> Bool { 170 | guard !str.isEmpty else { return false } 171 | var isIPAddress = false 172 | let coms = str.components(separatedBy: ".") 173 | for com in coms { 174 | if let intCom = Int(com), intCom >= 0, intCom <= 255 { 175 | isIPAddress = true 176 | } else { 177 | isIPAddress = false 178 | return isIPAddress 179 | } 180 | } 181 | return isIPAddress 182 | } 183 | ``` 184 | ### 参考 185 | * [深入理解 Objective-C:方法缓存](https://tech.meituan.com/2015/08/12/deep-understanding-object-c-of-method-caching.html) 186 | * [NSTimer](https://developer.apple.com/documentation/foundation/nstimer?language=objc) 187 | * [cornerRadius](http://lemon2well.top/2018/08/29/iOS%20%E5%BC%80%E5%8F%91/iOS%E4%B8%AD%E7%9A%84%E5%9C%86%E8%A7%92%E5%A4%84%E7%90%86%EF%BC%88%E7%BB%88%E7%BB%93%E7%AF%87%EF%BC%89/) 188 | * [animation](https://github.com/qunten/iOS-Core-Animation-Advanced-Techniques/blob/master/8-%E6%98%BE%E5%BC%8F%E5%8A%A8%E7%94%BB/%E6%98%BE%E5%BC%8F%E5%8A%A8%E7%94%BB.md) 189 | * [overloading in oc](https://stackoverflow.com/questions/2286312/method-overloading-in-objective-c) 190 | * [kvc](https://juejin.im/post/6844903710917672968#heading-8) 191 | * [iosInterviewQuestions](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8B%EF%BC%89.md#41-%E5%A6%82%E4%BD%95%E7%94%A8gcd%E5%90%8C%E6%AD%A5%E8%8B%A5%E5%B9%B2%E4%B8%AA%E5%BC%82%E6%AD%A5%E8%B0%83%E7%94%A8%E5%A6%82%E6%A0%B9%E6%8D%AE%E8%8B%A5%E5%B9%B2%E4%B8%AAurl%E5%BC%82%E6%AD%A5%E5%8A%A0%E8%BD%BD%E5%A4%9A%E5%BC%A0%E5%9B%BE%E7%89%87%E7%84%B6%E5%90%8E%E5%9C%A8%E9%83%BD%E4%B8%8B%E8%BD%BD%E5%AE%8C%E6%88%90%E5%90%8E%E5%90%88%E6%88%90%E4%B8%80%E5%BC%A0%E6%95%B4%E5%9B%BE) -------------------------------------------------------------------------------- /Interview/interview-network.md: -------------------------------------------------------------------------------- 1 | #### 请简述 TCP、UDP 的区别 2 | TCP:面向连接,提供可靠性服务,速度较慢;UDP:面向非连接,不提供数据可靠性,速度较快。 3 | 4 | #### 了解的端口和对应的服务 5 | * 21 - FTP 6 | * 22 - SSH 7 | * 23 - Telnet 8 | * 25 - SMTP 9 | * 53 - DNS 10 | * 80 - HTTP 11 | * 443 - HTTPS 12 | * 1080 - Sockets 13 | 14 | #### TCP 的三次握手 15 | * client -> server:发送一个 SYN 包,并将序号字段赋一个随机数 x 16 | * server -> client:接收 SYN 包,发送 SYNACK 包,ACK 字段值为 x + 1,序号值为 y 17 | * client -> server:接收 SYNACK 包,SYN 置为 0,ACK 字段值为 y + 1。 18 | 19 | #### 有哪些私有的(保留)地址? 20 | 私有 IP 地址是一段保留的IP地址。只是使用在局域网中,在 Internet 上是不使用的。 21 | 22 | * A类:10.0.0.0 - 10.255.255.255 23 | * B类:172.16.0.0 - 172.31.255.255 24 | * C类:192.168.0.0 - 192.168.255.255 25 | 26 | #### IP 地址分为几类?简单说下各个分类 27 | * IPv4 28 | - 32 bit 29 | - 允许分片 30 | * IPv6 31 | - 128 bit 32 | - 不允许分片 33 | 34 | #### 在浏览器中输入网址之后执行会发生什么? 35 | 浏览器解析URL生成报文 -> 根据域名通过 DNS 获取 IP 地址 -> 通过系统协议栈将报文传递给运输层 -> 网络层转发到目的地址 -> Server 处理请求返回数据 -> 浏览器接受返回数据,显示。 36 | 37 | #### 简单介绍下 ARP 的工作过程 38 | 39 | #### OSI 七层模型 40 | * 应用层 41 | * 表示层 42 | * 会话层 43 | * 运输层 44 | * 网络层 45 | * 数据链路层 46 | * 物理层 47 | 48 | #### TCP、IP 四层模型 49 | * 应用层 50 | * 传输层 51 | * 网络层 52 | * 链路层 53 | 54 | #### HTTP 协议包括哪些请求 55 | * GET/POST/DELETE/PUT/HEAD/OPTIONS/CONNECT 56 | 57 | #### HTTP 中 GET 和 POST 的区别 58 | * GET:用于获取信息 59 | * POST:用于更新信息 60 | 61 | 62 | Source:https://zhuanlan.zhihu.com/p/24001696 -------------------------------------------------------------------------------- /Interview/interview1.md: -------------------------------------------------------------------------------- 1 | ### 用递归写一个算法,计算从 1 到 100 的和。 2 | * 算法的时间复杂度是多少?答:O(n)。 3 | * 递归会有什么缺点?答:当数据量较大时,它会占用很大的空间,因为它需要一直压栈。 4 | * 不用递归能否实现,复杂度能否降到O(1)。 5 | 6 | 递归算法: 7 | ``` 8 | func sum(_ target: Int) -> Int { 9 | if target <= 0 { 10 | return 0 11 | } 12 | return target + sum(target - 1) 13 | } 14 | ``` 15 | 16 | 不用递归实现,且时间复杂度为O(1): 17 | ``` 18 | func sum(_ target: Int) -> Int { 19 | return (1 + target) * target / 2 20 | } 21 | ``` 22 | 23 | 更进一步的优化: 24 | ``` 25 | func sum(_ target: Int) -> Int { 26 | return (1 + target) * target << 1 27 | } 28 | ``` 29 | 30 | ### property 的作用是什么,有哪些关键词,分别是什么含义? 31 | 32 | property 的作用:setter + getter + instance。 33 | 34 | 关键词: 35 | * strong:用来修饰对象类型,强引用。 36 | * weak:用来修饰对象类型,弱引用。 37 | * copy:用来修饰 String、NSArray、NSDictionary 等。 38 | * atomic:原子性,一般不使用,因为并不能真的保证线程安全,还会带来性能问题。 39 | * nonatomic:非原子性,一般使用这个。 40 | * assign:修饰基本类型:Int等。 41 | 42 | ### 父类的 property 是如何查找的? 43 | 44 | ### NSArray、NSDictionary 应该如何选关键词? 45 | 46 | ### copy 和 muteCopy 有什么区别,深复制和浅复制是什么意思,如何实现深复制? 47 | 48 | ### 用 runtime 做过什么事情?runtime 中的方法交换是如何实现的? 49 | 50 | ### 讲一下对 KVC 和 KVO 的了解,KVC 是否会调用 setter 方法? 51 | 52 | ### __block 有什么作用 53 | 54 | ### 说一下对 GCD 的了解,它有那些方法,分别是做什么用的? 55 | 56 | ### 对二叉树是否了解? -------------------------------------------------------------------------------- /Interview/新东方.md: -------------------------------------------------------------------------------- 1 | 1、如何进行 cell 高度的缓存?说一下 [UITableView-FDTemplateLayoutCell](https://github.com/forkingdog/UITableView-FDTemplateLayoutCell) 的实现原理? 2 | 3 | 缓存 cell 高度: 4 | * 如果用的 frame ,则给 model 添加一个 cellH 的属性,然后在获取数据时计算好高度赋值给 cellH。 5 | * 如果用的 AutoLayout,创建相应布局等同的 cell,计算好高度然后缓存。 6 | 7 | FD 的实现: 8 | 9 | `fd_heightForCellWithIdentifier: configuration:` 方法会根据 identifier 以及 configuration block 提供一个和 cell 布局相同的 template layout cell,并将其传入 `fd_systemFittingHeightForConfiguratedCell:` 这个私有方法返回计算出的高度。主要使用技术为 runtime 。 10 | 11 | 2、在 Block 中使用 成员变量 (如:_name) 会造成循环引用吗(比如下面的代码)?如何解决? 12 | ``` 13 | typedef void(^TestBlock)(void); 14 | 15 | @interface DetailViewController () 16 | @property (nonatomic, copy) NSString *name; 17 | @property (nonatomic, copy) TestBlock block; 18 | @end 19 | 20 | @implementation DetailViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | self.name = @"fzh"; 25 | 26 | self.block = ^() { 27 | NSLog(@"%@", _name); 28 | }; 29 | } 30 | 31 | @end 32 | ``` 33 | 会造成循环引用,因为 _name 底层也是通过 self 去获取(`self->_name`)。 34 | 35 | `->` : 指向结构体成员运算符。 36 | 37 | 解决方案: 38 | * 强制将 block 置空,代码如下: 39 | ``` 40 | weakSelf.block = nil; 41 | ``` 42 | * 通过 weak、strong self 来解决,代码如下: 43 | ``` 44 | __weak typeof(self) weakSelf = self; 45 | 46 | self.block = ^() { 47 | __strong typeof(weakSelf) strongSelf = weakSelf; 48 | NSLog(@"%@", strongSelf->_name); 49 | }; 50 | ``` 51 | 52 | 在 block 还是推荐使用点语法去访问成员变量。 53 | 54 | 3、isa 指针的作用?它的应用场景? 55 | 56 | 作用:isa 指针指向它的类对象,从而可以找到对象上的方法。 57 | 58 | 应用场景: 59 | 60 | * KVO - isa 混写 (isa-swizzling) 61 | 62 | 4、描述消息转发机制?它的应用场景? 63 | 64 | 如果你给某个对象发送消息,而该对象没有实现该方法的话,就会进行消息转发,共有以下三步: 65 | * Method resolution 66 | * Fast forwarding 67 | * Normal forwarding 68 | 69 | 若以上三步皆不成功,则会报 `unrecognized selector sent to …` 的异常。 70 | 71 | 应用场景: 72 | * 特定奔溃预防处理 73 | * 苹果系统 API 迭代造成 API 不兼容的奔溃处理 74 | 75 | 5、组件化的了解,各组件之间如何通信?CTMediator 的底层实现原理? 76 | 77 | 组件化:按照项目功能模块分割成多个组件来开发维护,以此来降低项目的耦合性。 78 | 79 | 各组件如何通信: 80 | * 创建一个 Mediator,各组件通过 Mediator 通信,即 runtime 调度。 81 | * 注册表的方式,用URL表示接口,在模块启动时注册模块提供的接口,即 URL/protocol 注册调度。 82 | 83 | CTMediator 主要是基于Mediator模式和Target-Action模式,中间采用了 runtime 来完成调用。 84 | 85 | 6、array 的 copy 和 mutableCopy 的区别?mutableCopy 如果每个元素都是对象,那么会开辟新的内存空间吗?如何开辟新的内存空间? 86 | 87 | copy 为指针拷贝(内存地址一致),mutableCopy 为内容拷贝(内存地址不一致)。 88 | 89 | 如果元素为对象,不会开辟新的内存空间,因为 mutableCopy 是单层浅复制,我们需要给对象单独实现一个深复制的方法才可以。 90 | ``` 91 | - (id)deepCopy { 92 | CYLUser *copy = [[[self class] alloc] 93 | initWithName:_name 94 | age:_age 95 | sex:_sex]; 96 | copy->_friends = [[NSMutableSet alloc] initWithSet:_friends 97 | copyItems:YES]; 98 | return copy; 99 | } 100 | ``` 101 | 102 | 7、Fastlane 在使用中遇到什么问题? 103 | 104 | 没遇到过啥问题-_-||,如果又遇到啥问题的同学可以在评论区提出来大家讨论一下。 105 | 106 | ### 参考 107 | * [谈谈ivar的直接访问](https://satanwoo.github.io/2018/02/04/iOS-iVar/) 108 | * [iOS 组件化方案探索](https://wereadteam.github.io/2016/03/19/iOS-Component/) 109 | * [在现有工程中实施基于CTMediator的组件化方案](https://casatwy.com/modulization_in_action.html) 110 | * [iOS开发·runtime原理与实践: 消息转发篇(Message Forwarding) (消息机制,方法未实现+API不兼容奔溃,模拟多继承)](https://juejin.im/post/6844903600968171533#heading-2) 111 | * [UITableView+FDTemplateLayoutCell 框架学习(cell动态计算高度)](https://blog.csdn.net/wujakf/article/details/79995114) 112 | * [招聘一个靠谱的iOS](https://github.com/ChenYilong/iOSInterviewQuestions/blob/master/01%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88/%E3%80%8A%E6%8B%9B%E8%81%98%E4%B8%80%E4%B8%AA%E9%9D%A0%E8%B0%B1%E7%9A%84iOS%E3%80%8B%E9%9D%A2%E8%AF%95%E9%A2%98%E5%8F%82%E8%80%83%E7%AD%94%E6%A1%88%EF%BC%88%E4%B8%8A%EF%BC%89.md#1-%E5%AF%B9%E9%9D%9E%E9%9B%86%E5%90%88%E7%B1%BB%E5%AF%B9%E8%B1%A1%E7%9A%84copy%E6%93%8D%E4%BD%9C) -------------------------------------------------------------------------------- /Network/网络是怎样连接的.md: -------------------------------------------------------------------------------- 1 | ### 第一章 浏览器生成消息 2 | * 名词缩写 3 | - URL:Uniform Resource Locator 4 | - FTP:File Transfer Protocol 5 | - HTTP:Hypertext Transfer Protocol 6 | - URI:Uniform Resource Identifier 7 | - DNS:Domain Name System 8 | 9 | * 省略路径会访问默认文件,比如:index.html。 10 | * 浏览器的第一步工作就是对 URL 进行解析。 11 | * HTTP 消息包含:资源路径(对什么) + 动作(做什么)。 12 | * HTTP Method: 13 | - GET: 资源获取。 14 | - POST:用于客户端向服务端发送数据。 15 | - DELETE:删除。 16 | - PUT:替换文件,若不存在则新建。 17 | - HEAD:获取 header 的信息。 18 | 19 | * HTTP 请求消息: 20 | - 请求行。 21 | - 消息头。 22 | - 消息体。 23 | 24 | * HTTP 相应消息: 25 | - 状态行。 26 | - 消息头。 27 | - 消息体。 28 | 29 | * HTTP 状态码 30 | - 1xx:告知请求的处理进度和情况。 31 | - 2xx:成功。 32 | - 3xx:需要进一步操作。 33 | - 4xx:客户端错误。 34 | - 5xx:服务器错误。 35 | 36 | * 一条请求消息只能写一个 URI。如果需要获取多个文件,必须每个文件单独发送一条请求。 37 | * IP 地址的主机号 38 | - 全 0:表示整个子网。 39 | - 全 1:表示向子网所有设备发包,即广播。 40 | * DNS 查询信息 41 | - 域名、Class、记录类型 42 | * DNS 查询:客户端 -> 最近的 DNS 服务器 -> 根域 -> 下一级域... -> 目标 DNS 服务器。 43 | * DNS 查询优化:缓存查询结果;且缓存有有效期,防止缓存数据已改变。 44 | * 收发数据的 4 个操作 45 | - 创建套接字 46 | - 连接服务器的套接字 47 | - 收发数据 48 | - 断开管道,删除套接字 49 | * 应用程序通过描述符来识别套接字。 50 | * 描述符用于计算机内部识别套接字;端口号用于服务器识别套接字。 51 | * web 端口号: 80;电子邮件端口号:25。 52 | 53 | ### 第二章 用电信号传输 TCP/IP 数据 54 | * 名词缩写 55 | - TCP: Transmission Control Protocol 56 | - UDP:User Datagram Protocol 57 | - ICMP:Internet Message Protocol 58 | - IP:Internet Protocol 59 | - ARP:Address Resolution Protocol 60 | - MTU:Maximum Transmission Unit,一般为 1500 字节。 61 | - MSS:Maximum Segment Size 62 | - FCS:Frame Check Sequence 63 | 64 | #### TCP 65 | * 协议栈是根据套接字中记录的控制信息来工作的。 66 | * 创建套接字时,先分配内存空间,然后向其中写入初始状态。 67 | * 连接做的两件事:交换控制信息;创建内存空间。 68 | * 通信操作中的控制信息分两类: 69 | - 头部中记录的信息。 70 | - 套接字(协议栈中的内存空间)中记录的信息。 71 | * 缓存区何时发送数据判断两要素: 72 | - 网络包的长度。 73 | - 时间。 74 | * 通过 序号 和 ACK号 可以确认接收方是否收到了网络包。 75 | * TCP 会动态调整 ACK号 的等待时间。 76 | * 滑动窗口来优化 ACK号 等待的时间。基本思路:接收方告知发送方能接受多少数据,发送方根据这个值来控制发送操作。 77 | * 套接字会等待一段时间(一般为几分钟)后才删除,为什么? 78 | 答:防止误删。假设客户端最后的 ACK 号丢失,那服务器就会重发 FIN 包,而如果这时客户端的套接字已经删除,并把端口号分配了一个新的套接字,那这个新的套接字就会开始执行断开操作了。 79 | 80 | #### IP 81 | * 包的内容:头部 + 数据。 82 | * IP 和以太网的分层是为了保证网络架构上的灵活性,比如以太网可以替换为无线局域网、ADSL等。 83 | * IP 模块负责添加两个头部:IP 头部和 MAC 头部。 84 | * IP 模块对 TCP模块传递的内容不会校验,只是单纯的负责发送数据,至于送没送到也不会关心。 85 | 86 | #### 以太网 87 | * 使用 ARP 来通过 IP 地址获取 MAC 地址。ARP会用广播的方式发给所有设备,且为了提高效率,它会缓存结果。每次获取 MAC 地址时会先从缓存找,找不到再去广播。为防止缓存数据失效,一般缓存的数据会在几分钟后被全部删除。 88 | * 以太网类型: 89 | - 0800:IP 协议。 90 | - 0806:ARP。 91 | - 86DD:IPv6。 92 | * MAC 头部信息: 93 | - 接收方 MAC 地址。 94 | - 发送方 MAC 地址。 95 | - 以太网类型。 96 | 97 | * 网卡的 ROM 中保存着全世界唯一的 MAC 地址,是在生产网卡时写入的。MAC 地址会由网卡驱动程序读取并分配给 MAC 模块。 98 | * MAC 模块会在数据报开头添加报头和起始帧分界符,在尾部添加用于检查错误的 FCS。 99 | * 发送信号的两种方式:半双工和全双工。 100 | 101 | #### UDP 102 | * UDP 无连接,不负责错误校验,效率高。头部控制信息: 103 | - 发送方端口号 104 | - 接收方端口号 105 | - 数据长度 106 | - 校验和 107 | 108 | ### 第三章 集线器、交换机、路由器 109 | 一般的家庭路由器已经集成了集线器和交换机的功能。 110 | 111 | #### 集线器 112 | * 转发设备在进行转发时不会关心包的内容。因此,包在传输过程中是独立的,互相之间没有任何关联。 113 | * 信号衰减和噪声会导致数据传输出错。双绞线可以抑制噪声。 114 | * 集线器会将受到的信号广播出去,即使信号已经受到干扰已失真。 115 | * 集线器的作用? 116 | 117 | #### 交换机 118 | * 交换机端口的 MAC 模块不具有 MAC 地址。 119 | * 交换机根据 MAC 表查询对应 MAC 地址的端口,然后将信号转发的该端口。 120 | * 交换机会维护 MAC 表,对其进行添加和删除操作。 121 | * 交换机的特殊操作: 122 | - 当发现包要发回原端口时,即丢弃此包。 123 | - 若 MAC 表中找不到记录,则将包转发到除源端口外的所有端口。 124 | * 交换机的全双工模式可以同时接受和发送信号,不会发生碰撞。通过自动协商来调整半双工和全双工的切换。 125 | * 交换机可同时执行多个转发操作,所以性能优于集线器。 126 | 127 | #### 路由器 128 | * 路由器是基于 IP 设计的,交换机是基于以太网设计的。 129 | * 路由器的端口有 MAC 地址和 IP 地址。只接受与自身地址匹配的包,遇到不匹配的包则直接丢弃。 130 | * 路由器的包转发模块和端口模块: 131 | - 端口模块负责包的收发操作。 132 | - 转发模块负责判断包的转发目的地。 133 | * 路由器通过路由表来判断转发目标。 134 | * 路由器会忽略主机号只匹配网络号。 135 | * 路由聚合:多个子网合为一条数据。 136 | * 路由表维护: 137 | - 人工维护。 138 | - 通过路由器自行维护。 139 | * 路由表转发采用最长匹配原则,若网络号相同,则取跃点数最小的那条记录。 140 | * 路由器遇到不匹配的包直接丢弃,而交换机则会广播到除源端口外的所有端口。 141 | * 路由表中子网掩码为 0.0.0.0 的记录表示默认路由,可匹配任何地址。 142 | * IP 包通过 TTL 来决定包的有效期,每过一个路由器减一,当值为零时丢弃该包。该机制是为了防止包在一个地方死循环。TTL 的值一般为 64 或 128。 143 | * 通过 ARP 将 IP 地址转为 MAC 地址。 144 | * 路由器判断下一个转发目标: 145 | - 如果路由表的网关列内容为 IP 地址,则改地址即为下一个转发目标。 146 | - 如果网关为空,则 IP 头部中的接收方 IP 地址为下一个转发目标。 147 | * IP (路由器)负责将包送达通信对象这一整体过程,而其中将包传输到下一个路由器的过程则是由以太网(交换机)来负责。 路由器负责判断下一个转发对象,而交换机负责把数据传输给下一个目标对象。 148 | * IP 本身不负责包的传输,这样设计是为了保证架构的灵活性。 149 | * 用于内网的地址,称为私有地址: 150 | - 10.0.0.0 ~ 10.255.255.255 151 | - 172.16.0.0 ~ 172.31.255.255 152 | - 192.168.0.0 ~ 192.168.255.255 153 | * 内网地址通过路由器地址转换的功能来连接外网。地址转换的基本原理就是在转发网络包时对 IP 头部的 IP 地址和端口号进行改写。 154 | * 若对应表中没有相应的记录,互联网是无法想内网发送网络包的,这种机制具有防止非法入侵的效果。 155 | * 路由器的包过滤: 在对包进行转发时,根据 MAC 头部、IP 头部、TCP 头部的内容,按照事先设置好的规则决定是否转发这个包,还是丢弃这个包。 156 | 157 | ### 第四章 接入网、网络运营商 158 | 暂无兴趣,略过。 159 | 160 | ### 第五章 防火墙、缓存服务器 161 | #### 防火墙 162 | * 包过滤:根据头部控制信息来进行设置。 163 | * 包过滤方式的防火墙可根据接收方、发送方的 IP 地址,端口号,控制位等信息来判断是否允许某个包通过。 164 | 165 | #### 负载均衡 166 | * 服务器性能不足时,需使用负载均衡: 167 | - 通过 DNS 服务器轮训返回 IP 地址,缺点:不能自动跳过有故障的服务器。 168 | - 负载均衡器:通过将负载均衡器替代 Web 服务器注册到 DNS 服务器,实现客户端请求指到负载均衡器。再由负载均衡器根据一定的规则去请求相应服务器。 169 | 170 | #### 缓存服务器 171 | * 通过缓存的数据来提升服务器的性能。将缓存服务器替代 Web 服务器注册到 DNS 服务器。 172 | * 流程: 173 | - 无缓存:client -> cache server -> web server;web server -> cache server(缓存数据) -> client。 174 | - 有缓存:client -> cache server -> web server;web server(数据无更新) -> cache server(直接返回缓存的数据) -> client。 175 | - 若数据有更新,流程同 无缓存。 176 | * 通过 Via 头部字段表明消息是经过缓存服务器中转的。 177 | * 正向代理: 178 | - 放在客户端一侧,通过代理禁止员工访问危险的网站,或者与工作无关的网站。 179 | - 没有代理时,URI 只包含文件或目录名;有代理时,则是完整网址。 180 | * 反向代理:将请求信息中的 URI 中的目录名与 Web 服务器进行关联,使得代理能够转发一般的不包含完整网址的请求消息。 181 | * 透明代理:查看请求消息的包头部来判断访问哪台服务器。 182 | 183 | #### CDS - 内容分发服务 184 | * CDS 寻找服务器的策略: 185 | - 通过 DNS 来分发。 186 | - 通过重定向。 187 | 188 | ### 第六章 Web 服务器 189 | * 服务器初始状态为等待连接的状态,每进来一个请求都会新创建一个套接字进行连接。 190 | * 发起连接的一方为客户端,等待连接的一方为服务端。 191 | * 通过创建等待连接套接字的副本,来让服务器一直保持等待连接的状态。 192 | * 使用描述符的原因: 193 | - 等待连接的套接字中没有客户端的 IP 地址和端口号。 194 | - 使用描述符信息比较简单。 195 | * 服务器接收流程: 196 | - 网卡的 MAC 模块将网络包从信号还原为数字信息,校验 FCS 并存入缓存区。 197 | - 网卡驱动根据 MAC 头部判断协议类型,并将包交给相应的协议栈。 198 | - 协议栈 IP 会检查 IP 头部,1)判断是不是发给自己的;2)判断网络包是否经过分片;3)将包转给 TCP 或 UDP。 199 | - 如果收到发起连接的包,则 TCP 模块会,1)确认 TCP 头部的控制为 SYN;2)检查接收方端口号;3)为相应的等待连接套接字复制一个新的副本;4)记录发送方 IP 地址和端口号等信息。 200 | - 收到数据包时,TCP 模块会,1)根据收到的包的发送方 IP 地址、发送方端口号、接受方 IP 地址、接收方端口号找到相应的套接字;2)将数据拼合保存到缓冲区;3)向客户端返回 ACK。 201 | * Web 服务器访问控制规则: 202 | - 客户端 IP 地址 203 | - 客户端域名 204 | - 用户名和密码 205 | * 客户端根据 Content-Type 字段来区分资源类型。 206 | -------------------------------------------------------------------------------- /Network/计算机网络自顶向下.md: -------------------------------------------------------------------------------- 1 | ## 网络 5 层分层 2 | * 应用层 3 | * 运输层 4 | * 网络层 5 | * 链路层 6 | * 物理层 7 | 8 | ### 应用层 9 | 对应协议:HTTP、FTP 10 | 11 | #### HTTP 12 | * HTTP 非持续连接的缺点:时间和空间的浪费 13 | - 每个请求创建一个连接,增加 web server 的负担。 14 | - 每个对象需经受 2 倍的 RTT 交付时间。 15 | 16 | * HTTP 报文 17 | - 请求报文的通用格式:请求行(方法、URL、版本)、首部行(首部字段)、空行、实体体(POST 使用传递参数)。 18 | - 响应报文的通用格式:状态行(版本、状态码、短语)、首部行、空行、实体体。 19 | * 请求行方法:GET、POST、HEAD、PUT、DELETE。`用表单生成的数据不是必须使用 POST 方法。` 20 | * 状态码:200、301、400、404、500、505。 21 | * HTTP 是无状态的连接,它是如何识别用户的? 22 | 使用 cookie。cookie 在无状态的 HTTP 上建立了一个用户会话层。cookie 的争议:侵犯用户隐私。 23 | * cookie 使用到的组件:HTTP响应报文/HTTP 请求报文中的 cookie 首部行;用户端系统保留的 cookie 文件,由浏览器管理;web 站点的后端数据库。cookie 的内容为唯一标识码。 24 | 25 | * web 缓存/代理服务器 26 | - 用户发请求到缓存器,若缓存器存在请求对象,则直接返回请求对象。 27 | - 若不存在则缓存器在将请求转发给初始服务器,初始服务器将请求对象返回给缓存器。 28 | - 缓存器将请求对象缓存并返回给用户。 29 | 好处:节约流量,改善性能。 30 | 31 | * 如何解决缓存器数据过时的问题? 32 | 通过条件 GET (Conditional GET) 解决。 33 | - 请求报文使用 GET 方法。 34 | - 请求报文中包含 `If-Modified-Since` 首部行。 35 | 36 | #### SMTP 37 | * 一般不使用中间邮件服务器。 38 | * 默认使用的端口为 25。底层使用的是 TCP。 39 | * 流程:send agent -> send server -> recive agent -> recive server。 40 | 41 | #### HTTP 与 SMTP 42 | * HTTP 是拉协议;SMTP 是推协议。 43 | * SMTP 要求每个报文采用 7 比特 ASCII码,HTTP 没有这个限制。 44 | * 处理包含文本又包含图形的文档方式不同,HTTP 将各自的对象放在各自的响应报文中;SMTP将所有的报文对象放在一个报文中。 45 | 46 | #### DNS(Domain Name System) 47 | * DNS 位于应用层。默认端口为 53。运行于 UDP 之上。 48 | * 识别主机的两种方式:主机名/IP地址(IPv4 4字节)。 49 | * DNS 是一个有分层的 DNS 服务器实现的分布式数据库;一个能使主机查询分布式数据库的应用层协议。 50 | * DNS 其他的重要服务:主机别名、邮件服务器别名、负载分配。 51 | * DNS 为何采用分布式架构而不是单体架构 52 | - 单点故障会导致整个网络瘫痪 53 | - 通信容量巨大 54 | - 远距离的集中式数据库导致很高的时延 55 | - 维护成本高 56 | 57 | * DNS 服务器层级 58 | - 根 DNS 服务器 59 | - 顶级域 DNS 服务器 60 | - 权威 DNS 服务器 61 | 62 | * DNS 缓存 63 | - 缓存一般会在两天后移除。 64 | * DNS 的资源记录 65 | - 资源记录包含四个字段:Name、Value、Type、TTL。 66 | - 若 Type 为 A,则 Name 为主机名;若 Type 为 NS,则 Name 是个域;若 Type 为 CNAME,则 Value 为 别名为 Name 的主机对应的规范主机名;若 Type 为 MX,则 Value 是别名为 Name 的邮件服务器的规范主机名。 67 | - TTL 代表记录的生存时间。 68 | * BitTorrent:P2P 文件分发协议。 69 | * CDN(Content Distribution Network):优化服务器的性能;缓解服务器的压力。 70 | * TCP/UDP 的套接字 demo。 71 | 72 | ### 运输层 73 | * 网络层为主机之间提供逻辑通信;运输层为运行在不同主机上的进程之间提供了逻辑通信。 74 | * 运输层运行在端系统。 75 | 76 | 对应协议:TCP(Transfer Control Protocol)、UDP(User Data Protocol) 77 | 78 | * 多路复用:在源主机从不同套接字中收集数据块,并为每个数据块封装上首部信息,从而生成报文段,然后将报文段传递到网络层。 - 收集 79 | * 多路分解:在目的主机将运输层报文段中的数据交付到正确的套接字。- 分发 80 | * UDP 的套接字由一个二元组标识:(目的 IP 地址;目的端口号)。 81 | * TCP 的套接字由一个四元祖标识:(源 IP 地址;源端口号;目的 IP 地址;目的端口号)。 82 | 83 | #### UDP 84 | * 无连接、不可靠的运输层协议。 85 | * 采用 UDP 的原因: 86 | - 关于发送什么数据以及何时发送的应用层控制更为精细。 87 | - 无需建立连接。 88 | - 无连接状态。 89 | - 分组首部开销小。 90 | 总结:速度快,占用内存小。 91 | 92 | * UDP 的报文结构: 93 | - 源端口号、目的端口号。 94 | - 长度、检验和。 95 | - 应用数据。 96 | 97 | * 检验和提供了差错检测功能: 98 | - 将所有 16 bit 字的求和。求和时遇到的任何溢出都被回卷。 99 | - 将和进行反码运算。 100 | 101 | * UDP 为什么要提供差错检测? 102 | - 用来确保数据的正确性和完整性。 103 | - 当发生差错时,UDP 会直接丢弃包。 104 | 105 | * 可靠性的定义:数据按序、无损到达。 106 | * 如何解决 ACK、NAK 报文含糊不清的情况?使用序号。 107 | * 通过检验和、序号、定时器、肯定和否定确认分组来实现数据的可靠性。 108 | * Pipeline 给可靠性数据带来的影响 109 | - 增加序号范围。 110 | - 发送方和接收方必须缓存多个分组。 111 | - 如何处理丢失、损坏及延时过大的分组。 112 | 113 | * Pipeline 差错恢复的两种方法: 114 | - 回退 N 步(GBN),也被称为滑动窗口协议。 115 | - 选择重传(SR)。 116 | 117 | * GBN 发送方响应的三个事件: 118 | - 上层的调用。 119 | - 收到一个 ACK。 120 | - 超时事件。 121 | 122 | * GBN 接收方不会缓存任何失序分组。优点是接收缓存简单,缺点是不必要的重传。 123 | * 对 SR 而言,发送方和接收方的窗口并不总是一致。 124 | * SR 发送方的事件: 125 | - 从上层接受数据。 126 | - 超时。 127 | - 收到 ACK。 128 | 129 | * 可靠数据传输机制及其用途的总结 130 | - 检验和:用于检测传输分组中的比特错误。 131 | - 定时器:用于超时、重传一个分组,可能因为该分组在信道中丢失了。 132 | - 序号:用于为从发送方流向接收方的数据分组按顺序编号。 133 | - 确认:接收方告知发送方分组已被正确的收到。 134 | - 否定确认:接收方告知发送方分组未被正确的收到。 135 | - 窗口、流水线:对停等协议的优化。通过允许一次发送多个分组但未被确认,发送方的利用率可在停等操作模式的基础上得打增加。 136 | 137 | #### TCP 138 | * TCP 提供全双工服务;且连接是点对点。 139 | * 报文长度短语: 140 | - 最大报文段长度(Maximum segment size)- MSS。一般为1460字节。 141 | - 最大传输单位(Maximum Transmission unit) - MTU。一般为1500字节。 142 | * TCP 报文段结构: 143 | - 源端口号、目的端口号。 144 | - 序号。 145 | - 确认号。 146 | - 首部长度、保留未用、CWR、ECE、URG、ACK、PSH、RST、SYN、FIN、接受窗口。 147 | - 因特网检验和、紧急数据指针。 148 | - 选项。 149 | - 数据。 150 | * TCP 的初始序号为什么要选择随机数?- 减少报文段错误的情况。 151 | * Telnet 运行在 TCP 之上。 152 | * RTT:动态估算改变。 153 | * TCP 所有传输报文使用单一定时器。 154 | * 流量控制服务 155 | - 接收方会维护一个窗口来接收缓存。 156 | - 可用缓存:rwnd = RcVBuffer - [LastByteRcvd - LastByteRead] 157 | - 当 rwnd 为 0 时,发送方会继续发送只有一个字节数据的报文段,防止接收方有缓存空间而发送方仍然被阻塞。 158 | * TCP 三次握手 159 | - client -> server,SYN 标志位为1,不包含应用层数据,被称为 SYN 报文段。它会随机给序号生成一个初始值: client_isn(用来防止网络攻击)。 160 | - server -> client,SYN 标志位为1,被称为 SYNACK 报文段。确认号(ACK)为 client_isn + 1,序号(seq)为 server_isn。 161 | - client -> server,SYN 标志位为 0。确认号(ACK) 为 server_isn + 1,序号(seq) 为 client_isn + 1。至此,连接建立。 162 | * 为什么需要三次握手?两次行不行? 163 | 握手主要为了同步序号,两次并不能同步。 164 | 165 | * 为什么初始序号要随机,而不是从 0 开始? 166 | * TCP 四次挥手 167 | - client -> server,FIN 包。 168 | - server -> client,ACK 包。 169 | - server -> client,FIN 包。 170 | - client -> server,ACK 包。定时等待一段时间(典型的值为 30s、1min、2min)后关闭连接。 171 | 172 | * 四次挥手为什么要等待一段时间再断开连接? 173 | 174 | 答:防止误删。假设客户端最后的 ACK 号丢失,那服务器就会重发 FIN 包,而如果这时客户端的套接字已经删除,并把端口号分配了一个新的套接字,那这个新的套接字就会开始执行断开操作了。 175 | 176 | * SYN 泛洪攻击如何防范:SYN cookie 177 | - server 接受到报文段不会立即创建 TCP 连接,而是生成一个序列号(通过仅 server 知道的散列函数生成)。且 server 不会缓存该 cookie 或者对应 SYN 的其他信息。 178 | - 如果 client 合法,它将返回一个 ACK 报文段。server 会根据 ACK 报文段来验证之前的序列号。 179 | - 若 client 未返回报文段,则 server 不会分配任何资源。 180 | 181 | * 扫描端口(向目的主机发送 SYN 包)的三种结果: 182 | - 收到 SYNACK 报文段,说明该端口打开。 183 | - 接收到 RST 报文段,说明该端口未运行应用程序。 184 | - 什么也没收到,说明被防火墙隔断。 185 | 186 | * 网络阻塞的代价 187 | - 发送方必须执行重传以补偿缓存溢出而丢弃的分组。 188 | - 发送方不必要的重传会引起路由器利用其链路带宽来转发不必要的分组。 189 | - 由于拥塞而丢弃分组最终浪费了上游路由器的传输容量。 190 | 191 | * 拥塞控制的方法 192 | - 端到端拥塞控制(TCP 采用该方法)。 193 | - 网络辅助的拥塞控制。 194 | 195 | * TCP 拥塞控制 196 | - TCP 发送方如何限制发送流量的速率?答:通过调节拥塞窗口的值来调整发送速率。 197 | - TCP 发送方如何感知路径存在拥塞?答:当出现丢包时,则发送方认定路径存在拥塞。 198 | - 当发送方感知的拥塞时,用何种算法来改变发送速率?答:TCP 控制拥塞算法。 199 | 200 | * TCP 控制拥塞算法(加性增,乘性减) 201 | - 慢启动:起始为 1 个 MSS,后续每次确认数据量翻倍。 202 | - 拥塞避免 203 | - 快速恢复 204 | 205 | * 慢启动结束 206 | - 当丢包时,慢启动的指数增长会结束。cwnd 设置为 1 个 MSS,并且将慢启动阈值设为 cwnd/2。 207 | - 当慢启动阈值为 cwnd/2 时,若 cwnd 到达或超过慢启动阈值时,结束慢启动并且 TCP 转移到拥塞避免模式。 208 | - 检测到 3 个冗余 ACK,TCP 会执行快速重传并进入快速恢复状态。 209 | 210 | * 如何优化用户端远离数据中心导致时延很大的情况 211 | - 部署临近用户的前端服务器。 212 | - 使用 TCP 分岔来分裂 TCP 连接。 213 | - TCP 分岔大约能将 4 * RTT 优化到 RTT。 214 | 215 | * 拥塞避免 216 | - 进入拥塞避免状态,每个 RTT 直将 cwnd 的值增加一个 MSS。 217 | - 当出现超时时,行为与慢启动一致。cwnd 设置为 1 个 MSS,并且将慢启动阈值设为 cwnd/2。 218 | 219 | * 快速恢复 220 | - TCP 推荐而非必须的构建。 221 | - 当出现超时事件时,执行慢启动和拥塞避免一致的行为,并迁移到慢启动装填。 222 | - 当丢包事件出现时,cwnd 设置为 1 个 MSS,并且将慢启动阈值设为 cwnd/2。 223 | 224 | #### UDP 与 TCP 总结 225 | * TCP 面向连接,保证数据可靠性传输;UDP 非面向连接,不保证数据的可靠性传输。 226 | 227 | ### 网络层 228 | 对应协议:IP 229 | 230 | #### 数据平面 231 | * 网络层功能: 232 | - 转发,将分组移动到合适的输出链路。本地操作,一般由硬件实现。由数据平面执行的主要功能。 233 | - 路由选择,通过路由选择算法来决定分组所采用的的路由或路径。通常由软件实现。 234 | * 转发表用来存储分组的输出路径。 235 | * SDN (Software-Defined Networking) - 软件定义网络。通过远程控制器来计算并分发转发表。 236 | * 网络层提供尽力而为服务(best-effort service)。 237 | * 位于链路层的交换机被称为-链路层交换机;位于网络层的交换机被称为-路由器。 238 | * 路由器的四个组件: 239 | - 输入端口 240 | - 交换结构 241 | - 输出端口 242 | - 路由选择处理器 243 | 244 | * 输入端口 245 | - 接受链路层的数据。 246 | - 查询转发表决定路由器的输出端口。 247 | - 执行的操作:查找、转发、排队。 248 | - 查找输出端口使用 `最长前缀匹配规则`。 249 | - 一旦确认某分组的输出端口,该分组即可进入交换结构。 250 | - 匹配加动作。 251 | 252 | * 交换结构 253 | - 连接路由器的输入端口和输出端口。 254 | - 三种交换技术:内存、纵横式、总线。 255 | - 内存和总线是阻塞交换;纵横式为非阻塞交换。 256 | 257 | * 输出端口 258 | - 存储从交换结构接受的分组。 259 | - 向链路层和物理层传输接受的分组。 260 | 261 | * 路由选择处理器 262 | - 传统路由器:执行路由选择协议、维护并计算路由转发表。 263 | - SDN 路由器:负责与远程控制器通信,接受转发表执行操作。 264 | * 端口排队 265 | - 输入排队:线路前部阻塞(HOl),一个输入队列中排队的分组必须等待通过交换结构发送,因为它被位于线路前不得另一个分组所阻塞。 266 | - 输出排队 267 | 268 | * 缓存用完的移除策略:弃尾、删除已缓存的分组。具体算法:主动队列管理、随机早期检测。 269 | * 分组调度:FIFO、优先权排队、循环和加权公平排队。 270 | * 输入端口、输出端口、交换结构一般由硬件组成。 271 | * 当路由器的缓存空间用完时,再进入路由器的分组会被丢弃。 272 | * 将分组转发类比为汽车进入和离开立交桥: 273 | - 入口道路和入口对应输入端口。 274 | - 出口对应输出端口。 275 | - 环形交叉路对应交换结构。 276 | - 路线选择对应路由选择。 277 | * 转发表是由路由选择处理器计算和更新、或者转发表接受来自远程 SDN 控制器的内容。 278 | 279 | #### IPv4(32 bit - 4 byte) 280 | * 数据报格式 281 | - 版本、首部长度、服务类型、数据报长度。 282 | - 16 bit 标识、标志、13 bit 片偏移。 283 | - 寿命、上层协议、首部检验和。 284 | - 32 bit 源 IP 地址。 285 | - 32 bit 目的 IP 地址。 286 | - 选项(如果有的话)。 287 | - 数据。 288 | * 版本(4 bit):规定了 IP 协议的版本,通过查看版本号,路由器能够确定如何解释 IP 数据报剩余部分。 289 | * 首部长度(4 bit):确定 IP 数据报中载荷实际开始的地方。 290 | * 服务类型:区分不同类型的 IP 数据报。比如特别要求低时延、高吞吐量的数据报。 291 | * 数据报长度:IP 数据报的总长度。一般很少超过 1500 字节。 292 | * 标识、标志、片偏移:与 IP 分片有关。 293 | * 寿命(TTL):确保数据不会永远在网络中循环,每过一台路由器减 1,若为 0 ,则数据报被丢弃。 294 | * 协议:指明上层运输层协议。值为 6 表明 TCP,为 17 表明 UDP。 295 | * 首部检验和:帮助路由器检测收到的 IP 数据报中的比特错误。 296 | - 计算方式:将首部中每 2 个字节当做一个数,用反码算数进行求和。 297 | - 路由器一般会丢弃检测数错误的数据报。 298 | - 为什么 IP 和 TCP、UDP 都进行错误检测?1) IP 只检测首部,TCP/UDP 检测整个报文段;2)IP 的上层不一定是 TCP/UDP。 299 | * 源和目的 IP 地址:确定传输数据的起始点。 300 | * 数据:需要传输的数据。 301 | * MTU:链路层承载的最大数据量,最大传送单元。 302 | * 数据报通过分片来满足各个链路层不同 MTU 的问题。数据报的组装工作在端系统中执行。 303 | * 子网:分开主机和路由器的每个接口,产生的几个隔离的网络岛。隔离的网络被称为子网。 304 | * 子网掩码:同一子网相同的前缀。 305 | * 因特网地址分配策略:无类别域间路由选择。 306 | * 利用子网掩码来减少路由转发表的长度。 307 | * 255.255.255.255 为广播地址,报文会发送到同一网络的所有主机。 308 | * 主机地址获取:动态主机配置协议 309 | - 主机可自动获取 IP 地址。 310 | - 自动配置连接主机的网络内容。 311 | * DHCP 配置流程: 312 | - DHCP 服务器发现。 313 | - DHCP 服务器提供。 314 | - DHCP 请求。 315 | - DHCP ACK。 316 | * NAT(Network Address Translation):将内部的 IP 地址转为可用的外部 IP 地址。 317 | - NAT 通过 NAT 转换表来标识分组应该转发到那个内部主机。 318 | - 外部 IP -> `NAT` -> 内部IP。 319 | 320 | #### IPv6 321 | * 数据报格式 322 | - 版本、流量类型、流标签。 323 | - 有效载荷长度、下一个首部、跳限制。 324 | - 源地址(128 bit)。 325 | - 目的地址(128 bit)。 326 | - 数据。 327 | 328 | * 版本:4 bit 用来标识 IP 版本号,值为6(需要注意的是,将值改为 4 ,并不能创建一个合法的 IPv4 数据报)。 329 | * 流量类型:8 bit,作用和 IPv4 中的 服务类型 字段作用类似。 330 | * 流标签:20 bit,用于标识一条数据报的流,对六种的某些数据报给出优先权。 331 | * 有效荷载长度:16 bit,限定了除定长 40 字节数据报首部的其他字节数量。即装载数据的长度。 332 | * 下一个首部:同 IPv4。 333 | * 跳限制:转发数据报每经过一个路由器减一,若为零则数据报被丢弃。 334 | * 源地址和目的地址。 335 | * 数据:数据报的有效荷载部分。 336 | * IPv6 不允许在中间路由器进行分片和重新组装,该操作只能在源与目的地执行。若数据报太大则路由器直接丢弃数据并返回一个 ”分组太大“ 的 ICMP 差错报文。 337 | * IPv6 不会校验数据。 338 | 339 | #### 控制平面 340 | * 路由器转发表的维护策略: 341 | - 每路由器控制 - 每台路由器都有自己的控制逻辑。 342 | - 逻辑集中式控制 - 由服务器统一派发控制逻辑。 343 | 344 | 345 | ### 链路层 346 | 对应协议: 347 | 348 | ### 物理层 349 | 对应协议: 350 | 351 | 352 | 353 | ### 硬件工作原理、名词解释 354 | * 集线器 355 | * 链路层交换机 356 | * 路由器 357 | * IP(Internet Protool) 地址:位于网络层,24 位(IPv4) 或 48 位 (IPv6) ,运营商提供,可修改,用来定位计算机的逻辑地址。 358 | * MAC(Media Access Control) 地址:位于数据链路层,48位(6字节),厂家烧制,不可修改,用来定位计算机的物理地址。 359 | * 为什么有了 Mac 地址还需要 IP 地址? 360 | -------------------------------------------------------------------------------- /OperationSystem/对递归的理解.md: -------------------------------------------------------------------------------- 1 | 什么是递归:自己调用自己的行为统称为递归。 2 | 3 | 举个例子:从前有座山,山里有座庙,庙里有个老和尚和小和尚,老和尚对小和尚说,从前有座山,山里有座庙,庙里有个老和尚和小和尚,老和尚对小和尚说,从前有座山,山里有座庙,庙里有个老和尚和小和尚,老和尚对小和尚说...... 这个就是无限递归。 4 | 5 | 它是通过将一个大问题分解成若干个小问题来解决,小问题都解决了,大问题自然也就解决了。 6 | 7 | 它主要包含下面两个要素: 8 | * 递归出口(终止递归的条件) 9 | * 递归表达式(递归表达式) 10 | 11 | 用斐波那契数列(0, 1, 1, 2, 3, 5, 8...),来举个例子来说明一下上面的要素。看一下 for 循环版的代码: 12 | ``` 13 | func fib(_ target: Int) -> Int { 14 | if target == 0 { return 0 } 15 | if target == 1 || target == 2 { return 1 } 16 | 17 | var prev = 1 18 | var next = 1 19 | for _ in 3...target { 20 | (prev, next) = (next, prev + next) 21 | } 22 | return next 23 | } 24 | ``` 25 | 首先来分析一下递归出口,通过上面的代码可以发现 target == 0 或者 target == 1 即是递归出口。 26 | ``` 27 | func fib1(_ target: Int) -> Int { 28 | // 递归出口 29 | if target == 0 { return 0 } 30 | if target == 1 || target == 2 { return 1 } 31 | return 0 32 | } 33 | ``` 34 | 接着,来分析一下递归表达式,斐波那契数列的规律是前面两数之和为当前数,即 fib1(n - 1) + fib1(n - 1) = fib1(n),这样就凑齐了递归的两要素。那整个递归函数也就出炉了: 35 | ``` 36 | func fib1(_ target: Int) -> Int { 37 | // 递归出口 38 | if target == 0 { return 0 } 39 | if target == 1 || target == 2 { return 1 } 40 | // 递归表达式 41 | return fib1(target - 2) + fib1(target - 1) 42 | } 43 | ``` 44 | 45 | 代码流程示意图: 46 | ``` 47 | ------ call ------- call -------- call ------ 48 | | | ---->| | ----> | | -->...| | 49 | |f(n)| |f(n-1)| |f(n-2)| | f(0)| 50 | | | <----| | <-----| | <--...| | 51 | ------ back ------- back -------- back ------- 52 | ``` 53 | 54 | 递归的缺点: 55 | * 函数的调用需要出栈入栈,消耗的资源多。 56 | * 若数据量过大会导致 Stack Overflow。 -------------------------------------------------------------------------------- /OperationSystem/移动端内存管理机制的理解.md: -------------------------------------------------------------------------------- 1 | https://blog.indoorway.com/swift-vs-kotlin-the-differences-in-memory-management-860828edf8 2 | 3 | ### Swift 4 | ARC:自动引用计数,对象循环引用会造成内存泄漏。 5 | 6 | ### Kotlin 7 | CMS:Concurrent Mark Sweep:通过 GCRoot 来管理,每个创建的对象有一个 mark 位,默认为 0,当对象被访问后,mark 位会被置为 1。对象循环引用不会造成内存泄漏。 -------------------------------------------------------------------------------- /OperationSystem/除法与右移.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengzhihao123/FZHBlog/23c8cc51470ad7981dc0154ce7bddac332513ba1/OperationSystem/除法与右移.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FZHBlog 2 | 3 | `读书百遍其义自见!` 4 | 5 | ## 读书 6 | ### 文学类 7 | * [一生自在](https://github.com/fengzhihao123/FZHBlog/blob/master/ReadBookNote/一生自在.md) 2020.05.05 8 | * 谈美 2020.10.01 9 | * 彷徨 2021.02.19 10 | 11 | ### 技术类 12 | * [计算机是怎样跑起来的](https://github.com/fengzhihao123/FZHBlog/blob/master/ReadBookNote/%E8%AE%A1%E7%AE%97%E6%9C%BA%E6%98%AF%E6%80%8E%E6%A0%B7%E8%B7%91%E8%B5%B7%E6%9D%A5%E7%9A%84.md) 2020.04.25 13 | * 代码大全 2020.07.01 14 | * [重构](https://github.com/fengzhihao123/FZHBlog/blob/master/ReadBookNote/重构.md) 2020.08.20 15 | 16 | ## 数据结构 17 | ### 链表 18 | * [初识链表](https://github.com/fengzhihao123/FZHBlog/blob/master/DataStructureAlgorithm/Datastructure/初识链表.md) 19 | 20 | ## 计算机网络 21 | ### 读书笔记 22 | * [网络是怎样连接的](https://github.com/fengzhihao123/FZHBlog/blob/master/Network/网络是怎样连接的.md) 2020.09.10 23 | * [计算机网络自顶向下方法](https://github.com/fengzhihao123/FZHBlog/blob/master/Network/计算机网络自顶向下.md) 2020.12.05 24 | ### 知识点总结 25 | 26 | ## 操作系统 27 | * [对递归的理解](https://github.com/fengzhihao123/FZHBlog/blob/master/OperationSystem/对递归的理解.md) 28 | 29 | ## iOS 30 | * [如何使用断点来判断ViewController内存泄漏](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Objective-C/如何使用断点来判断ViewController内存泄漏.md) 31 | 32 | ### Swift 33 | * [Type Erase(类型擦除)]() 34 | * [Struct & Class]() 35 | * [Swift 基本类型所占字节数](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/Swift%20基本类型所占字节数.md) 36 | * [Final 关键字](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/final%20关键字的理解.md) 37 | * [加密算法 - SHA256 - Swift](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/SHA256-Swift.md) 38 | * [关于 Swift 访问控制的总结](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/关于%20Swift%20访问控制的总结.md) 39 | * [快速了解 Swift](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/快速了解%20Swift.md) 40 | * [如何迭代 Swift 中枚举的所有 case](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/如何迭代%20Swift%20中枚举的所有case.md) 41 | * [Swift 什么情况会发生内存访问冲突](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/Swift%20什么情况会发生内存访问冲突.md) 42 | * [Swift 的属性观察者](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/Swift%20的属性观察者.md) 43 | * [Property Wrappers](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/通过%20Property%20Wrappers%20简化代码.md) 44 | 45 | #### 性能篇 46 | * [Copy On Write](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/Swift%20性能/Copy%20On%20Write.md) 47 | 48 | ### Swift 读源码系列 49 | 50 | * [Array.swift](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/读%20Swift%20源码系列/Array.md) 51 | * [Reverse.swift](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/读%20Swift%20源码系列/Reverse.md) 52 | * [Zip.swift](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/读%20Swift%20源码系列/Zip.md) 53 | * [ArrayBody](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/读%20Swift%20源码系列/ArrayBody.md) 54 | * [Algorithm.swift](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/读%20Swift%20源码系列/Algorithm.md) 55 | * [Comparable](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/读%20Swift%20源码系列/Comparable.md) 56 | * [Print](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/读%20Swift%20源码系列/Print.md) 57 | 58 | ### 三方库源码阅读 59 | * [Delegated 源码阅读](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/读%20Swift%20源码系列/Delegated%20源码阅读.md) 60 | 61 | ### WWDC 62 | #### WWDC 2019 63 | * [Advances in Foundation](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/WWDC/WWDC2019/Advances%20in%20Foundation.md) 64 | * [Introduce Combine](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/WWDC/WWDC2019/Introduce%20Combine.md) 65 | 66 | #### WWDC 2020 67 | * [Unsafe Swift](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/WWDC/WWDC2020/unsafe_swift.md) 68 | * [List in UICollectionView](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/WWDC/WWDC2020/Lists%20in%20UICollectionView.md) 69 | * [Eliminate animation hitches with XCTest](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/WWDC/WWDC2020/Eliminate%20animation%20hitches%20with%20XCTest.md) 70 | * [Embrace Swift Type Inference](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/WWDC/WWDC2020/Embrace%20Swift%20Type%20Inference.md) 71 | * [Why is my app getting killed](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/WWDC/WWDC2020/why%20is%20my%20app%20getting%20killed.md) 72 | 73 | ### UI 74 | [通过functionBuilder 简化 UIAlertController 的调用](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/UI/通过functionBuilder%20简化%20UIAlertController%20的调用.md) 75 | 76 | 77 | [每日练习](https://github.com/fengzhihao123/FZHBlog/blob/master/DataStructureAlgorithm/每日算法练习.md) 78 | -------------------------------------------------------------------------------- /ReadBookNote/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengzhihao123/FZHBlog/23c8cc51470ad7981dc0154ce7bddac332513ba1/ReadBookNote/.DS_Store -------------------------------------------------------------------------------- /ReadBookNote/一生自在.md: -------------------------------------------------------------------------------- 1 | ## 一生自在 2 | 本书一共五章内容,通过这五章内容得以窥探季羡林先生的一生,以及老先生的人生处世之学问。 3 | 4 | ### 要点摘录: 5 | * 成功 = 天资 + 勤奋 + 机遇,前后两者皆不可控,所以我们只能在勤奋上下功夫。`个人觉得天资要比勤奋重要得多,但是天资不是我们可控的,所以天资不佳的只能通过后期勤奋来弥补。` 6 | * 我们“人”的“生”,都绝对是被动的。没有哪一个人能现制定一个诞生计划,然后再下生,一步步让计划实现。`没有好剧本,那也只能硬着头皮往下演了。` 7 | * “食色,性也。”食是为了解决生存和温饱的问题,色是为了解决发展问题,也就是所谓传宗接代。`读完这句话感觉有点悲哀,万物之灵只不过是基因传宗接代的载体罢了。` 8 | * 所以我说不完满才是人生。这是一个平凡的道理,但是真能了解其中的意义,对己对人都有好处。对己,可以不烦不燥;对人,可以相互谅解。这会大大地有利于整个社会的安定团结。 9 | * 如果人生真有意义和价值的话,其意义与价值就在于对人类发展的承上启下、承前启后的责任感。 10 | 11 | 通过一本书去过一段完整的人生,去学习、去感悟、去成长。推荐大家阅读。 12 | -------------------------------------------------------------------------------- /ReadBookNote/代码大全.md: -------------------------------------------------------------------------------- 1 | 耗时两个多月,终于把这本大部头读完了🤣。本书的核心思想总结起来就是四个字:`简单、易读`。从声明变量到项目架构,手把手教你如何使代码做到上面的四个字。 2 | ### 变量 3 | ### 判断语句与循环语句 4 | * if 嵌套层数 <= 3;嵌套 > 3 的做法:1)重新设计代码逻辑;2)替换为 switch-case;3)将子项抽取为函数 5 | * 6 | ### 函数 7 | * 参数个数限制 8 | * 函数体行数限制 9 | ### 类 10 | ### 测试 11 | ### 以人为本 -------------------------------------------------------------------------------- /ReadBookNote/浪潮之巅.md: -------------------------------------------------------------------------------- 1 | 1、国外基金会慈善家 2 | 根据大家的报道,都以为比尔盖茨是首富慈善家,殊不知背后他是为了躲避遗产税及其他的税务来变相给子女留更多的钱。所以以后大多数人云亦云的新闻都不可靠,经不起推敲。 3 | 4 | ``` 5 | 如果盖茨卖掉自己手里长期持有的微软股票卖掉,他将缴纳 15%的资产增值税, 如果他兑现短期的投资所得,则要交高达 35%联邦税,而在克林顿时代更高达 38%。我们不 妨为盖茨算一笔账,如果他将自己的股票卖掉转给孩子,那么,一个亿只剩下 1×(1-45%)× (1-20%)×(1-15%)=37%,即三千七百万。假如我们将这三千七百万拿去投资,按每年 10% 的投资回报算(这在美国是一个合理的数),每年投资收入按平均交 30%的税率缴税,那么, 到三十年后盖茨的孩子将获得 2.8 亿美元。 6 | 如果想少缴税,而将财产尽可能多地留给孩子,唯一的办法是将财产捐给自己的慈善基 金会。这样做可以免除三种税,第一次买股票的资产增值税,遗产税和每年的投资增值税。 7 | 8 | 盖茨和那些真正的将毕生积蓄无条件捐给他人而不是自己的基金会的那些真正的慈善家是不同的。 9 | ``` 10 | 11 | 2、现实即合理,合理即现实。现在存在的现象, 当初产生它的时候必然有产生它的原因和理由。如果这个理由将来不存在了,终究有一天它也会消亡。 12 | 13 | 3、托尔斯泰讲,幸福的家庭都是相似的,不幸的家庭各有各的不幸。在信息工业中,这句话要反过来讲,成功的公司各有各的绝招,失败的公司倒是有不少共同之处。 14 | -------------------------------------------------------------------------------- /ReadBookNote/程序是怎样跑起来的.md: -------------------------------------------------------------------------------- 1 | ## 程序是怎样跑起来的 2 | ### 对程序员来说 CPU 是什么 3 | * CPU 的组成部分:寄存器、控制器、运算器、时钟。 4 | * 汇编语言转化为机器语言的过程为汇编;机器语言转化为汇编语言的过程为反汇编。 5 | * -------------------------------------------------------------------------------- /ReadBookNote/计算机是怎样跑起来的.md: -------------------------------------------------------------------------------- 1 | ## 计算机是怎样跑起来的 2 | 3 | 本书系统的讲解了计算机相关知识,完整的从计算机硬件一路讲到软件、面向对象思想、算法、数据结构、数据库、网络、加密等等。本书的目的主要是从宏观的角度去全面了解计算机相关的基础知识,每个大块都是讲的基础,要是想深入了解某个领域还需看相关的专业书,比如算法这块知识简单的告诉我们什么是算法以及几个必会的算法,并没有深入的讲解二叉树等。这本书推荐大家可以看一下,画个一下午的时间就能看完,可以对计算机基础有个很好的了解。 4 | 5 | ### 名词缩写 6 | * LAN - Local Area Network 7 | * XML - Extensible Markup Language 8 | * TCP/IP - Transition Control Protocol、Internet Protocol 9 | * CPU - Central Processing Unit 10 | * DBMS - DataBase Management System 11 | 12 | ### 个人总结 13 | * 计算机三大原则: 14 | - 计算机是执行输入、计算、输出的机器。 15 | - 程序是指令和数据的集合。 16 | - 计算机的处理方式有时与人的思维不一致。 17 | * OOP三大原则:继承(子类享受父类的信息)、封装(将不必要的信息封装起来)、多态(不同类对同一消息的表现不同) 18 | * 计算机硬件三大块:CPU、内存、I/O 19 | * 需要熟悉的经典算法:辗转相除法、埃拉托斯特尼筛法、顺序查找、二分查找、哈希查找、冒泡排序、快速排序 20 | * 哨兵的含义和用处 21 | * 创建模块是要保持易维护、易用 22 | * 对称加密算法:使用同一个密钥;非对称加密:使用公钥和私钥 23 | * XML:格式化数据,易读,但文件体积会变大 24 | * 大端:数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中;小端:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中 25 | -------------------------------------------------------------------------------- /ReadBookNote/重构.md: -------------------------------------------------------------------------------- 1 | 最近花了一个月的时间将重构从头到尾读了一遍,颇有收获,推荐没读过的朋友都通读一遍,这样能让读你代码的朋友少掉点头发😏。 2 | 3 | ### 测试的重要性 4 | 测试是重构的前提,当我们在重构一个函数后,完善的单元测试可以使我们快速的知道重构后的函数是否改变了原始的行为。所以,在我们完成一个功能时,应添加完备的测试代码来保证后续重构。 5 | 6 | ### 何时重构 7 | 重构不一定非得等专门的一段时间,可以随时进行。当你开发一个新需求时,如果发现在现有的代码上无法便捷的添加新需求的代码,这就说明你应该重构代码,从而能轻松的添加新需求的代码。 8 | 9 | 或者,当你阅读之前的代码觉得以前的代码无法很好地表述现有的场景时,你也应该重构来使代码更好的表述现有的场景,从而使代码更易读、易理解。 10 | 11 | ### 拆分大函数 12 | 原因:当一个函数超过 50 行的时候,你就应该警惕它,问问自己是否该函数符合单一原则。若它同时处理了多件事,那你应该使用 Swift 中的嵌套函数来重构它。 13 | 14 | 待重构函数(举例函数的行数并不多,但是也应该重构): 15 | ``` 16 | func calculateScore(stus: [Student]) -> Double { 17 | // 获取所有学生的成绩 18 | var scores = [Double]() 19 | for stu in stus { 20 | scores.append(stu.score) 21 | } 22 | 23 | // 只计算 60 - 90 之间的平均值 24 | var valueScores = [Double]() 25 | for score in scores { 26 | if score > 60 && score < 90 { 27 | valueScores.append(score) 28 | } 29 | } 30 | 31 | // 计算平均值 32 | var totalScore = 0.0 33 | valueScores.forEach { totalScore += $0 } 34 | let aveScore = totalScore / Double(valueScores.count) 35 | return aveScore 36 | } 37 | 38 | ``` 39 | 40 | 重构后的函数: 41 | ``` 42 | func calculateScore(stus: [Student]) -> Double { 43 | // 获取所有学生的成绩 44 | func getScores() -> [Double] { 45 | return stus.map { $0.score } 46 | } 47 | 48 | // 只计算 60 - 90 之间的平均值 49 | func getValueScores(scores: [Double]) -> [Double] { 50 | return scores.filter { $0 > 60 && $0 < 90 } 51 | } 52 | 53 | // 计算平均值 54 | func calculate(valueScores: [Double]) -> Double { 55 | let totalScore = valueScores.reduce(0) { (res, cur) -> Double in 56 | return res + cur 57 | } 58 | return totalScore / Double(valueScores.count) 59 | } 60 | 61 | let valueScores = getValueScores(scores: getScores()) 62 | return calculate(valueScores: valueScores) 63 | } 64 | ``` 65 | 66 | ### 拆分循环 67 | 原因:一个循环负责多种逻辑,会使代码难以理解。 68 | 69 | 疑惑:拆分后可能会对代码的性能造成影响?现在的硬件设施一般都不会造成可感知的影响。如果会有影响,逻辑清晰的代码也会比不清晰的代码更加好改。 70 | 71 | 待重构函数: 72 | ``` 73 | var count = 0 74 | let name = "refactor" 75 | 76 | for _ in 0..<5 { 77 | count += 1 78 | print(name) 79 | } 80 | ``` 81 | 82 | 重构后的函数: 83 | ``` 84 | for _ in 0..<5 { 85 | count += 1 86 | } 87 | 88 | for _ in 0..<5 { 89 | print(name) 90 | } 91 | ``` 92 | 93 | ### 用高阶函数代替循环 94 | 原因:高阶函数比 for-in 更加易读,且代码简洁。这意味着代码出错的几率更小。 95 | 96 | 待重构函数: 97 | ``` 98 | /// 添加的产品是否包含手机 99 | func hasCellPhone(goodsItem: [GoodsItem]?) -> Bool { 100 | var result = false 101 | let cellPhoneType = "21" 102 | for item in goodsItem ?? [] { 103 | if let goodsType = item.goodsType { 104 | if goodsType.code == cellPhoneType { 105 | result = true 106 | break 107 | } 108 | } 109 | } 110 | return result 111 | } 112 | ``` 113 | 重构后的函数: 114 | ``` 115 | /// 添加的产品是否包含手机 116 | func hasCellPhone(goodsItem: [GoodsItem]?) -> Bool { 117 | guard let items = goodsItem else { return false } 118 | let cellPhoneType = "21" 119 | let result = items.filter { $0.goodsType?.code == cellPhoneType } 120 | return !result.isEmpty 121 | } 122 | ``` 123 | 124 | ### 使用 guard 拆解嵌套if 125 | 原因:使用 guard 可以是代码逻辑更加清晰易读。 126 | 127 | 待重构函数: 128 | ``` 129 | func buyStock(buyer: Buyer) { 130 | if buyer.age > 18 { 131 | if buyer.accountIsValid { 132 | if buyer.accountMoney > 0 { 133 | buyer.buy(stock: "") 134 | } 135 | } 136 | } 137 | } 138 | ``` 139 | 140 | 重构后的函数: 141 | ``` 142 | func buyStock(buyer: Buyer) { 143 | guard buyer.age > 18 && buyer.accountIsValid && buyer.accountMoney > 0 else { 144 | return 145 | } 146 | buyer.buy(stock: "") 147 | } 148 | ``` 149 | 150 | ### 保持数据的不可变性 151 | 原因:如果数据是可变的,那么在未来的某个地方可能会修改它,而你并不知道,这样就会造成代码的错误(可能会发生的,就一定会发生)。如果数据开始就是不可修改的,那就能保证代码的正确性。 152 | 153 | 待重构函数: 154 | ``` 155 | var scores = [Double]() 156 | for stu in stus { 157 | scores.append(stu.score) 158 | } 159 | ``` 160 | 161 | 重构后的函数: 162 | ``` 163 | let scores = stus.map { $0.score } 164 | ``` 165 | 166 | 实践才是检验真理的唯一标准,希望看完这篇文章你能立马开始项目的重构。🤩 -------------------------------------------------------------------------------- /iOS/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengzhihao123/FZHBlog/23c8cc51470ad7981dc0154ce7bddac332513ba1/iOS/.DS_Store -------------------------------------------------------------------------------- /iOS/Objective-C/Objective-C 高级编程:iOS 与 OS X 多线程和内存管理.md: -------------------------------------------------------------------------------- 1 | ### 内存管理 2 | ### Block 3 | ### GCD -------------------------------------------------------------------------------- /iOS/Objective-C/如何使用断点来判断ViewController内存泄漏.md: -------------------------------------------------------------------------------- 1 | 1、选择 Breakpoint Navigator 2 | 3 | 2、点击 +,选择 Symbolic Breakpoint... 4 | 5 | 3、将 `Symbol` 设值为 `-[UIViewController dealloc]` 6 | 7 | 4、点击 `Add Action` 按钮,选择 `Sound`,再设值为自己喜欢的声音(此处设值为 ping) 8 | 9 | 5、再次点击 `+` ,设值输出 action,选择 `Log Message`,设置打印的值:`--- dealloc @(id)[$arg1 description]@` 10 | 11 | 6、勾选 `Automatically continue after evaluating actions`,这样每次断点会自动通过 12 | 13 | 最后,通过上面的 6 步,你就可以享受无码检测内存泄漏的快感了😏。 -------------------------------------------------------------------------------- /iOS/Swift/SHA256-Swift.md: -------------------------------------------------------------------------------- 1 | ### 引子 2 | 在编写个人项目的时候,需要给字符串加密。于是就编写了以前经常使用的MD5加密算法。但是该算法在 XCode 中显示在 iOS13 以后将被废弃,系统推荐使用 SHA256 算法。 3 | 下面是被警告的 MD5 算法。 4 | ``` 5 | var md5: String { 6 | let utf8 = cString(using: .utf8) 7 | var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) 8 | CC_MD5(utf8, CC_LONG(utf8!.count - 1), &digest) 9 | return digest.reduce("") { $0 + String(format:"%02x", $1) } 10 | } 11 | ``` 12 | ### 为什么废弃MD5 13 | 通过 Xcode 的警告可以了解到,MD5 在加密方面已经遭到破坏,为了数据安全不应该再使用它,应该使用更加安全的 SHA256 算法。 14 | 15 | ### 什么是SHA256 16 | SHA 是 Secure Hash Algorithm 的缩写,即安全哈希算法。 17 | 18 | SHA 的计算流程:对数字数据进行数学运算 -> 将计算出的哈希值与已知预期的哈希值进行比较 -> 从而确定数据的完整性。 19 | 20 | `哈希值可以从任意数据段生成,但该过程不能逆向,即不能从哈希值逆向生成数据。` 21 | 22 | SHA256 也成为 SHA2,它是从[SHA1](https://en.bitcoinwiki.org/wiki/SHA-1)进化而来,目前没有发现SHA256被破解,但随着计算机计算能力越来越强大,它肯定会被破解,所以SHA3已经在路上了。 23 | ### 如何实现 24 | ``` 25 | extension String { 26 | var sha256: String { 27 | let utf8 = cString(using: .utf8) 28 | var digest = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) 29 | CC_SHA256(utf8, CC_LONG(utf8!.count - 1), &digest) 30 | return digest.reduce("") { $0 + String(format:"%02x", $1) } 31 | } 32 | } 33 | 34 | var str = "hello, world" 35 | print(str.sha256) // 09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b 36 | ``` 37 | ### 参考 38 | * [SHA256](https://en.bitcoinwiki.org/wiki/SHA-256) -------------------------------------------------------------------------------- /iOS/Swift/Swift 中的 Struct 与 Class.md: -------------------------------------------------------------------------------- 1 | ### Swift 中的 Struct 与 Class 2 | 本文基于 Swift5.2 3 | 4 | * 是什么 5 | * 怎么用 6 | * 主要区别 7 | * 总结 -------------------------------------------------------------------------------- /iOS/Swift/Swift 什么情况会发生内存访问冲突.md: -------------------------------------------------------------------------------- 1 | 众所周知,Swift 是一门类型安全的语言,它会通过编译器报错来阻止你代码中不安全的行为。比如变量必须在使用之前声明、变量被销毁之后内存不能在访问、数组越界等问题。 2 | 3 | Swift 会通过对于修改同一块内存,同一时间以互斥访问权限的方式(同一时间,只能有一个写权限),来确保你的代码不会发生内存访问冲突。虽然 Swift 是自动管理内存的,在大多数情况下你并不需要关心这个。但理解何种情况下会发生内存访问冲突也是十分必要的。 4 | 5 | 首先,来看一下什么是内存访问冲突。 6 | 7 | ### 内存访问冲突 8 | 当你设值或者读取变量的值得时候,就会访问内存。 9 | ``` 10 | var age = 10 // 写权限 11 | print(age) // 读权限 12 | ``` 13 | 14 | 当我们对同一块内存,同时进行读写操作时,会产生不可预知的错误。比如上面的 age,假如在你读取它值的期间有别的代码将它设为 20,那么你读取到的有可能是 10,也有可能是 20。这就产生了问题。 15 | 16 | 内存访问冲突:对同一块内存,同时进行读写操作,或者同时进行多个写入操作时,就会造成内存访问冲突。 17 | 18 | 了解了什么是内存访问冲突,下面来看下什么情况下回造成内存访问冲突。 19 | 20 | ### In-Out 参数 21 | 当 In-Out 参数为全局变量,并且该变量在函数体内被修改时,就会造成内存访问冲突。比如下面的代码: 22 | ``` 23 | var age = 10 24 | 25 | func increment(_ num: inout Int) { // step1 26 | num += age // step2 27 | } 28 | increment(&age) 29 | ``` 30 | increment(:) 在整个函数体内,对所有的 In-Out 参数都有写权限。在上述代码中,step1 已经获得了 age 的写权限,而 step2 有得到了 age 的读权限,这样就造成了同一块内存,同时进行了读写操作。从而造成了内存访问冲突。 31 | 32 | 上面的问题可以通过将 age 拷贝一份来解决: 33 | ``` 34 | // step1 35 | var copyOfAge = age 36 | increment(©OfAge) 37 | age = copyOfAge 38 | ``` 39 | 40 | step1 将 age 的值拷贝到另一块内存上,这样在函数体内就是存在对 age 的读权限和对 copyOfAge 的写权限,因为 age 和 copyOfAge 是两块内存,所以就不会造成内存访问冲突。 41 | 42 | ### 结构体的 mutating 函数 43 | 对于结构体的 mutating 函数来说,它整个函数体都有 self 的写权限。 44 | ``` 45 | struct Person { 46 | var age: Int 47 | mutating func increment(_ num: inout Int) { 48 | age += num 49 | } 50 | } 51 | 52 | var p1 = Person(age: 10) 53 | p1.increment(&p1.age) 54 | ``` 55 | 上述的代码编译器会报错:`Overlapping accesses to 'p1', but modification requires exclusive access; consider copying to a local variable`。很明显这是一个内存访问冲突。 56 | 57 | In-Out 参数获得了 p1 的写权限;mutating 函数也获得了 p1 的写权限。同一块内存,同时有两个写操作。造成内存访问冲突。可以通过同上的拷贝操作来解决。 58 | 59 | ### 值类型的属性 60 | 对于结构体、枚举、元祖等值类型来说,修改它们的属性就相当于修改它们整个的值。比如下面的代码: 61 | ``` 62 | func increment(_ num1: inout Int, _ num2: inout Int) { 63 | print(num1 + num2) 64 | } 65 | 66 | var tuple = (age: 10, height: 20) 67 | increment(&tuple.age, &tuple.height) 68 | ``` 69 | `&tuple.age` 拿到了 tuple 的写权限,`&tuple.height` 又拿了 tuple 的写权限。同一块内存,同时有两个写操作。造成内存访问冲突。 70 | 71 | 这个问题可以通过局部变量来解决: 72 | ``` 73 | func someFunction() { 74 | var tuple = (age: 10, height: 20) 75 | increment(&tuple.age, &tuple.height) 76 | } 77 | ``` 78 | 因为在 someFunction() 函数里,age 和 height 没有产生任何的交互(没有在其期间去读取或者写入 age 和 height),所以编译器可以保证内存安全。 79 | 80 | ### 总结 81 | * 对同一块内存,同时进行读写操作,或者同时进行多个写入操作时,就会造成内存访问冲突。 82 | * 会造成内存访问冲突的情况: 83 | * In-Out 为全局参数,并且在函数体内修改了它。 84 | * 结构体的 mutating 函数内修改结构体的值。 85 | * 同一值类型的多个属性当做函数的 In-Out 参数。 86 | -------------------------------------------------------------------------------- /iOS/Swift/Swift 基本类型所占字节数.md: -------------------------------------------------------------------------------- 1 | * Bool 占 1 字节 2 | * Int64 占 8 字节 3 | * Float 占 4 字节 4 | * Double 占 8 字节 5 | * String 占 16 字节 6 | * Character 占 16 字节 7 | 8 | ``` 9 | /// Bool 占 1 字节 10 | print("========Bool========") 11 | func boolPointer(_ p1: UnsafePointer, _ p2: UnsafePointer) { 12 | print("p1 address = \(p1), p2 address = \(p2)") 13 | } 14 | 15 | var boolX = true 16 | var boolY = false 17 | boolPointer(&boolX, &boolY) 18 | 19 | /// Int64 占 8 字节 20 | print("========Int========") 21 | func intPointer(_ p1: UnsafePointer, _ p2: UnsafePointer) { 22 | print("p1 address = \(p1), p2 address = \(p2)") 23 | } 24 | 25 | var intX = 1 26 | var intY = intX 27 | intPointer(&intX, &intY) 28 | 29 | /// Float 占 4 字节 30 | print("========Float========") 31 | func floatPointer(_ p1: UnsafePointer, _ p2: UnsafePointer) { 32 | print("p1 address = \(p1), p2 address = \(p2)") 33 | } 34 | 35 | var floatX: Float = 1.0 36 | var floatY = floatX 37 | floatPointer(&floatX, &floatY) 38 | 39 | /// Double 占 8 字节 40 | print("========Double========") 41 | func doublePointer(_ p1: UnsafePointer, _ p2: UnsafePointer) { 42 | print("p1 address = \(p1), p2 address = \(p2)") 43 | } 44 | 45 | var doubleX = 1.0 46 | var doubleY = doubleX 47 | doublePointer(&doubleX, &doubleY) 48 | 49 | /// String 占 16 字节 50 | print("========String========") 51 | func stringPointer(_ p1: UnsafePointer, _ p2: UnsafePointer) { 52 | print("p1 address = \(p1), p2 address = \(p2)") 53 | } 54 | 55 | var strX = "1" 56 | var strY = strX 57 | stringPointer(&strX, &strY) 58 | 59 | /// Character 占 16 字节 60 | print("========Char========") 61 | func charPointer(_ p1: UnsafePointer, _ p2: UnsafePointer) { 62 | print("p1 address = \(p1), p2 address = \(p2)") 63 | } 64 | 65 | var charX: Character = "1" 66 | var charY = charX 67 | charPointer(&charX, &charY) 68 | ``` 69 | -------------------------------------------------------------------------------- /iOS/Swift/Swift 性能/Copy On Write.md: -------------------------------------------------------------------------------- 1 | ### 开篇 2 | 在 Swift 中,有两种传值方式:引用类型(Class)和值类型(Struct/Enum)。而值类型有一个copy的操作,它的意思是当你传递一个值类型的变量的时候(给一个变量赋值,或者函数中的参数传值),它会拷贝一份新的值让你进行传递。你会得到拥有相同内容的两个变量,分别指向两块内存。 3 | 4 | 这样的话,在你频繁操作占用内存比较大的变量的时候就会带来严重的性能问题,Swift 也意识到了这个问题,所以推出了 Copy-on-Write 机制,用来提升性能。下面我们来说一下什么是 Copy-on-Write。 5 | 6 | ### 什么是 Copy-on-Write 7 | 当你有一个占用内存很大的一个值类型,并且不得不将它赋值给另一个变量或者当做函数的参数传递的时候,拷贝它的值是一个非常消耗内存的操作,因为你不得不拷贝它所有的东西放置在另一块内存中。 8 | 9 | 为了优化这个问题,Swift 对于一些特定的值类型(集合类型:Array、Dictionary、Set)做了一些优化,在对于 Array 进行拷贝的时候,当传递的值进行改变的时候才会发生真正的拷贝。而对于String、Int 等值类型,在赋值的时候就会发生拷贝。下面来看代码验证一下: 10 | 11 | #### 先看一下基本类型(Int、String等) 12 | ``` 13 | var num1 = 10 14 | var num2 = num1 15 | print(address(of: &num1)) //0x7ffee0c29828 16 | print(address(of: &num2)) //0x7ffee0c29820 17 | 18 | var str1 = "abc" 19 | var str2 = str1 20 | print(address(of: &str1)) //0x7ffee0c29810 21 | print(address(of: &str2)) //0x7ffee0c29800 22 | 23 | //打印内存地址 24 | func address(of object: UnsafeRawPointer) -> String { 25 | let addr = Int(bitPattern: object) 26 | return String(format: "%p", addr) 27 | } 28 | ``` 29 | 30 | `从上面的打印我们可以看出基本类型在进行赋值的时候就发生了拷贝操作` 31 | 32 | #### 在看一下集合类型 33 | ``` 34 | var arr1 = [1,2,3,4,5] 35 | var arr2 = arr1 36 | print(address(of: &arr1)) //0x6000023b06b0 37 | print(address(of: &arr2)) //0x6000023b06b0 38 | 39 | arr2[2] = 4 40 | print(address(of: &arr1)) //0x6000023b06b0 41 | print(address(of: &arr2)) //0x6000023b11f0 42 | ``` 43 | `从上面代码我们可以看出,当arr1赋值给arr2时并没有发生拷贝操作,当arr2的值改变的时候才发生了拷贝操作` 44 | 45 | Copy-on-Write 是一种非常好的方式去优化值类型的拷贝,虽然对于这套底层逻辑我们不用实现,但是了解这个机制对于我们来说还是非常必要的。通过上面的代码我们看到了 Copy-on-Write 机制是如何发生作用的,但是知道它如何应用是不够的,我们要做到知其然并且知其所以然。所以,接下来我们看一下 Swift 源代码是如何实现这一机制的。 46 | 47 | ### Copy-on-Write 如何实现的 48 | 你可以在[OptimizationTips.rst](https://github.com/apple/swift/blob/master/docs/OptimizationTips.rst)里发现如下代码: 49 | 50 | ``` 51 | final class Ref { 52 | var val : T 53 | init(_ v : T) {val = v} 54 | } 55 | 56 | struct Box { 57 | var ref : Ref 58 | init(_ x : T) { ref = Ref(x) } 59 | 60 | var value: T { 61 | get { return ref.val } 62 | set { 63 | if (!isUniquelyReferencedNonObjC(&ref)) { 64 | ref = Ref(newValue) 65 | return 66 | } 67 | ref.val = newValue 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | 该例子显示了如何用一个引用类型去实现一个拥有 Copy-on-Write 特性的泛型值类型T。具体逻辑就是当你进行 set 的时候判断是否有多个 reference,如果是多个 reference 则进行拷贝,反之则不会。 74 | 75 | #### 自定义的结构体并不支持 Copy-on-Write 76 | ``` 77 | struct Person { 78 | var name = "" 79 | } 80 | 81 | var p1 = Person(name: "aaa") 82 | print(address(of: &p1)) // 0x104d6a300 83 | var p2 = p1 84 | print(address(of: &p2)) // 0x104d6a310 85 | p2.name = "bbb" 86 | print(address(of: &p2)) // 0x104d6a310 87 | ``` 88 | 89 | 上述代码可以看出,虽然将 p1 赋值给了 p2,但它俩的内存地址依然是不同的。由此可见自定义的结构体并不能支持 Copy-on-Write。 90 | 91 | ### 总结 92 | * Copy-on-Write 是一种用来优化占用内存大的值类型的拷贝操作的机制。 93 | * 对于 Int、String 等基本类型的值类型,它们在赋值的时候就会发生拷贝,它们没有 Copy-on-Write 这一特性(因为Copy-on-Write带来的开销往往比直接复制的开销要大)。 94 | * 对于 Array、Dictionary、Se t类型,当它们赋值的时候不会发生拷贝,只有在修改的之后才会发生拷贝,即 Copy-on-Write。 95 | * 对于自定义的结构体,不支持 Copy-on-Write。 96 | 97 | ### 参考 98 | * [Understanding Swift Copy-on-Write mechanisms](https://medium.com/@lucianoalmeida1/understanding-swift-copy-on-write-mechanisms-52ac31d68f2f) -------------------------------------------------------------------------------- /iOS/Swift/Swift 的属性观察者.md: -------------------------------------------------------------------------------- 1 | 属性观察者,用来监听和响应属性值的改变。在每次属性值被设置新值时都会被调用,即使设置的新值跟属性的当前值一模一样。 2 | 3 | 可以添加属性观察者的属性类型: 4 | * 定义的存储属性。 5 | * 继承的存储属性。 6 | * 继承的计算属性。 7 | 8 | 属性观察者通过下面的两个函数来监听: 9 | * `willSet`:在值存储前被调用。 10 | * `didSet`:在值存储后调用。 11 | 12 | ### 定义的存储属性 13 | ``` 14 | class Person { 15 | var name: String = "jack" { 16 | willSet(newName) { 17 | print("newName == \(newName)") 18 | } 19 | 20 | didSet { 21 | print("oldName == \(oldValue)") 22 | } 23 | } 24 | } 25 | 26 | let p = Person() 27 | p.name = "rose" // name == jack;newName == rose name == rose;oldName == jack 28 | ``` 29 | 30 | 上面的代码定义了一个 Person 的类,该类包含一个 name 的存储属性。name 属性添加了属性观察者,并实现了 willSet/didSet 两个函数。 31 | 32 | 通过打印可以看出,在给 name 属性赋值之后,会先调用 willSet 打印 `name == jack;newName == rose`,再调用 didSet 打印 `name == rose;oldName == jack`。由此可以看出 `willSet` 是在值存储前被调用,而 `didSet` 是在值存储后调用。 33 | 34 | 35 | 需要注意的是,`当调用构造函数的时候,不会触发属性观察者。`详见下面的代码: 36 | ``` 37 | class Person { 38 | var name: String { 39 | willSet(newName) { 40 | print("name == \(name);newName == \(newName)") 41 | } 42 | 43 | didSet { 44 | print("name == \(name);oldName == \(oldValue)") 45 | } 46 | } 47 | 48 | init(name: String) { 49 | self.name = name 50 | } 51 | } 52 | 53 | let p = Person(name: "rose") 54 | ``` 55 | 上面的代码中,将 name 属性的初始值删掉,并提供了一个构造函数。当调用 let p = Person(name: "rose") 这行代码时,并没有任何打印。`说明调用构造函数并不会触发属性观察者。` 56 | 57 | ### 继承的存储属性 58 | ``` 59 | class Person { 60 | var name: String 61 | 62 | init(name: String) { 63 | self.name = name 64 | } 65 | } 66 | 67 | class Student: Person { 68 | override var name: String { 69 | willSet(newName) { 70 | print("name == \(name);newName == \(newName)") 71 | } 72 | 73 | didSet { 74 | print("name == \(name);oldName == \(oldValue)") 75 | } 76 | } 77 | } 78 | 79 | let stu = Student(name: "jack") 80 | stu.name = "rose" // name == jack;newName == rose name == rose;oldName == jack 81 | ``` 82 | 上面的代码声明了一个 Student 的类,继承自 Person。并对Student 继承 的 name 属性添加了属性观察者。 83 | 84 | 通过打印可以看出,在给 name 属性赋值之后,会先调用 willSet 打印 `name == jack;newName == rose`,再调用 didSet 打印 `name == rose;oldName == jack`。 85 | 86 | 虽然,调用父类构造函数不会触发属性观察者,但如果在子类中修改属性值,是会触发属性观察者的。详见下面的代码: 87 | ``` 88 | class Person { 89 | var name: String { 90 | willSet(newName) { 91 | print("name == \(name);newName == \(newName)") 92 | } 93 | 94 | didSet { 95 | print("name == \(name);oldName == \(oldValue)") 96 | } 97 | } 98 | 99 | init(name: String) { 100 | self.name = name 101 | self.name = "Person, \(name)" 102 | } 103 | } 104 | 105 | class Student: Person { 106 | override init(name: String) { 107 | super.init(name: name) 108 | self.name = "Student, \(name)" 109 | } 110 | } 111 | 112 | let p = Person(name: "mike") // 没有任何打印 113 | let stu = Student(name: "robin") 114 | // name == Person, robin;newName == Student, robin 115 | // name == Student, robin;oldName == Person, robin 116 | ``` 117 | 可以看到,在 Student 的构造器中添加 `self.name = "Student, \(name)"` 之后,就会触发属性观察者。 118 | 119 | 而在 Person 的构造器中,无论添加多少 `self.name = "Person, \(name)"` ,都不会触发属性观察者。 120 | 121 | ### 继承的计算属性 122 | ``` 123 | struct BodyInfo { 124 | var height = 0 125 | var weight = 0 126 | } 127 | 128 | class Person { 129 | var name: String 130 | var body = BodyInfo() 131 | 132 | var info: BodyInfo { 133 | get { 134 | return BodyInfo(height: body.height + 1, weight: body.weight + 1) 135 | } 136 | 137 | set { 138 | body.height = newValue.height + 1 139 | body.weight = newValue.weight + 1 140 | } 141 | } 142 | 143 | init(name: String) { 144 | self.name = name 145 | } 146 | } 147 | 148 | class Student: Person { 149 | override var info: BodyInfo { 150 | willSet(newInfo) { 151 | print("newInfo = \(newInfo)") 152 | } 153 | 154 | didSet { 155 | print("oldInfo = \(oldValue)") 156 | } 157 | } 158 | } 159 | 160 | 161 | let stu = Student(name: "jack") 162 | stu.info = BodyInfo(height: 15, weight: 20) 163 | // newInfo = BodyInfo(height: 15, weight: 20) 164 | // oldInfo = BodyInfo(height: 1, weight: 1) 165 | ``` 166 | 需要说明上面的代码逻辑上狗屁不通,只是当做代码说明🤣。 167 | 168 | 上述代码中,Person 定义了一个 info 的计算属性,然后 Student 中继承了 info,并给它添加了属性观察者。 169 | 170 | 通过 stu.info = BodyInfo(height: 15, weight: 20) 的调用,可以看到输出的结果是符合预期的。 171 | 172 | 需要说明的是,不能再 Person 中给 info 添加属性观察者,因为 willSet/didSet 是不能和 get 同时出现的,感兴趣的同学可以自己动手实践一下。编译器会报错:`'willSet' cannot be provided together with a getter`。 173 | 174 | ### 总结 175 | * 可以添加属性观察者的属性类型: 定义的存储属性/继承的存储属性/继承的计算属性。 176 | * 属性观察者的两个函数:*`willSet`:在值存储前被调用;`didSet`:在值存储后调用。 177 | * 调用本类的构造函数不会触发属性观察者,但在子类中的构造器中修改属性会触发。 -------------------------------------------------------------------------------- /iOS/Swift/Type Erase in Swift.md: -------------------------------------------------------------------------------- 1 | ## Type Erase - 类型擦除 2 | https://robnapier.net/erasure 3 | -------------------------------------------------------------------------------- /iOS/Swift/final 关键字的理解.md: -------------------------------------------------------------------------------- 1 | ### final 的作用 2 | final 关键字的作用:使用它修饰的变量、方法、类不可继承。 3 | 4 | 整个类不可继承,若有类想继承 Animal,则编译器会报错: 5 | ``` 6 | final class Animal { 7 | let name: String 8 | init(name: String) { 9 | self.name = name 10 | } 11 | 12 | func eat() { } 13 | } 14 | ``` 15 | 属性不可继承: 16 | ``` 17 | class Animal { 18 | final let name: String 19 | init(name: String) { 20 | self.name = name 21 | } 22 | 23 | func eat() { } 24 | } 25 | ``` 26 | 方法不可继承: 27 | ``` 28 | class Animal { 29 | let name: String 30 | init(name: String) { 31 | self.name = name 32 | } 33 | 34 | final func eat() { } 35 | } 36 | ``` 37 | 总结:但凡用 final 修饰的都不可继承,它可以修饰 属性、方法、类。 38 | 39 | ### 有何好处 40 | 使用 final 可以提高性能。使用 final 修饰可以避免系统的动态派发(Dynamic Dispatch)。关于 Dynamic Dispatch 的更多优化可以参见[此处](https://developer.apple.com/swift/blog/?id=27)。 -------------------------------------------------------------------------------- /iOS/Swift/关于 Optional 的一些实践.md: -------------------------------------------------------------------------------- 1 | ### 为 Optional 添加 extension 2 | ``` 3 | extension Optional where Wrapped == String { 4 | var safeValue: String { 5 | return self ?? "" 6 | } 7 | } 8 | 9 | var name: String? = "Swift" 10 | print(name ?? "") 11 | 12 | name = nil 13 | print(name!) // 强制解包造成 crash 14 | 15 | print(name.safeValue) // 无需解包,不存在上述情况 16 | ``` 17 | 优点:无需解包,从而减少代码因强制解包而出现的问题。 18 | 19 | ### 为 Optional 添加单元测试 20 | [XCTUnwrap(_:_:file:line:)](https://developer.apple.com/documentation/xctest/3380195-xctunwrap) : 如果表达式为 nil,则测试失败;若不为 nil,测试成功,返回解包的值。 21 | ``` 22 | func testNameValue() throws { 23 | let name: String? = "Swift" 24 | let unwrappedTitle = try XCTUnwrap(name, "Title should be set") 25 | XCTAssertEqual(unwrappedTitle, "Swift") 26 | } 27 | ``` 28 | 29 | ### Optional Protocol 30 | 通过 extension 来实现 optional Protocol。 31 | ``` 32 | protocol Eat { 33 | func eatFish() 34 | func eatApple() //Optional 35 | } 36 | 37 | extension Eat { 38 | func eatApple() { } 39 | } 40 | 41 | struct Cat: Eat { 42 | func eatFish() { 43 | print("Dog eat fish") 44 | } 45 | } 46 | ``` 47 | 48 | ### 避免嵌入式 Optional 49 | optional 可以重复使用,比如下面的代码: 50 | ``` 51 | var name: String?? = "Swift" // 使用这种方式估计会被人打死🤣 52 | print(name!!) 53 | ``` 54 | 下面给出一种更加可能的使用方式: 55 | ``` 56 | let nameAndVersion: [String:Int?] = ["Swift": 5] 57 | let swiftVersion = nameAndVersion["Swift"] 58 | print(swiftVersion) // Optional(Optional(5)) 59 | print(swiftVersion!) // Optional(5) 60 | print(swiftVersion!!) // 5 61 | ``` 62 | 我们应该尽量避免上面的类似的使用。 -------------------------------------------------------------------------------- /iOS/Swift/关于 Swift 访问控制的总结.md: -------------------------------------------------------------------------------- 1 | ### 为什么需要访问控制(Access Control) 2 | 访问控制可以限制别的源文件或者模块来访问你的代码。该特性可以让你隐藏代码的具体实现,从而使代码有更好的`封装性`。 3 | 4 | ### 5 个关键字 5 | 对于访问控制,Swift 提供了五个关键字。根据可访问的优先级,从高到低依次为:open、public、internal、fileprivate、private。 6 | 7 | 下面来总结一下这几个关键字的区别: 8 | * open:本模块和其他模块都能访问,只能应用在类或者类成员上。允许其他模块继承或重写。 9 | * public:本模块和其他模块都能访问,不允许其他模块继承或重写。 10 | * internal:本模块能访问。不写访问控制关键字,默认为 internal。 11 | * fileprivate:当前源文件访问。 12 | * private:只允许在当前定义体内使用。 13 | 14 | 关于 public 还有一点值得注意:当使用 public 去修饰一个类型的时候,该类型是 public,但其成员、方法默认是 internal 的。如果你想要使其可以让其他模块调用,必须要显式的用 public 修饰。这样做的目的是防止该类型中 internal 的代码被当做 public 公开。 15 | 16 | 17 | ### 指导准则 18 | 来自官方文档的指导原则:`No entity can be defined in terms of another entity that has a lower (more restrictive) access level.` 19 | 20 | 翻译:一个实体不可以被更低访问级别的实体多定义。 21 | 22 | 代码举例: 23 | ``` 24 | fileprivate class Student { } 25 | // Error: Constant cannot be declared public because its type 'Student' uses a fileprivate type 26 | public let stu = Student() 27 | ``` 28 | 如上述代码所示,stu 用 `public` 修饰,而 Student 使用 `fileprivate`。这样就导致了 stu 的访问权限比 Student 的`高`,从而造成编译器错误。将 Student 改为 public 或 open 即可消除编译器错误。 29 | 30 | 代码示例: 31 | ``` 32 | public class SomePublicClass { // explicitly public class 33 | public var somePublicProperty = 0 // explicitly public class member 34 | var someInternalProperty = 0 // implicitly internal class member 35 | fileprivate func someFilePrivateMethod() {} // explicitly file-private class member 36 | private func somePrivateMethod() {} // explicitly private class member 37 | } 38 | ``` 39 | 虽然 SomePublicClass 为 public,但 someInternalProperty 是 internal ,即别的模块无法调用,只能访问显式 public 的 somePublicProperty。 40 | 41 | ### 元组(Tuple) 42 | 元组类型的访问控制 ≤ 元组类型中最小的。 43 | 44 | ``` 45 | struct Dog { } 46 | fileprivate struct Cat { } 47 | 48 | fileprivate var ani: (Dog, Cat) 49 | ``` 50 | 因为 fileprivate 小于 internal,所以 ani 只能使用 `fileprivate` 或者 `private` 修饰,否则会有编译器错误。 51 | 52 | ### 泛型 53 | 泛型类型的访问控制须 ≤ `类型访问级别` 以及 `所有泛型类型参数的访问级别`的最小值。 54 | ``` 55 | struct Dog { } 56 | fileprivate struct Cat { } 57 | public struct Person { } 58 | 59 | fileprivate var p = Person() 60 | ``` 61 | 62 | 上述代码,虽然 Person 使用 public 修饰,但 Cat 使用的 fileprivate, fileprivate 小于 public 和 internal。所以 p 的访问权限修饰符只能使用 `fileprivate` 或者 `private` 修饰,否则会有编译器错误。 63 | 64 | ### 成员与嵌套类型 65 | 类型的访问控制会影响到成员(属性、方法、构造器、下标)、嵌套类型的访问控制。 66 | * 一般情况下,类型为 fileprivate 或 private,那么成员和嵌套类型也默认为 fileprivate 或 private。 67 | * 一般情况下,类型为 internal 或 public,那么成员和嵌套类型默认为 public。 68 | 69 | ``` 70 | public class SomePublicClass { // explicitly public class 71 | public var somePublicProperty = 0 // explicitly public class member 72 | var someInternalProperty = 0 // implicitly internal class member 73 | fileprivate func someFilePrivateMethod() {} // explicitly file-private class member 74 | private func somePrivateMethod() {} // explicitly private class member 75 | } 76 | 77 | class SomeInternalClass { // implicitly internal class 78 | var someInternalProperty = 0 // implicitly internal class member 79 | fileprivate func someFilePrivateMethod() {} // explicitly file-private class member 80 | private func somePrivateMethod() {} // explicitly private class member 81 | } 82 | 83 | fileprivate class SomeFilePrivateClass { // explicitly file-private class 84 | func someFilePrivateMethod() {} // implicitly file-private class member 85 | private func somePrivateMethod() {} // explicitly private class member 86 | } 87 | 88 | private class SomePrivateClass { // explicitly private class 89 | func somePrivateMethod() {} // implicitly private class member 90 | } 91 | ``` 92 | 93 | ### 成员的重写 94 | * 子类重写成员的访问控制须 ≥ 子类,或者 ≥ 父类被重写成员。 95 | * 父类的成员不能被成员作用域外定义的子类重写。 96 | 97 | ``` 98 | public class Person { 99 | private var age = 0 100 | } 101 | // Error: Property does not override any property from its superclass 102 | class Student: Person { 103 | override var age: Int { 104 | set {} 105 | get { 10 } 106 | } 107 | } 108 | ``` 109 | 因为子类中的 age 访问控制为 internal,而父类中的 age 访问控制为 private, internal ≥ private (子类的重写成员访问控制≤ 父类被重写成员,不符合👆的第一条),因此造成编译器错误。将 private 删除即可消除错误。 110 | 111 | 若不想删除 private 也可做以下修改: 112 | ``` 113 | public class Person { 114 | private var age = 0 115 | 116 | class Student: Person { 117 | override var age: Int { 118 | set {} 119 | get { 10 } 120 | } 121 | } 122 | } 123 | ``` 124 | 因为 age 的作用域就是 Person 的整个大括号,所以这样符合👆的第二条。 125 | 126 | ### Getter、Setter 127 | get/set 的访问控制默认与所属环境一致,即该类型为 private ,则get/set 也为 private。 128 | 129 | 在日常开发中,我们经常会碰到这样一个问题:允许别人读取该属性的值,但`不允许修改`。如何实现这个呢?答案就是使用 `private(set)`。 130 | 131 | ``` 132 | public class Person { 133 | private(set) var age = 0 134 | } 135 | ``` 136 | age 外部可读但不可写。 137 | 138 | ### 构造器 139 | * 如果别的模块想要调用 public 修饰的类的默认构造器,则需用 public 显式的修饰默认构造器。因为默认构造器是 internal。 140 | * 如果结构体中有 fileprivate、private的存储属性,那么成员构造器也需预期保持一致。 141 | 142 | ### Enum 143 | * 所有 case 自动与 enum 的访问控制保持一致。 144 | * case 不能单独设置访问控制。 145 | 146 | ``` 147 | public enum CompassPoint { 148 | case north // public 149 | case south // public 150 | case east // public 151 | case west // public 152 | } 153 | ``` 154 | CompassPoint 的所有 case 都为 public,不能给他们设置别的访问控制。 155 | 156 | ### Protocol 157 | 协议中定义的内容自动与类型的访问控制保持一致,不可单独设置访问控制。 158 | 159 | 大家可以看一下下面的代码是否能编译通过: 160 | ``` 161 | public protocol PublicProtocol { 162 | func test() // public 163 | } 164 | 165 | public class PublishClass: PublicProtocol { 166 | func test() { } 167 | } 168 | ``` 169 | 170 | 答案是不能,因为 public 修饰的 PublishClass,它的 test() 默认是 internal,而 PublicProtocol 的 test() 是 public,所以报错。正确代码如下: 171 | ``` 172 | public class PublishClass: PublicProtocol { 173 | public func test() { } 174 | } 175 | ``` 176 | ### Extension 177 | * 如果显式的设置了 extension 的访问控制,extension 的成员自动接收 extension 的访问控制。 178 | 179 | ``` 180 | struct Student { } 181 | private extension Student { 182 | func run() { } // private 183 | } 184 | ``` 185 | * 若没有显式设置,则 extension 中的成员与类型中定义的成员访问控制一致。 186 | ``` 187 | extension Student { 188 | func write() { } // internal 189 | } 190 | ``` 191 | * 可以单独给 extension 中的成员设置访问控制。 192 | ``` 193 | extension Student { 194 | private func eat() { } // private 195 | func read() { } // internal 196 | } 197 | ``` 198 | * 不能给用于遵守协议的 extension 显式设置 extension 的访问控制。 199 | ``` 200 | protocol TestProtocol { 201 | func test() 202 | } 203 | 204 | // Error: 'internal' modifier cannot be used with extensions that declare protocol conformances 205 | internal extension Student: TestProtocol { 206 | func test() { } 207 | } 208 | ``` 209 | 210 | * [Apple Document](https://docs.swift.org/swift-book/LanguageGuide/AccessControl.html) -------------------------------------------------------------------------------- /iOS/Swift/初识 Scanner.md: -------------------------------------------------------------------------------- 1 | Scanner 是一种字符串解析器,用于扫描字符集中的子字符串或字符,也可以扫描十进制、十六进制和浮点表示形式的数值。 2 | 3 | Scanner 对象会将字符串中的字符解释转换为数字类型或者字符串类型。当创建 Scanner 对象时,可以赋值一个字符串。它会从给予字符串的头部开始扫描,根据你请求的次数,一直扫描到尾部。 4 | 5 | 跳过字符串中的换行符,提取所有的数字: 6 | ``` 7 | let scanner = Scanner(string: "123\n456\n7890") 8 | 9 | let res = scanner.scanUpToCharacters(from: .newlines) 10 | let res1 = scanner.scanUpToCharacters(from: .newlines) 11 | let res2 = scanner.scanUpToCharacters(from: .newlines) 12 | 13 | print(res, res1, res2) // Optional("123") Optional("456") Optional("7890") 14 | ``` 15 | 16 | 由于类簇的性质,Scanner 对象并不是真正 Scanner 类的实例,而是它的私有子类。虽然 Scanner 对象的类是私有的,但它的接口都是 public。对于 Scanner 的属性和函数都可以调用。 17 | 18 | 使用 `charactersToBeSkipped` ,跳过扫描 "-": 19 | ``` 20 | let scanner = Scanner(string: "123-456-7890") 21 | scanner.charactersToBeSkipped = CharacterSet(charactersIn: "-") 22 | ``` 23 | 24 | 字符集默认跳过换行符和空格。 25 | 26 | 实际应用:Hex Color -> UIColor 27 | ``` 28 | extension UIColor { 29 | public convenience init?(hex: String) { 30 | let r, g, b, a: CGFloat 31 | 32 | if hex.hasPrefix("#") { 33 | let start = hex.index(hex.startIndex, offsetBy: 1) 34 | let hexColor = String(hex[start...]) 35 | 36 | if hexColor.count == 8 { 37 | let scanner = Scanner(string: hexColor) 38 | var hexNumber: UInt64 = 0 39 | 40 | if scanner.scanHexInt64(&hexNumber) { 41 | r = CGFloat((hexNumber & 0xff000000) >> 24) / 255 42 | g = CGFloat((hexNumber & 0x00ff0000) >> 16) / 255 43 | b = CGFloat((hexNumber & 0x0000ff00) >> 8) / 255 44 | a = CGFloat(hexNumber & 0x000000ff) / 255 45 | 46 | self.init(red: r, green: g, blue: b, alpha: a) 47 | return 48 | } 49 | } 50 | } 51 | 52 | return nil 53 | } 54 | } 55 | 56 | let gold = UIColor(hex: "#ffe700ff") 57 | ``` 58 | 59 | * [上面的栗子出处](https://www.hackingwithswift.com/example-code/uicolor/how-to-convert-a-hex-color-to-a-uicolor) -------------------------------------------------------------------------------- /iOS/Swift/如何迭代 Swift 中枚举的所有case.md: -------------------------------------------------------------------------------- 1 | 在 Swift 中,如何能迭代获取 enum 的所有 case 呢?答案就是 CaseIterable。通过遵守 CaseIterable 协议,可以通过 `allCases` 属性来获取所有 case 的集合。 2 | 3 | ### 获取所有的 case 4 | ``` 5 | enum Direction: CaseIterable { 6 | case east 7 | case west 8 | case north 9 | case south 10 | } 11 | // 可以获取所有 case 的数量 12 | let count = Direction.allCases.count 13 | // 也可以遍历所有 case 14 | for direction in Direction.allCases { 15 | print(direction) 16 | } 17 | 18 | east 19 | west 20 | north 21 | south 22 | ``` 23 | 24 | ### 支持集合的所有操作 25 | 26 | 对于该集合,我们不仅可以遍历它,还可以对它进行 filter 、map 的高阶函数的操作。 27 | ``` 28 | enum Direction: Int, CaseIterable { 29 | case east = 0 30 | case west 31 | case north 32 | case south 33 | } 34 | 35 | // 进行 filter 操作 36 | let result = Direction.allCases.filter { $0.rawValue > 1 } 37 | for direction in result { 38 | print(direction) 39 | } 40 | 41 | // 进行 map 和 joined 操作 42 | let caseList = Direction.allCases 43 | .map({ "\($0)" }) 44 | .joined(separator: ", ") 45 | // caseList == "north, south, east, west" 46 | print(caseList) 47 | ``` 48 | 49 | ### 当 enum 有联合值的时候,如何使用 CaseIterable 50 | 当 enum 遵守该协议时,编译器会自动提供 CaseIterable 要求的函数实现,比如上面的代码。但 enum 有联合值的时候,是不会自动提供函数实现的。这时候就需要我们自己去实现 CaseIterable 协议要求实现的内容了。 51 | ``` 52 | enum Barcode: CaseIterable { 53 | case upc(Int, Int, Int) 54 | case qrCode(String) 55 | } 56 | ``` 57 | 上面这个代码是无法编译的,会提示报错:`Type 'Barcode' does not conform to protocol 'CaseIterable'`。 58 | 59 | 修改成下面的代码就可以了: 60 | ``` 61 | enum Barcode: CaseIterable { 62 | typealias AllCases = [Barcode] 63 | static var allCases: [Barcode] = [.upc(0, 0, 0), .qrCode("")] 64 | 65 | case upc(Int, Int, Int) 66 | case qrCode(String) 67 | } 68 | 69 | let count = Barcode.allCases.count 70 | ``` 71 | 实现了 AllCases 和 allCases 之后我们就可以跟之前那样使用了。 72 | -------------------------------------------------------------------------------- /iOS/Swift/快速了解 Swift.md: -------------------------------------------------------------------------------- 1 | 本文主要用于需要快速了解 Swift 的基础知识。包含五大块: 2 | 3 | * 变量常量 4 | * if、for、while 5 | * 函数 6 | * 集合 7 | * struct 与 class 8 | 9 | 推荐大家用 Xcode 的 Playground 来了解、练习 Swift 的基础知识。 10 | 11 | `本文内容基于 Swift 5.3.` 12 | 13 | ### 变量常量 14 | 在 Swift 中,通过 let 来声明常量,var 来声明变量。 15 | 16 | #### let 17 | 用 let 修饰的常量赋值后不可修改。 18 | ``` 19 | let name = "Swift" 20 | // 这句代码会报错:Cannot assign to value: 'name' is a 'let' constant 21 | name = "Objective-C" 22 | ``` 23 | 24 | #### var 25 | 用 var 修饰的变量在赋值后仍可以修改它的值。 26 | ``` 27 | var age = 2012 28 | age = 2011 29 | ``` 30 | Tips: `Swift 是可以自动进行类型推断的,Compiler 会根据你的赋值自动推断其类型。所以在声明常量或变量的时候可以不写其类型。` 31 | 32 | 比如下面的代码是等效的: 33 | ``` 34 | var age = 2012 35 | var age: Int = 2012 36 | ``` 37 | 38 | #### 如何选择使用 39 | * 如果代码中存储的值不需要改变,则使用 let。 40 | * 如果代码中存储的值需要改变,则只用 var。 41 | * 如果代码中存储的值不确定是否需要改变,优先使用 let,后续变更可再改为 var。因为这样能更好的保持代码的健壮性。 42 | 43 | ### if、for、while 44 | 45 | #### if 46 | if 后的判断条件不需要写小括号。 47 | ``` 48 | let age = 18 49 | if age < 18 { 50 | print("Denied") 51 | } else { 52 | print("Allow") 53 | } 54 | ``` 55 | 56 | #### for 57 | 在 Swift 中,弃用了 for(int i = 0; i <= 10, i++) 这种方式,而是采取了更加简洁的 for-in 。 58 | ``` 59 | // 0...10 等价于 [0...10] 60 | for i in 0...10 { 61 | print(i) 62 | } 63 | 64 | // 0...10 等价于 [0...10) 65 | for i in 0..<10 { 66 | print(i) 67 | } 68 | ``` 69 | 70 | #### while 71 | ``` 72 | var count = 10 73 | while count > 0 { 74 | print(count) 75 | count -= 1 76 | } 77 | ``` 78 | 需要特别指出的是:Swift 不支持 `count--` 或 `--count` 这种语法,因为这种代码比较容易出错,且不易于阅读。 79 | 80 | ### 函数 81 | 通过 func 关键字来声明函数。 82 | ``` 83 | // 无参无返回值 84 | func doSomething() { } 85 | // 有参无返回值 86 | func doSomething(parameter: Int) { } 87 | // 多个参无返回值。parameter1 、parameter2 只为示意,真实项目中的代码不要这样命名。 88 | func doSomething(parameter1: Int, parameter2: Int) -> Int { return 0 } 89 | // 有参有返回值 90 | func doSomething(parameter: Int) -> Int { return 0 } 91 | ``` 92 | 93 | * 函数中的参数默认是 let 修饰,即不可修改的。如果想修改需添加 inout 关键字。 94 | ``` 95 | func doSomething(parameter: inout Int) { } 96 | ``` 97 | * 可以在参数前面添加 _,从而在调用是忽略参数名。 98 | ``` 99 | func doSomething(parameter: Int) { } 100 | doSomething(parameter: 10) 101 | 102 | func doSomething1(_ parameter: Int) { } 103 | doSomething1(10) 104 | ``` 105 | * 参数设置默认值。 106 | ``` 107 | func doSomething(parameter: Int = 1024) { } 108 | // parameter 默认值为 1024 109 | doSomething() 110 | ``` 111 | ### 集合 112 | 113 | #### Array 114 | Array:用来存储一组同类型、有序的数据。 115 | ``` 116 | // 声明 117 | var nums = [1, 2, 3, 4] 118 | // 尾部添加 119 | nums.append(5) // 1 2 3 4 5 120 | // 移除第一个元素 121 | nums.remove(at: 0) // 2 3 4 5 122 | // 移除尾部元素 123 | nums.removeLast() // 2 3 4 124 | // 移除首部元素 125 | nums.removeFirst() // 3 4 126 | // 在 index 为 2 的位置插入 10 127 | nums.insert(10, at: 2) // 3 4 10 128 | // 删除全部元素 129 | nums.removeAll() // [] 130 | ``` 131 | 132 | * 不要对空数组调用 removeLast/removeFirst 函数。 133 | * remove/insert 函数的索引应是有效的。 134 | 135 | #### Dictionary 136 | Dictionary:用来存储键值对,它的 key 是唯一的,且数据是无序的。 137 | ``` 138 | // 声明 139 | var goods = ["name": "appale", "price": "20"] 140 | // 添加键值对 141 | goods["weight"] = "10.0" // ["name": "appale", "weight": "10.0", "price": "20"] 142 | // 修改 143 | goods["name"] = "orange" // ["price": "20", "name": "orange", "weight": "10.0"] 144 | // 获取所有的 key 145 | goods.keys //["name", "price", "weight"] 146 | // 移除某个键值对 147 | goods["name"] = nil // ["price": "20", "weight": "10.0"] 148 | // 移除所有元素 149 | goods.removeAll() 150 | ``` 151 | 152 | #### Set 153 | Set:用来存储一组同类型、无序、值唯一的数据。 154 | 155 | ``` 156 | // 声明 157 | var set = Set() 158 | // 添加元素 159 | set.insert(10) // [10] 160 | set.insert(20) // [20, 10] 161 | // 因为 set 值唯一,所以还是 [20, 10] 162 | set.insert(10) // [20, 10] 163 | // 移除头部元素 164 | set.removeFirst() // [10] 或者 [20] 165 | // 移除所有元素 166 | set.removeAll() 167 | ``` 168 | 169 | * removeFirst:因为 Set 是无序的,所以它移除的并不一定是最先添加进去的元素。并且不要对空的 set 调用该函数。 170 | 171 | ### struct 与 class 172 | #### struct 173 | 用 struct 关键字修饰,可以声明属性和函数。 174 | ``` 175 | // 定义 176 | struct Person { 177 | let name: String 178 | let age: Int 179 | 180 | func run() { } 181 | } 182 | // 使用 183 | let person = Person(name: "jack", age: 10) 184 | person.run() 185 | ``` 186 | 187 | * struct 在 Swift 中是值类型,为内容拷贝。具体参见下方代码: 188 | ``` 189 | let jack = Person(name: "jack", age: 10) 190 | var rose = jack 191 | // 虽然 rose 的内容是从 jack 来的,但修改 rose 的 name 属性并不会影响 jack 的name 属性。 192 | rose.name = "rose" 193 | 194 | print(rose.name, jack.name) // rose jack 195 | ``` 196 | * struct 默认会提供构造函数。 197 | 198 | #### class 199 | 用 class 关键字修饰,可以声明属性和函数。 200 | ``` 201 | // 声明 202 | class Person { 203 | var name: String 204 | let age: Int 205 | 206 | func run() { } 207 | init(name: String, age: Int) { 208 | self.name = name 209 | self.age = age 210 | } 211 | } 212 | 213 | // 使用 214 | let jack = Person(name: "jack", age: 10) 215 | var rose = jack 216 | rose.name = "rose" 217 | print(rose.name, jack.name) // rose rose 218 | ``` 219 | * class 在 Swift 是引用类型,为指针拷贝。所以👆的代码改变了 rose 的 name 值,jack 的 name 值也跟着改变。 220 | * class 不会提供构造函数,需要自己创建。 221 | 222 | ### 总结 223 | * 介绍了 var/let 、if/for/while、函数、集合、struct/class。 224 | * var 声明可变的数据;let 声明不可变的数据。 225 | * Array 存储一组同类型、有序的数据;Ditionary 存储键唯一且无序的键值对;Set 存储一组同类型、无序且值唯一的数据。 226 | * struct 为值类型,系统默认提供构造函数;class 为引用类型,系统不提供构造函数,需自己创建。 227 | 228 | ### 参考 229 | * [Apple Document](https://docs.swift.org/swift-book/LanguageGuide/TheBasics.html) -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/Algorithm.md: -------------------------------------------------------------------------------- 1 | ## Algorithm.swift 2 | ## min 3 | 4 | ### min - 两个参数 5 | 源码: 6 | ``` 7 | public func min(_ x: T, _ y: T) -> T { 8 | return y < x ? y : x 9 | } 10 | ``` 11 | 通过源码可以得知,min函数的底层就是使用三目运算符计算得出结果。`这里需要注意的一点,如果 x 等于 y,它返回的是 x。这样做的原因是保持数据的稳定性。` 12 | 13 | 何为数据稳定性?比如我们有一个需要排序的数组:[5, 3, 2, 4, 3],如果我们通过排序,第二个位置的 3 经过排序后在第五个位置的 3 前面,则是稳定的,反之则为不稳定的。 14 | 15 | 通过下面的代码可以看出如果相等,确实返回的第一个参数: 16 | ``` 17 | struct Student: Comparable { 18 | var age = 0 19 | var name = "" 20 | 21 | static func < (lhs: Student, rhs: Student) -> Bool { 22 | return lhs.age < rhs.age 23 | } 24 | } 25 | 26 | let s1 = Student(age: 18, name: "jack") 27 | let s2 = Student(age: 18, name: "rose") 28 | 29 | let res = min(s1, s2) 30 | print(res.name) // jack 31 | ``` 32 | ### min - 多个参数 33 | 源码: 34 | ``` 35 | public func min(_ x: T, _ y: T, _ z: T, _ rest: T...) -> T { 36 | var minValue = min(min(x, y), z) 37 | // 如果 value 等于 minValue,会选择 minValue 当最小值,原因同上。 38 | for value in rest where value < minValue { 39 | minValue = value 40 | } 41 | return minValue 42 | } 43 | ``` 44 | 45 | 通过代码可以看出,可以传递多个参数的 min 函数是先求出前三个参数的最小值 - minValue,再去 rest 集合中寻找是否有比 minValue 更小的值。 46 | 47 | ### max - 两个参数 48 | 源码: 49 | ``` 50 | public func max(_ x: T, _ y: T) -> T { 51 | return y >= x ? y : x 52 | } 53 | ``` 54 | 同样通过三目运算符得出结果,如果两个值相等,则选择 y 为结果,原因同上。 55 | 56 | ### max - 多个参数 57 | 源码: 58 | ``` 59 | public func max(_ x: T, _ y: T, _ z: T, _ rest: T...) -> T { 60 | var maxValue = max(max(x, y), z) 61 | for value in rest where value >= maxValue { 62 | maxValue = value 63 | } 64 | return maxValue 65 | } 66 | ``` 67 | 原理同接受多个参数的 min 函数。 68 | 69 | ### EnumeratedSequence 70 | 当数组调用 `enumerated()` 函数时,会返回一个 `EnumeratedSequence` 类型的实例。 71 | 72 | 源码: 73 | ``` 74 | // 1、构建一个支持泛型的结构体 75 | public struct EnumeratedSequence { 76 | @usableFromInline 77 | internal var _base: Base 78 | 79 | @inlinable 80 | internal init(_base: Base) { 81 | self._base = _base 82 | } 83 | } 84 | 85 | // 2、提供一个访问 index 的迭代器,从而可以循环访问每个元素 86 | extension EnumeratedSequence { 87 | @frozen 88 | public struct Iterator { 89 | @usableFromInline 90 | internal var _base: Base.Iterator 91 | @usableFromInline 92 | internal var _count: Int 93 | 94 | /// Construct from a `Base` iterator. 95 | @inlinable 96 | internal init(_base: Base.Iterator) { 97 | self._base = _base 98 | self._count = 0 99 | } 100 | } 101 | } 102 | 103 | // 3、迭代器遵守 IteratorProtocol 和 Sequence,可以通过 next() 访问下一个元素 104 | extension EnumeratedSequence.Iterator: IteratorProtocol, Sequence { 105 | public typealias Element = (offset: Int, element: Base.Element) 106 | @inlinable 107 | public mutating func next() -> Element? { 108 | guard let b = _base.next() else { return nil } 109 | let result = (offset: _count, element: b) 110 | _count += 1 111 | return result 112 | } 113 | } 114 | 115 | // 4、遵守 Sequence 返回迭代器 116 | extension EnumeratedSequence: Sequence { 117 | @inlinable 118 | public __consuming func makeIterator() -> Iterator { 119 | return Iterator(_base: _base.makeIterator()) 120 | } 121 | } 122 | ``` 123 | 124 | 示例代码: 125 | ``` 126 | var s = ["foo", "bar"].enumerated() 127 | for (n, x) in s { 128 | print("\(n): \(x)") 129 | } 130 | //0: foo 131 | //1: bar 132 | ``` 133 | 这里有一点疑问,第 4 步的代码为何不放在第 3 步?我的理解是每个 extension 只做一件事,保持单一职能,如果理解错误还烦请知道的不吝赐教。 134 | -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/Array.md: -------------------------------------------------------------------------------- 1 | ## 读 Swift 源码系列 - Array 2 | 3 | ### 数组内存增长策略 4 | 当我们向一个数组尾部拼接元素且数组的内存空间不够使用时,数组就会动态的增加内存空间,那么 Swift 的数组内存增长策略是什么?通过查阅 Swift 代码库的 Array.swift 文件的 155 行:`The new storage is a multiple of the old storage's size.`可以得知,它的策略就是将旧的内存空间乘以 2 。下面通过代码来验证一下: 5 | ``` 6 | var nums = [1, 2, 3, 4, 5] 7 | // 初始容量:5,初始元素个数:5 8 | print("初始容量:\(nums.capacity),初始元素个数:\(nums.count)") 9 | nums.append(6) 10 | // 添加元素后容量:10,元素个数:6 11 | print("添加元素后容量:\(nums.capacity),元素个数:\(nums.count)") 12 | 13 | ``` 14 | 可以看到:`nums` 的初始容量为 5,元素个数为 5。通过在尾部添加一个元素后,`nums` 的新容量变成了 10,元素个数则为 6。 15 | 16 | 可能我们会觉得每次容量乘以 2 有点空间浪费,想自己制定内存增长策略。那么该如何实现呢?答案就是使用 `reserveCapacity(_ minimumCapacity:)` 函数,通过 minimumCapacity 参数来控制每次内存增长。但是这里有一点我们需要注意一下,`For performance reasons, the size of the newly allocated storage might be greater than the requested capacity. Use the array's 'capacity' property to determine the size of the new storage.`(Array.swift 的第962行) 因为性能原因,每次增长后的新内存可能只比数组的最小容量大,不能保证每次的新内存大小都是你指定的大小。这句话可能理解起来有点抽象,我们通过代码来说明一下: 17 | ``` 18 | var arr = Array(1...4) 19 | var factor = 0 20 | for index in 1...20 { 21 | print("第\(index)次执行,数组的内存大小:\(arr.capacity)") 22 | factor += 10 23 | arr.reserveCapacity(factor) 24 | } 25 | ``` 26 | 27 | 执行结果: 28 | ![执行结果](https://github.com/fengzhihao123/FZHBlog/blob/master/images/Array-capacity.png) 29 | 30 | 通过上述的打印我们可以看到在第1-13次的内存容量都增加了我们指定的 10,但是 14-19次的内存容量并没有变,而最后一次则是增加了 64 次。由此得知,我们指定的内存策略系统只会大概的去按照执行,并不会精确的去执行,所以我们应该使用`capacity` 来获得数组当前确切的容量。 31 | 32 | 具体应用:在我们知道大概的元素增量时,可以先扩展数组的内存容量,再去添加元素。 33 | ``` 34 | // 一般写法 35 | var names = ["小明", "小红", "小强"] 36 | for i in 1...10 { 37 | names.append("name\(i)") 38 | } 39 | // count = 13; capacity = 24 40 | print(names.count, names.capacity) 41 | // 优化后写法 42 | names = ["小明", "小红", "小强"] 43 | // 后续我们大概需要添加10个名字,可以先把容量扩找到 15,再去添加元素 44 | names.reserveCapacity(15) 45 | for i in 1...10 { 46 | names.append("name\(i)") 47 | } 48 | // count = 13; capacity = 15 49 | print(names.count, names.capacity) 50 | 51 | ``` 52 | `需要注意的是不要在 for-loop 里面调用 reserveCapacity() 函数。` 53 | 54 | ### ArraySlice 55 | 在我们使用数组切片时,需要注意的是切片的索引是和原数组的索引是一致的,需要注意索引越界的问题,比如下面的代码: 56 | ``` 57 | var nums = [1, 2, 3, 4, 5] 58 | let slice = nums[1...2] 59 | // 切片的起始索引:1, 结束索引:3 60 | print("切片的起始索引:\(slice.startIndex), 结束索引:\(slice.endIndex)") 61 | ``` 62 | 可以看到 `slice` 的起始索引是1,结束索引是 3,包含的元素为`[2, 3]`。索引跟原数组是一致的,所以,我们不能通过`slice[0]` 来去访问它的第一个元素,这会造成越界问题。 63 | 64 | 使用切片还有一个需要注意的问题就是,我们不应该长时间的持有切片。因为即使在原始数组的生命周期结束后,切片也回持有对较大数组的整个内存的引用,而不仅仅是对它所表示的部分的引用。因此,切片的长期存储可能会导致元素的生命周期延长,而这些元素在其他情况下是不可访问的,这就可能会出现内存和对象泄漏。 65 | 66 | “Long-term storage of `ArraySlice` instances is discouraged...can appear to be memory and object leakage.”[出自 ArraySlice.swift 第65-70行]。 67 | 68 | ### copy on write 69 | 当将一个数组的数据赋值给一个新创建的变量时,它并不会立即拷贝。而是当新创建的变量修改的时候才会发生拷贝。如下: 70 | ``` 71 | var nums = [1, 2, 3, 4, 5] 72 | // 没有开辟新的内存空间,将nums的数据拷贝过去。此时 newNums 指向的内存地址与 nums 是相同的 73 | var newNums = nums 74 | // 开辟了新的内存空间,此时 newNums 指向的内存地址与 nums 是不同的 75 | newNums[0] = 10 76 | ``` 77 | 78 | 79 | ### NSArray 与 Array 的桥接 80 | 如果数组中的元素是类的实例,或者是用 @obj 协议声明的实例,Array 转换为 NSArray 的时间复杂度和空间复杂度都为O(1),若不是则复杂度都为O(*n*)。 81 | 82 | #### Array -> NSArray 83 | 通过 as 将 Array 转换为 NSArray: 84 | ``` 85 | class Person { 86 | var name = "123" 87 | } 88 | let p1 = Person() 89 | 90 | let objs = Array(repeating: p1, count: 10) 91 | let nsObjs = objs as NSArray 92 | ``` 93 | 94 | #### NSArray -> Array 95 | 96 | 1、当数组的元素是类或者有 @objc 标识时: 97 | 98 | 调用copyWithZone: 函数来拷贝数据,因为 NSArray 是不可变的,所以返回的拷贝数组的地址和原数组地址一致。NSArray 和 Array 的实例都拥有 copy-on-write 的特性,且它俩共享一块内存。 99 | 100 | 2、当数组的元素是基本类型时: 101 | 102 | 将数据拷贝到一块连续的内存中,时间复杂度为O(*n*) 。比如:`NSArray` 转换为 `Array`,就会产生上述拷贝。在访问数组中的元素时就不需要桥接了。 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/ArrayBody.md: -------------------------------------------------------------------------------- 1 | ## ArrayBody 2 | 3 | Array 始于 Body,终于集合中连续的元素。该结构体就是用来表现 Body 的部分。 4 | 5 | ### 两个构造方法 6 | * `init(count: Int, capacity: Int, elementTypeIsBridgedVerbatim: Bool = false)` 7 | * `init()` 8 | ### 包含属性 9 | * count:数组中元素的个数 10 | * capacity:数组的容量 11 | * elementTypeIsBridgedVerbatim:数组元素是否是桥接的OC类,默认是false 12 | * _capacityAndFlags:与`elementTypeIsBridgedVerbatim`一起用来压缩容量进行优化 13 | -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/ArrayBuffer.md: -------------------------------------------------------------------------------- 1 | ## 读 Swift 源码系列 - ArrayBuffer 2 | 3 | ArrayBuffer: 它是一个用来给 Array 实现存储和对象管理的类。底层是由 struct 实现的。 4 | 5 | ### _asCocoaArray() 6 | 作用:将 Array 转化为 NSArray 7 | 8 | 时间复杂度:如果元素类型是类的实例或者遵守了 `@objc` 协议,为O(1)。反之,则为O(*n*)。 9 | ``` 10 | @inlinable 11 | internal func _asCocoaArray() -> AnyObject { 12 | return _fastPath(_isNative) ? _native._asCocoaArray() : _nonNative.buffer 13 | } 14 | ``` 15 | 16 | 17 | * UnsafeMutableRawPointer 18 | * _fastPath 19 | * SwiftShims 20 | * unsafeBitCast 21 | * withUnsafeBufferPointer/withUnsafeMutableBufferPointer 22 | * _isClassOrObjCExistential 23 | * nativeOwner: -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/BetterCodable 源码阅读.md: -------------------------------------------------------------------------------- 1 | ## BetterCodable 源码阅读 2 | * [项目地址](https://github.com/marksands/BetterCodable) 3 | 4 | 该项目通过 Property Wrappers 来简化 Codable 的使用,使其更好的应对值为 nil,或者类型对应不正确而导致解码失败的情况。 5 | 6 | 本项目支持以下三种类型的 Codable: 7 | * 基本类型。 8 | * 集合类型:Array、Dictionary。 9 | * 日期类型。 10 | 11 | ### 基本类型 12 | 基础类型包含这几个文件: 13 | * DefaultCodable:为缺失值提供自定义实现。 14 | * DefaultFalse:若 Bool 类型的字段解析失败,默认值为 false。 15 | * DefaultTrue:若 Bool 类型的字段解析失败,默认值为 true。 16 | * LosslessValue:将值解析为你声明的类型,比如声明的字段 name 为 String,而服务端返回的值为 Int,解析时自动转为 String。 17 | * LossyOptional:若解析失败,默认值为 nil。 18 | -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/Comparable.md: -------------------------------------------------------------------------------- 1 | ## Comparable 2 | Comparable 是 Swift 标准库中的 Protocol,继承自 Equatable。它的作用就是支持遵守协议的类型可以使用 `<`、 `<=`、 `>=`、 `>`操作符。 3 | 4 | 注:标准库中很多类型已遵守该协议,如Int、String等。 5 | 6 | ### 协议中的方法 7 | ``` 8 | public protocol Comparable: Equatable { 9 | static func < (lhs: Self, rhs: Self) -> Bool 10 | static func <= (lhs: Self, rhs: Self) -> Bool 11 | static func >= (lhs: Self, rhs: Self) -> Bool 12 | static func > (lhs: Self, rhs: Self) -> Bool 13 | } 14 | 15 | extension Comparable { 16 | @inlinable 17 | public static func > (lhs: Self, rhs: Self) -> Bool { 18 | return rhs < lhs 19 | } 20 | @inlinable 21 | public static func <= (lhs: Self, rhs: Self) -> Bool { 22 | return !(rhs < lhs) 23 | } 24 | @inlinable 25 | public static func >= (lhs: Self, rhs: Self) -> Bool { 26 | return !(lhs < rhs) 27 | } 28 | } 29 | ``` 30 | 31 | 虽然 Comparable 中声明了很多方法,但要求必须实现的只有一个:`static func < (lhs: Self, rhs: Self) -> Bool`。 32 | 33 | ### 如何使用 34 | 下面来看一下如何让自定义的类型支持上述操作符。 35 | ``` 36 | struct Student: Comparable { 37 | var age = 0 38 | 39 | static func < (lhs: Student, rhs: Student) -> Bool { 40 | return lhs.age < rhs.age 41 | } 42 | } 43 | 44 | let stu1 = Student(age: 10) 45 | let stu2 = Student(age: 20) 46 | print(stu1 > stu2) // false 47 | ``` 48 | -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/Delegated 源码阅读.md: -------------------------------------------------------------------------------- 1 | ### Delegated - 自动 weak self 2 | * [Delegated 项目地址](https://github.com/dreymonde/Delegated) 3 | 4 | Closure,在日常代码中会经常使用到。使用它时,为了防止可恶的内存泄漏,要时常记得写 `[weak self]` 这样的代码。但对于程序员来说写这种模板代码是很枯燥的,所以, Delegated 就应运而生了。通过它注册过后的 Closure,可以自动的 weak self。下面就来看一下它的基本使用吧。 5 | 6 | #### 基本使用 7 | 8 | 它的使用很简单,通过下面两个步骤就可以实现自动的 weak self: 9 | * 使用 @Delegated 声明 Closure。 10 | * 使用 delegate() 函数来调用 Closure。 11 | 12 | 来看一个具体的例子: 13 | ``` 14 | final class TextField { 15 | // 第一步:使用 @Delegated 声明 Closure。 16 | @Delegated var didUpdate: (String) -> () 17 | } 18 | 19 | // 第二步:使用 delegate() 函数来调用 Closure。 20 | textField.$didUpdate.delegate(to: self) { (self, text) in 21 | // `self` is weak automatically! 22 | self.label.text = text 23 | } 24 | ``` 25 | 26 | 通过上面的两步就实现了自动 weak self,没有循环引用,没有内存泄漏,没有 `[weak self]`!🎉 27 | 28 | #### 实现原理 29 | 掌握了它的使用方法之后,下面来看一下它的实现原理。它的代码很简单,只有一个 Delegated.swift 文件,代码只有 410 行。 30 | 31 | 所有类的说明: 32 | * Delegated0:对应无返回值,无参数的 Closure。 33 | * Delegated1:对应无返回值,一个参数的 Closure。 34 | * Delegated2:对应无返回值,两个参数的 Closure。 35 | * Delegated3:对应无返回值,三个参数的 Closure。 36 | * ReturningDelegated0:对应有返回值,无参数的 Closure。 37 | * ReturningDelegated1:对应有返回值,一个参数的 Closure。 38 | * ReturningDelegated2:对应有返回值,两个参数的 Closure。 39 | * ReturningDelegated3:对应有返回值,三个参数的 Closure。 40 | 41 | 虽然代码包含上面 8 个类,但实质上只要理解了其中的任意一个类即可,因为其他的类只是参数和有无返回值的不同而已。 42 | 43 | 在这里,拿 Delegated1 来举例说明一下它的实现原理。 44 | ``` 45 | @propertyWrapper 46 | public final class Delegated1 { 47 | public init() { self.callback = { _ in } } 48 | private var callback: (Input) -> Void 49 | public var wrappedValue: (Input) -> Void { return callback } 50 | 51 | public var projectedValue: Delegated1 { 52 | return self 53 | } 54 | } 55 | 56 | public extension Delegated1 { 57 | func delegate( 58 | to target: Target, 59 | with callback: @escaping (Target, Input) -> Void 60 | ) { 61 | self.callback = { [weak target] input in 62 | guard let target = target else { 63 | return 64 | } 65 | return callback(target, input) 66 | } 67 | } 68 | 69 | func manuallyDelegate(with callback: @escaping (Input) -> Void) { 70 | self.callback = callback 71 | } 72 | 73 | func removeDelegate() { 74 | self.callback = { _ in } 75 | } 76 | } 77 | ``` 78 | 79 | 首先,来分析一下 Delegated1 类。可以看到它使用了 @propertyWrapper 关键字来修饰,@propertyWrapper 的作用简单来说就是用来封装平常的模板代码。[关于@propertyWrapper更详细的介绍可以看这里](https://github.com/fengzhihao123/FZHBlog/blob/master/iOS/Swift/通过%20Property%20Wrappers%20简化代码.md)。 80 | 81 | 接着,它通过定义 Input 来实现参数支持泛型,然后声明了 callback 属性来存储 Closure。对于 wrappedValue 和 projectedValue 则是重写的 @propertyWrapper 的内置参数。 82 | 83 | 接着,来看一下 extension 中的 delegate 函数。delegate 函数接受两个参数: 84 | * target:需要 weak 的对象。 85 | * callback:实际用到的 closure。 86 | 87 | 可以看到,在 delegate 函数体内,就是自动 weak self 的关键部分。对 callback 进行了重新赋值: 88 | ``` 89 | self.callback = { [weak target] input in 90 | // 通过这里实现自动 weak self 91 | guard let target = target else { 92 | return 93 | } 94 | return callback(target, input) 95 | } 96 | ``` 97 | extension 中还有两个函数,它们的代码也很好理解: 98 | * manuallyDelegate:手动管理,即不使用自动 weak self。通过代码也可以看出它是直接给 callback 赋值,没有进行上面的 weak self 操作。 99 | * removeDelegate:移除代理。 100 | 101 | 至此,源码就分析完了。可以看到这个库的代码还是非常短小精悍的。 102 | 103 | #### 总结 104 | * 通过 @propertyWrapper 来进行模板代码封装。 105 | * 通过 callback 的重新赋值来实现自动 weak self。 106 | -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/NSCache 从入门到源码.md: -------------------------------------------------------------------------------- 1 | # 读 Swift 源码系列 - NSCache 从入门到源码 2 | 3 | ## 如何使用 4 | 通常用它来缓存常用数据,提升性能。 5 | 它和 Dictionary 很像,但有以下几个不同点: 6 | * 自动移除缓存,保证系统的可用内存空间 7 | * 线程安全 8 | * 它不会拷贝 key 9 | 10 | 可配置的属性: 11 | * name 12 | * countLimit 13 | * totalCostLimit 14 | * evictsObjectsWithDiscardedContent 15 | 16 | 可用 API: 17 | * object(forKey:) 18 | * setObject(_:forKey:) 19 | * setObject(_:forKey:cost:) 20 | * removeObject(forKey:) 21 | * removeAllObjects() 22 | 23 | 24 | * FIFO 25 | * LFU 26 | * LRU 27 | * MRU 28 | 29 | ### NSCache 使用的为 LFU 算法 30 | ``` 31 | // 通过下面的代码可以得出,它是通过使用次数来淘汰的 32 | let cache = NSCache() 33 | cache.countLimit = 3 34 | cache.setObject("10", forKey: "key1") 35 | cache.object(forKey: "key1") 36 | cache.object(forKey: "key1") 37 | cache.object(forKey: "key1") 38 | 39 | cache.setObject("20", forKey: "key2") 40 | cache.object(forKey: "key2") 41 | 42 | cache.setObject("30", forKey: "key3") 43 | cache.object(forKey: "key3") 44 | 45 | cache.setObject("40", forKey: "key4") 46 | cache.setObject("50", forKey: "key5") 47 | 48 | print(cache.object(forKey: "key1")) 49 | // nil 50 | print(cache.object(forKey: "key2")) 51 | // nil 52 | print(cache.object(forKey: "key3")) 53 | ``` 54 | 55 | 先进先出算法(FIFO):FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,他与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。 56 | 57 | 最不经常使用算法(Least Frequently Used - LFU):这个缓存算法使用一个计数器来记录条目被访问的频率。通过使用LFU缓存算法,最低访问数的条目首先被移除。缺点:无法对一个拥有最初高访问率之后长时间没有被访问的条目缓存负责。 58 | 59 | 最近最少使用算法(Least Recently used - LRU):这个缓存算法将最近使用的条目存放到靠近缓存顶部的位置。当一个新条目被访问时,LRU将它放置到缓存的顶部。当缓存达到极限时,较早之前访问的条目将从缓存底部开始被移除。这里会使用到昂贵的算法,而且它需要记录“年龄位”来精确显示条目是何时被访问的。此外,当一个LRU缓存算法删除某个条目后,“年龄位”将随其他条目发生改变。 60 | 61 | 自适应缓存替换算法(ARC):在IBM Almaden研究中心开发,这个缓存算法同时跟踪记录LFU和LRU,以及驱逐缓存条目,来获得可用缓存的最佳使用。 62 | 最近最常使用算法(MRU):这个缓存算法最先移除最近最常使用的条目。一个MRU算法擅长处理一个条目越久,越容易被访问的情况。 63 | -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/Print.md: -------------------------------------------------------------------------------- 1 | #### print(_:separator:terminator:) 2 | * print(item) == String(item),可传递0个或多个参数,原样输出需打印的内容。 3 | ``` 4 | print("One two three four five") 5 | // Prints "One two three four five" 6 | 7 | print(1...5) 8 | // Prints "1...5" 9 | 10 | print(1.0, 2.0, 3.0, 4.0, 5.0) 11 | // Prints "1.0 2.0 3.0 4.0 5.0" 12 | ``` 13 | * separator:支持 item 之间添加分隔符,默认为空格。 14 | ``` 15 | print(1, 2, 3, 4, 5, separator: "...") 16 | // 1...2...3...4...5 17 | ``` 18 | * terminator:支持 item 末尾的终止符,默认为换行。 19 | ``` 20 | for i in 1...5 { 21 | print(i, terminator: "") 22 | } 23 | // 12345 24 | ``` 25 | #### debugPrint(_:separator:terminator:) 26 | * 使用及参数效果同上。 27 | * debugPrint 会额外的带有一些有用的调试信息: 28 | ``` 29 | print(1...5) 30 | // 1...5 31 | debugPrint(1...5) 32 | // ClosedRange(1...5) 33 | ``` 34 | 35 | #### 总结 36 | * print / debugPrint 可以使用 separator 和 terminator 参数进行定制化输出。 37 | * print 旨在提供标准化的输出。 38 | * debugPrint 在输出对象时,会附加更多有用的信息。所以用它调试复杂情况更加高效。 39 | 40 | #### 参考 41 | * [Stack Overflow](https://stackoverflow.com/questions/41826683/print-vs-debugprint-in-swift) -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/Reverse.md: -------------------------------------------------------------------------------- 1 | ## Reverse.swift 2 | ### reverse() 的源码实现 3 | 4 | ``` 5 | public mutating func reverse() { 6 | if isEmpty { return } 7 | var f = startIndex 8 | var l = index(before: endIndex) 9 | while f < l { 10 | swapAt(f, l) 11 | formIndex(after: &f) 12 | formIndex(before: &l) 13 | } 14 | } 15 | ``` 16 | 复杂度为 O(*n*)。 17 | 18 | 思路:循环头尾交换。 19 | 20 | ### 自己实现一个reverse() 21 | ``` 22 | func fzhReverse(_ nums: inout [Int]) { 23 | var f = nums.startIndex 24 | var l = nums.index(before: nums.endIndex) 25 | while f < l { 26 | nums.swapAt(f, l) 27 | nums.formIndex(after: &f) 28 | nums.formIndex(before: &l) 29 | } 30 | } 31 | ``` 32 | -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/Zip.md: -------------------------------------------------------------------------------- 1 | ## Zip 2 | 3 | zip 函数用来同时遍历两个集合,如果两个集合的长度不一致,则遍历完短集合即结束遍历。 4 | 5 | ### 使用 6 | 7 | ``` 8 | let string = "abcdef" 9 | let range = 1...4 10 | for (str, index) in zip(string, range) { 11 | print(str, index) 12 | // a 1 13 | // b 2 14 | // c 3 15 | // d 4 16 | } 17 | ``` 18 | 19 | ### 源码 20 | 该函数底层使用的是一个名字为 `Zip2Sequence` 的结构体。该结构体通过实现 `Sequence` 协议生成迭代器,实现 `IteratorProtocol` 协议进行迭代来实现同时遍历两个集合。 21 | 22 | #### 核心函数 23 | ``` 24 | public mutating func next() -> Element? { 25 | // 1 26 | if _reachedEnd { 27 | return nil 28 | } 29 | // 2 30 | guard let element1 = _baseStream1.next(), 31 | let element2 = _baseStream2.next() else { 32 | _reachedEnd = true 33 | return nil 34 | } 35 | // 3 36 | return (element1, element2) 37 | } 38 | ``` 39 | 40 | * 通过`_reachedEnd`变量来判断是否遍历至尾部 41 | * 判断是否两个集合的`next()`是否为空,若都不为空则进行下地迭代,若为空则置`_reachedEnd`为`true`,返回nil结束迭代。 42 | * 返回每次迭代的 tuple 43 | -------------------------------------------------------------------------------- /iOS/Swift/读 Swift 源码系列/图片下载 - Kingfisher.md: -------------------------------------------------------------------------------- 1 | ### 异步加载图片 2 | 3 | ### 内存和磁盘缓存图片 4 | 5 | ### 图片绘制 6 | 7 | ### 如何实现支持的特性 8 | * 异步下载缓存图片 9 | - 下载图片的函数调用栈:setImage -> retrieveImage -> loadAndCacheImage -> downloadImage -> resume 10 | - 使用 NSLock 来保证线程安全。 11 | * 可加载网络和本地的图片 12 | - 加载网络图片使用👆的步骤。 13 | - loadAndCacheImage 函数中,case 为 provider ,则会调用 provideImage 设置本地的图片并缓存。 14 | * 支持内存和磁盘缓存 15 | - 缓存图片的函数调用栈:cacheImage -> store -> storeNoThrow/syncStoreToDisk(队列异步执行)。 16 | - 内存缓存:MemoryStorage,底层数据结构使用的 NSCache。 17 | - 磁盘缓存:DiskStorage,使用 FileManager 方式管理。 18 | - 移除策略: 19 | - 为什么将 MemoryStorage、DiskStorage 声明为 enum ? -------------------------------------------------------------------------------- /iOS/Swift/通过 Property Wrappers 简化代码.md: -------------------------------------------------------------------------------- 1 | ### 什么是 Property Wrappers 2 | 属性包装器,Swift 的特性之一。用来修饰属性,它可以抽取关于属性重复的逻辑来达到简化代码的目的。比如线程安全检查、将数据存到数据库等。 3 | 4 | ### 如何使用 5 | 通过 `@propertyWrapper` 来标识。下面通过一个例子来说明它是如何使用的。 6 | 7 | #### 基本使用 8 | 假设有一个 SmallRectangle 的结构体,它有 height 和 width 两个属性,需要将两个属性的值都限制在 12 以下。 9 | 10 | 根据上述的需求,先创建一个 TwelveOrLess 限制条件的结构体: 11 | ``` 12 | @propertyWrapper 13 | struct TwelveOrLess { 14 | private var number: Int 15 | init() { self.number = 0 } 16 | var wrappedValue: Int { 17 | get { return number } 18 | set { number = min(newValue, 12) } 19 | } 20 | } 21 | ``` 22 | 23 | 在 SmallRectangle 结构体上使用: 24 | ``` 25 | struct SmallRectangle { 26 | @TwelveOrLess var height: Int 27 | @TwelveOrLess var width: Int 28 | } 29 | 30 | var rectangle = SmallRectangle() 31 | print(rectangle.height) 32 | // Prints "0" 33 | 34 | rectangle.height = 10 35 | print(rectangle.height) 36 | // Prints "10" 37 | 38 | rectangle.height = 24 39 | print(rectangle.height) 40 | // Prints "12" 41 | ``` 42 | 43 | 通过上面的代码可以看出,使用 @TwelveOrLess 修饰的属性可以自动将值限制在 12 及以下。 44 | 45 | 那么,当使用 `@TwelveOrLess var height: Int` 这句代码时,实际发生了什么呢? 46 | ``` 47 | // 这句代码 @TwelveOrLess var height: Int 相当于下面的代码(编译器会自动转为下面的代码): 48 | private var _height = TwelveOrLess() 49 | var height: Int { 50 | get { return _height.wrappedValue } 51 | set { _height.wrappedValue = newValue } 52 | } 53 | ``` 54 | 55 | `rectangle.height = 24` 这句代码的调用路径: 56 | 57 | * 调用 SmallRectangle height 的 set 函数。 58 | * 调用 TwelveOrLess wrappedValue 的 set 函数。 59 | * 调用 `number = min(newValue, 12)` 来保证新设置的值小于等于 12。 60 | 61 | #### 设置初始值 62 | 将上面的 SmallRectangle 改写为下面的代码你会发现报错 - `Argument passed to call that takes no arguments`: 63 | ``` 64 | struct SmallRectangle { 65 | @TwelveOrLess var height: Int = 1 66 | @TwelveOrLess var width: Int = 1 67 | } 68 | ``` 69 | 70 | 这是因为我们的 TwelveOrLess 并没有提供有参的初始化函数。在 TwelveOrLess 添加有参的初始化函数即可解决: 71 | ``` 72 | init(wrappedValue: Int) { 73 | number = min(12, wrappedValue) 74 | } 75 | ``` 76 | 77 | 假如我们的条件变量 12 也是可以动态的设置,可以再添加一个初始化函数,用来设置条件变量: 78 | ``` 79 | init(wrappedValue: Int, maximum: Int) { 80 | self.maximum = maximum 81 | number = min(maximum, wrappedValue) 82 | } 83 | ``` 84 | 85 | 使用 init(wrappedValue: Int, maximum: Int) 初始化属性: 86 | ``` 87 | struct NarrowRectangle { 88 | @TwelveOrLess(wrappedValue: 2, maximum: 5) var height: Int 89 | @TwelveOrLess(wrappedValue: 3, maximum: 4) var width: Int 90 | } 91 | 92 | var narrowRectangle = NarrowRectangle() 93 | print(narrowRectangle.height, narrowRectangle.width) 94 | // Prints "2 3" 95 | 96 | narrowRectangle.height = 100 97 | narrowRectangle.width = 100 98 | print(narrowRectangle.height, narrowRectangle.width) 99 | // Prints "5 4" 100 | ``` 101 | 102 | `Tips:当没有给 @TwelveOrLess 修饰的变量赋初始值时,默认使用 init() 初始化。` 103 | ``` 104 | struct ZeroRectangle { 105 | @TwelveOrLess var height: Int 106 | @TwelveOrLess var width: Int 107 | } 108 | 109 | var zeroRectangle = ZeroRectangle() 110 | print(zeroRectangle.height, zeroRectangle.width) 111 | // Prints "0 0" 112 | ``` 113 | 114 | #### ProjectedValue 115 | projectedValue 用来获取你定义逻辑的一些额外状态值。 116 | 117 | 比如在上面的例子中,你想获取你设置的值是否超过了限定的最大值,这个就可以用 projectedValue 来获取。 118 | ``` 119 | @propertyWrapper 120 | struct TwelveOrLess { 121 | private var number: Int 122 | private var maximum: Int 123 | var projectedValue: Bool 124 | 125 | var wrappedValue: Int { 126 | get { return number } 127 | set { 128 | if newValue > 12 { 129 | number = 12 130 | projectedValue = true 131 | } else { 132 | number = newValue 133 | projectedValue = false 134 | } 135 | } 136 | } 137 | 138 | init() { 139 | projectedValue = false 140 | self.number = 0 141 | self.maximum = 12 142 | } 143 | 144 | init(wrappedValue: Int) { 145 | projectedValue = false 146 | maximum = 12 147 | number = min(maximum, wrappedValue) 148 | } 149 | 150 | init(wrappedValue: Int, maximum: Int) { 151 | projectedValue = false 152 | self.maximum = maximum 153 | number = min(maximum, wrappedValue) 154 | } 155 | } 156 | ``` 157 | 158 | 获取状态值: 159 | ``` 160 | struct SomeStructure { 161 | @TwelveOrLess var someNumber: Int 162 | } 163 | var someStructure = SomeStructure() 164 | 165 | someStructure.someNumber = 4 166 | print(someStructure.$someNumber) 167 | // Prints "false" 168 | 169 | someStructure.someNumber = 55 170 | print(someStructure.$someNumber) 171 | // Prints "true" 172 | ``` 173 | `通过 $+属性名的方式来获取 projectedValue.` 174 | 175 | 当设值为 4 的时候,没有大于 12,没有触发条件,所以 `$someNumber` 为 false;当设值为 55 的时候,大于 12,触发了条件,所以 `$someNumber` 为 true。 176 | 177 | ### 实际项目应用 178 | 179 | * 将字符串字符全部小写: 180 | ``` 181 | @propertyWrapper 182 | struct LowerLetter { 183 | private var value = "" 184 | var wrappedValue: String { 185 | set { 186 | value = newValue.lowercased() 187 | } 188 | get { return value} 189 | } 190 | 191 | init(wrappedValue: String) { 192 | value = wrappedValue.lowercased() 193 | } 194 | } 195 | 196 | struct Person { 197 | @LowerLetter var name: String 198 | } 199 | 200 | let p = Person(name: "ABCd") 201 | p.name // abcd 202 | ``` 203 | * [自动 weak self](https://github.com/dreymonde/Delegated): 204 | ``` 205 | // 声明 206 | @propertyWrapper 207 | public final class Delegated1 { 208 | public init() { self.callback = { _ in } } 209 | private var callback: (Input) -> Void 210 | public var wrappedValue: (Input) -> Void { return callback } 211 | public var projectedValue: Delegated1 { return self } 212 | } 213 | 214 | public extension Delegated1 { 215 | func delegate( 216 | to target: Target, 217 | with callback: @escaping (Target, Input) -> Void) { 218 | self.callback = { [weak target] input in 219 | guard let target = target else { 220 | return 221 | } 222 | return callback(target, input) 223 | } 224 | } 225 | } 226 | 227 | // 使用 228 | final class TextField { 229 | @Delegated1 var didUpdate: (String) -> () 230 | } 231 | 232 | let textField = TextField() 233 | 234 | textField.$didUpdate.delegate(to: self) { (self, text) in 235 | // `self` is weak automatically! 236 | self.label.text = text 237 | } 238 | ``` -------------------------------------------------------------------------------- /iOS/UI/通过functionBuilder 简化 UIAlertController 的调用.md: -------------------------------------------------------------------------------- 1 | 在开始介绍 functionBuilder 的用法前,我们先来看一下目前如何来调用一个 UIAlertController: 2 | ``` 3 | let alertController = UIAlertController( 4 | title: "删除", 5 | message: "确定删除?", 6 | preferredStyle: .alert 7 | ) 8 | let deleteAction = UIAlertAction(title: "删除", style: .destructive) { _ in 9 | // 删除逻辑 10 | } 11 | let cancelAction = UIAlertAction(title: "取消", style: .cancel) 12 | alertController.addAction(deleteAction) 13 | alertController.addAction(cancelAction) 14 | ``` 15 | 可以看到代码量还是比较多的,下面来看一下如何使用 FunctionBuilder ,来构建一个调用起来更加舒适的 API。 16 | 17 | ### 什么是 FunctionBuilder 18 | 19 | 该特性实在 Swift 5.1 开始提出的,由于它现在还未完全支持,所以我们只能使用私有的修饰符 `@_functionBuilder`,而不是 @functionBuilder。你可以在[这里](https://github.com/apple/swift-evolution/blob/9992cf3c11c2d5e0ea20bee98657d93902d5b174/proposals/XXXX-function-builders.md#function-building-methods)找到关于它官方的介绍。 20 | 21 | 该特性主要侧重于用 DSL 来表示HTML 树,但它也大量应用于 SwiftUI 中,比如 @ViewBuilder。 22 | 23 | 主要使用的是以下三个方法: 24 | * 必须实现: buildBlock(_ components: Component...) -> Component 25 | * 可选:buildIf(_ component: Component?) -> Component 26 | * 可选:buildEither(first: Component) -> Component / buildEither(second: Component) -> Component 27 | 28 | ### FunctionBuilder 初体验 29 | 现在来具体实操一下,首先来声明一个 Action 来代替 UIAlertAction: 30 | ``` 31 | struct Action { 32 | let title: String 33 | let style: UIAlertAction.Style 34 | let action: () -> Void 35 | } 36 | ``` 37 | 38 | 接下来声明一个工厂方法来创建 UIAlertController 对象: 39 | ``` 40 | func makeAlertController(title: String, 41 | message: String, 42 | style: UIAlertController.Style, 43 | actions: [Action]) -> UIAlertController { 44 | let controller = UIAlertController( 45 | title: title, 46 | message: message, 47 | preferredStyle: style 48 | ) 49 | for action in actions { 50 | let uiAction = UIAlertAction(title: action.title, style: action.style) { _ in 51 | action.action() 52 | } 53 | controller.addAction(uiAction) 54 | } 55 | return controller 56 | } 57 | ``` 58 | 然后,就是我们的重头戏-`functionBuilder`的使用: 59 | ``` 60 | @_functionBuilder 61 | struct ActionBuilder { 62 | typealias Component = [Action] 63 | static func buildBlock(_ children: Component...) -> Component { 64 | return children.flatMap { $0 } 65 | } 66 | } 67 | ``` 68 | 使用它来创建 UIAlertController: 69 | ``` 70 | func Alert(title: String, 71 | message: String, 72 | @ActionBuilder _ makeActions: () -> [Action]) -> UIAlertController { 73 | makeAlertController(title: title, 74 | message: message, 75 | style: .alert, 76 | actions: makeActions()) 77 | } 78 | ``` 79 | 80 | ok,现在到了品尝胜利果实的时候了,来看一下现在如何调用 UIAlertController: 81 | ``` 82 | let alertVC = Alert(title: "删除", message: "确认删除?") { () -> [Action] in 83 | [Action(title: "删除", style: .destructive, action: { /* ... */ })] 84 | [Action(title: "取消", style: .cancel, action: {})] 85 | } 86 | ``` 87 | 额....,虽然代码确实简化了,但闭包里的[Action...]看着还是很怪异。这个可以通过将其包装为类方法来解决,更新 Action 代码如下: 88 | ``` 89 | extension Action { 90 | static func `default`(_ title: String, action: @escaping () -> Void) -> [Action] { 91 | return [Action(title: title, style: .default, action: action)] 92 | } 93 | 94 | static func destructive(_ title: String, action: @escaping () -> Void) -> [Action] { 95 | return [Action(title: title, style: .destructive, action: action)] 96 | } 97 | 98 | static func cancel(_ title: String, action: @escaping () -> Void = {}) -> [Action] { 99 | return [Action(title: title, style: .cancel, action: action)] 100 | } 101 | } 102 | ``` 103 | 104 | 现在再来看一下使用: 105 | ``` 106 | let alertVC = Alert(title: "删除", message: "确认删除?") { () -> [Action] in 107 | Action.destructive("删除") { } 108 | Action.cancel("取消") 109 | } 110 | ``` 111 | 嗯,看起来好多了😊。 112 | 113 | ### 更进一步 - 如何支持 if 114 | 假设我们想在 Alert 的闭包中添加 if 语句的话,编译器会报错:`Closure containing control flow statement cannot be used with function builder 'ActionBuilder'`。比如下面的代码: 115 | ``` 116 | let alertVC = Alert(title: "删除", message: "确认删除?") { () -> [Action] in 117 | Action.destructive("删除") { } 118 | if condition { } 119 | Action.cancel("取消") 120 | } 121 | ``` 122 | 该错误可以通过实现 `func buildIf(_ component: Component?) -> Component` 来解决,更新后的 ActionBuilder 代码如下: 123 | ``` 124 | @_functionBuilder 125 | struct ActionBuilder { 126 | typealias Component = [Action] 127 | 128 | static func buildBlock(_ children: Component...) -> Component { 129 | return children.flatMap { $0 } 130 | } 131 | 132 | static func buildIf(_ component: Component?) -> Component { 133 | return component ?? [] 134 | } 135 | } 136 | ``` 137 | 如需支持 if - else,则需添加下述两个方法: 138 | ``` 139 | static func buildEither(first component: Component) -> Component { 140 | return component 141 | } 142 | 143 | static func buildEither(second component: Component) -> Component { 144 | return component 145 | } 146 | ``` 147 | 148 | Note:若添加上述两个方法,就不再需要 `func buildIf(_ component: Component?) -> Component` 了。 149 | 150 | ### 最后的尝试 - 支持 for - in 151 | 比如下述的代码: 152 | ``` 153 | let alertVC = Alert(title: "删除", message: "确认删除?") { () -> [Action] in 154 | for str in ["删除", "取消"] { 155 | Action.default(str) { print(str) } 156 | } 157 | } 158 | ``` 159 | 160 | 创建 helper 函数来解决: 161 | ``` 162 | func ForIn( 163 | _ sequence: S, 164 | @ActionBuilder makeActions: (S.Element) -> [Action] 165 | ) -> [Action] { 166 | 167 | return sequence 168 | .map(makeActions) // [[Action]] 169 | .flatMap { $0 } // [Action] 170 | } 171 | ``` 172 | 173 | 最终,这样使用它: 174 | ``` 175 | let alertVC = Alert(title: "删除", message: "确认删除?") { () -> [Action] in 176 | ForIn(["删除", "取消"]) { string in 177 | Action.default(string) { print(string) } 178 | } 179 | } 180 | ``` 181 | 182 | 最后,来个代码对比: 183 | ``` 184 | let alertController = UIAlertController( 185 | title: "删除", 186 | message: "确定删除?", 187 | preferredStyle: .alert 188 | ) 189 | let deleteAction = UIAlertAction(title: "删除", style: .destructive) { _ in 190 | // 删除逻辑 191 | } 192 | let cancelAction = UIAlertAction(title: "取消", style: .cancel) 193 | alertController.addAction(deleteAction) 194 | alertController.addAction(cancelAction) 195 | 196 | // 使用 @_functionBuilder 后的 UIAlertController 197 | let alertVC = Alert(title: "删除", message: "确认删除?") { () -> [Action] in 198 | Action.destructive("删除") { } 199 | Action.cancel("取消") 200 | } 201 | ``` 202 | 203 | 👀了上面的代码对比,要不要使用 `@_functionBuilder` 就不用我说了吧,大兄嘚。 -------------------------------------------------------------------------------- /iOS/WWDC/WWDC2019/Advances in Foundation.md: -------------------------------------------------------------------------------- 1 | ### Foundation 的改进 2 | 3 | #### difference 的使用 4 | `difference` 函数用于任意元素实现 Equaltable 、且有序集合。 5 | 6 | 下面是该函数的示例代码: 7 | ``` 8 | let bird = "bird" 9 | let bear = "bear" 10 | // step1 11 | let diff = bird.difference(from: bear) //两个字符创不同的部分: i r e a 12 | // step2 13 | let newBird = bear.applying(diff) // bird 14 | ``` 15 | 16 | step1 通过 `difference` 函数获得了 bird 和 bear 不同的部分 diff,step2 通过调用 `applying` 函数将 diff 应用到 bear 上来得到 newBird。 17 | 18 | 因为 `difference` 函数可以应用于任意有序集合,所以也可以在数组中使用: 19 | ``` 20 | let arr1 = [1, 2, 3] 21 | let arr2 = [2, 3, 4] 22 | let diff = arr1.difference(from: arr2) // 两个数组不同的部分:[1, 4] 23 | let newArr1 = arr2.applying(diff) 24 | print(newArr1) // [1, 2, 3] 25 | ``` 26 | 27 | 因为 `difference` 函数返回的结果本身也是一个集合,所以我们也可以使用 for-in 来迭代它的元素: 28 | ``` 29 | for change in diff { 30 | switch change { 31 | case .remove(let offset, let element, _): 32 | print("offset = \(offset), element = \(element)") // offset = 2, element = 4 33 | case .insert(let offset, let element, _): 34 | print("offset = \(offset), element = \(element)") // offset = 0, element = 1 35 | } 36 | } 37 | ``` 38 | 39 | * 对 arr2 移除 index 为 2 的元素,得到 [2, 3]。 40 | * 在 index 为 0 的位置插入 1。得到 [1, 2, 3] 即 arr1 的内容 41 | 42 | #### 数据压缩 43 | 在 Swift 5.1,官方提供了数据压缩函数,但该函数仅限于 NSData 使用。 44 | 45 | 示例代码: 46 | ``` 47 | let str = "Swift" 48 | let data = str.data(using: .utf8) 49 | if let d = data { 50 | let d1 = d as NSData 51 | let res = try d1.compressed(using: .lzfse) 52 | print(res) 53 | } 54 | ``` 55 | CompressionAlgorithm 为枚举,提供四种 case:lz4、lzfse、lzma、zlib。 56 | 57 | #### 新增单位类型 58 | UnitDuration 新增以下类型: 59 | * milliseconds:毫秒 60 | * microseconds:微秒 61 | * nanoseconds:纳秒 62 | * picoseconds:皮秒 63 | 64 | UnitFrequency 新增 framesPerSecond,可用来测试 FPS。 65 | 66 | UnitInformationStorage 新增以下类型: 67 | * bits 68 | * bytes 69 | * nibbles 70 | 71 | #### 显示日期或时间 72 | 新增 RelativeDateTimeFormatter 用来计算两个日期之间的间隔,支持本地化。这个更新很实用,可以很方便的计算各种类似于微信聊天的时间标签。 73 | 74 | ``` 75 | let oldDateStr = "2021-01-05" 76 | let dateFormatter = DateFormatter() 77 | dateFormatter.dateFormat = "yyyy-MM-dd" 78 | let oldDate = dateFormatter.date(from: oldDateStr)! 79 | 80 | let nowDate = Date() 81 | let formatter = RelativeDateTimeFormatter() 82 | formatter.locale = Locale(identifier: "zh_CN") 83 | let dateString = formatter.localizedString(for: oldDate, relativeTo: nowDate) 84 | print(dateString) // 1周前 85 | ``` 86 | 87 | #### List Formatter 88 | 用于本地化显示数组转成的字符串: 89 | ``` 90 | let str = ["🤣", "🐖", "🦄"] 91 | let formatter = ListFormatter() 92 | 93 | formatter.locale = Locale(identifier: "zh_CN") 94 | let zhString = formatter.string(for: str) 95 | print(zhString) // "🤣、🐖和🦄" 96 | 97 | formatter.locale = Locale(identifier: "en_US") 98 | let enString = formatter.string(for: str) 99 | print(enString) // "🤣, 🐖, and 🦄" 100 | ``` 101 | 102 | 通过 itemFormatter 来设置 item 的格式。 103 | ``` 104 | func stringToDate(str: String) -> Date? { 105 | let dateFormatter = DateFormatter() 106 | dateFormatter.dateFormat = "yyyy-MM-dd" 107 | return dateFormatter.date(from:str) 108 | } 109 | 110 | let dateStr = ["2020-01-09", "2020-02-05", "2020-04-21"] 111 | let dates = dateStr.compactMap { stringToDate(str: $0) } 112 | 113 | let listFormatter = ListFormatter() 114 | let dateFromatter = DateFormatter() 115 | dateFromatter.dateFormat = "yyyy-MM-dd" 116 | 117 | listFormatter.itemFormatter = dateFromatter 118 | let str = listFormatter.string(for: dates) // "2020-01-09, 2020-02-05, and 2020-04-21" 119 | 120 | // 通过设置 locale 来进行本地化输出 121 | listFormatter.itemFormatter = dateFromatter 122 | listFormatter.locale = Locale(identifier: "es_ES") 123 | let str = listFormatter.string(for: dates) // "2020-01-09, 2020-02-05 y 2020-04-21" 124 | ``` 125 | 126 | #### Operation Queue 127 | 新增 `addBarrierBlock` 。当所有 task 执行完之后,才会执行 `addBarrierBlock` 的 task。 128 | ``` 129 | let queue = OperationQueue() 130 | 131 | queue.addOperation { 132 | print("task1") 133 | } 134 | 135 | queue.addOperation { 136 | print("task2") 137 | } 138 | 139 | queue.addOperation { 140 | print("task3") 141 | } 142 | 143 | queue.addOperation { 144 | print("task4") 145 | } 146 | 147 | queue.addBarrierBlock { 148 | print("complete") 149 | } 150 | // task1 task2 task3 task4 complete 151 | ``` 152 | 153 | 通过设置 totalUnitCount 来进行进度检测: 154 | ``` 155 | queue.progress.totalUnitCount = 3 156 | ``` 157 | 158 | #### Scanner 159 | 优化了 Scanner 的使用方式。 160 | 161 | ``` 162 | let scanner = Scanner(string: "") 163 | 164 | // Swift 4 165 | var nameNSString: NSString? 166 | if scanner.scanUpToCharacters(from: .newlines, into: &nameNSString) { 167 | let name = nameNSString! as String 168 | } 169 | 170 | // Swift 5.1 171 | let nameString = scanner.scanUpToCharacters(from: .newlines) 172 | ``` -------------------------------------------------------------------------------- /iOS/WWDC/WWDC2019/Introduce Combine.md: -------------------------------------------------------------------------------- 1 | ### 什么是 Combine 2 | Combine:一个统一的、声明式的 API,用来处理随时间而改变的数据流。 3 | 4 | 它可以用来合并异步事件,比如异步调用三个接口,等三个接口全部请求完再处理结果。这种需求就可以使用 Combine 来实现。 5 | 6 | #### 可以处理的异步交互 7 | * Targe/Action 8 | * Notification center 9 | * URLSession 10 | * Key-value observing 11 | * Ad-hoc callbacks 12 | 13 | #### 特性 14 | * 支持泛型 15 | * 支持类型安全 16 | * 组合优先 17 | * 请求驱动 18 | 19 | ### 关键概念 20 | Combine 由三个关键概念来驱动:Publishers、Subscribers、Operators。 21 | 22 | #### Publisher 23 | Publisher:用来的定义数据流和错误是如何被处理。底层由 struct 来实现,即为值类型。允许注册 Subscriber。 24 | 25 | #### Subscriber 26 | Subscriber:接受 Publisher 传递的值,底层由 class 实现,即为引用类型。 27 | 28 | #### Operator 29 | Operator:用来适配 Publisher 到 Subscriber 的数据流,使数据流可在 Subscriber 中使用,值类型。 30 | 31 | 数据流向:Operator 从上游的 Publisher 接受数据,适配数据,再将适配结果发送给下游的 Subscriber。 32 | 33 | 声明式操作符 API: 34 | * 函数转变 35 | * 列表操作 36 | * 错误处理 37 | * 线程或队列转移 38 | * 时间调度 39 | 40 | -------------------------------------------------------------------------------- /iOS/WWDC/WWDC2020/Eliminate animation hitches with XCTest.md: -------------------------------------------------------------------------------- 1 | ### 使用 XCtest 测试动画卡顿。 2 | 3 | #### 为什么会出现卡顿? 4 | iPhone 的频率是 60 hz,平均每一帧的时间是 16.67 ms,当 APP 中的帧时间占用大于 16.67 ms 时就会造成视觉上的卡顿。 5 | 6 | * 通过两种方式来表示: 7 | - Hitch time:帧延迟显示的时间(ms)。 8 | - Hitch ratio:每秒延迟的时间,ms/s。 9 | * Hitch ratio 的分级: 10 | - Good: ≤ 5ms/s 11 | - Warning: ≥ 5ms/s && < 10ms/s 12 | - Critical: ≥ 10ms/s 13 | 14 | #### 如何检测卡顿?- XCTOSSignpostMetric 15 | * 使用 XCTOSSignpostMetric 的预备配置:防止这些配置影响测试结果 16 | - Scheme -> Test -> Build Configuration 调成 Release 。 17 | - xctestplan -> Configurations -> Automatic Screenshots 调成 Off。 18 | - xctestplan -> Configurations -> Code Coverage 调成 Off。 19 | - xctestplan -> Configurations -> Runtime Sanitization/Runtime API Checking/Memory Management 调成 Off。 20 | * XCTOSSignpostMetric 默认会测试 5 次。 21 | * 测试代码: 22 | ``` 23 | func testScrollingAnimationPerformance() throws { 24 | // step1 25 | let app = XCUIApplication() 26 | app.launch() 27 | app.staticTexts["Meal Planner"].tap() 28 | 29 | let foodCollection = app.collectionViews.firstMatch 30 | // step2 31 | let meausreOptions = XCTMeasureOptions() 32 | meausreOptions.invocationOptions = [.manuallyStop] 33 | // step3 34 | measure(metrics: [XCTOSSignpostMetric.scrollDraggingMetric], 35 | options: meausreOptions) { 36 | // 向上滑动 37 | foodCollection.swipeUp(velocity: .fast) 38 | // 停止测试 39 | stopMeasuring() 40 | // 向下滑动 41 | foodCollection.swipeDown(velocity: .fast) 42 | } 43 | } 44 | ``` 45 | * 代码说明 46 | - step1,进入到相关的测试页面。 47 | - step2,配置 XCTMeasureOptions,表示我们要手动停止每次测试。 48 | - step3,测试相关 XCTOSSignpostMetric 相关指标。 49 | 50 | * 添加手动停止、停止测试、向下滑动的代码重置环境,是因为要保证每次测试的环境是一致的,这样的测试结果才会相对正确。 51 | 52 | * XCTOSSignpostMetric 可测试的指标 53 | - navigationTransitionMetric:测试两个 view 之间导航的动画。 54 | - customNavigationTransitionMetric:测试两个 view 之间自定义导航的动画。 55 | - scrollDecelerationMetric:测试减速时的动画。 56 | - scrollDraggingMetric:测试滑动时的动画。 57 | -------------------------------------------------------------------------------- /iOS/WWDC/WWDC2020/Embrace Swift Type Inference.md: -------------------------------------------------------------------------------- 1 | ### 活用 Swift 类型推断 2 | * 类型推断的工作机制 3 | ``` 4 | let x = "" 5 | //虽然代码中没有显式的写出 x 的类型,但 compile 可以通过类型推断算法将其补全为下面的代码。 6 | let x: String = "" 7 | ``` 8 | * compiler 如何自动捕获代码中的错误 9 | - 记录源码中的错误信息。 10 | - 尝试去修复错误一遍继续类型推断算法。 11 | - 基于收集的信息来提供有用的错误信息。 12 | * 类型推断就像拼图,一块一块的拼完整。 13 | * 显示当前错误信息: 14 | - Xcode -> Behaviors -> Edit Behaviors -> Fails -> 选中 [Show] Navigator [Current] 15 | 16 | * 对于复杂的类型推断编译器是如何替换的: 17 | - Before 18 | ![image](https://github.com/fengzhihao123/FZHBlog/blob/master/images/embrace_swift_type_inference_before.png "类型推断前") 19 | - After 20 | ![image](https://github.com/fengzhihao123/FZHBlog/blob/master/images/embrace_swift_type_inference_after.png "类型推断后") 21 | * 类型推断的好处: 22 | - 代码更加简洁、易读。 23 | - 编码更加简单、高效。 24 | -------------------------------------------------------------------------------- /iOS/WWDC/WWDC2020/Lists in UICollectionView.md: -------------------------------------------------------------------------------- 1 | ## List in UICollectionView 2 | 在 iOS 14 中,我们可以很轻松通过下面的三步来实现一个默认的列表。 3 | * 设置 collectionView 的 configuration 和 layout。 4 | * 配置 ListCell。 5 | * 设置 DiffableDataSource。 6 | 7 | ### 设置 configuration 和 layout 8 | 通过 UICollectionLayoutListConfiguration、UICollectionViewCompositionalLayout来配置,仅需三行代码。 9 | ``` 10 | func makeCollectionView() -> UICollectionView { 11 | // step1 12 | let config = UICollectionLayoutListConfiguration(appearance: .plain) 13 | // step2 14 | let layout = UICollectionViewCompositionalLayout.list(using: config) 15 | return UICollectionView(frame: view.bounds, collectionViewLayout: layout) 16 | } 17 | ``` 18 | 19 | 代码说明: 20 | #### UICollectionLayoutListConfiguration 21 | step1:设置 collectionView list 风格的config。 22 | 23 | UICollectionLayoutListConfiguration 包含 Appearance、HeaderMode、FooterMode 三个 enum。 24 | * Appearance:用于设置列表的格式。 25 | * HeaderMode:用于设置头视图的类型。 26 | * FooterMode:用于设置尾视图的类型。 27 | 28 | 除了👆的三个枚举,它还包含👇两个比较重要的属性: 29 | * leadingSwipeActionsConfigurationProvider:左向清扫的事件集合。 30 | * trailingSwipeActionsConfigurationProvider:右向清扫的事件集合。 31 | 32 | #### UICollectionViewCompositionalLayout 33 | step2:将 config 赋值给 UICollectionViewCompositionalLayout 34 | 35 | ### 配置 ListCell 36 | 设置完 collectionView ,下面来配置下 UICollectionViewListCell 。该类是 iOS 14 新推出的,专门针对 list 视图。 37 | ``` 38 | func makeCellRegistration() -> UICollectionView.CellRegistration { 39 | UICollectionView.CellRegistration { cell, indexPath, contact in 40 | // step1 41 | var config = cell.defaultContentConfiguration() 42 | // step2 43 | config.text = contact.name 44 | config.secondaryText = contact.email 45 | // step3 46 | cell.contentConfiguration = config 47 | // step4 48 | cell.accessories = [.disclosureIndicator()] 49 | } 50 | } 51 | ``` 52 | 53 | 代码说明: 54 | * step1:获取 cell 默认的 config。注意,该 config 有默认的样式但没有任何数据。 55 | * step2:给 config 设置内容。 56 | * step3:仅获取默认的 config,并给该 config 赋值是不够的的,还需给 `contentConfiguration` 赋值。 57 | * step4:设置指示器。 58 | 59 | ### 设置 DiffableDataSource 60 | 最后一步,配置数据源。 61 | 62 | ``` 63 | func makeDataSource() -> UICollectionViewDiffableDataSource { 64 | let cellRegistration = makeCellRegistration() 65 | return UICollectionViewDiffableDataSource( 66 | collectionView: collectionView) { (view, indexPath, item) -> UICollectionViewCell? in 67 | view.dequeueConfiguredReusableCell( 68 | using: cellRegistration, 69 | for: indexPath, 70 | item: item) 71 | } 72 | } 73 | ``` 74 | 至此,我们即可通过 collectionView 完成一个列表视图。 75 | 76 | ### 总结 77 | 通过设置config + layout、配置 cell、添加数据源三步即可实现一个列表。主要使用到的类: 78 | * UICollectionLayoutListConfiguration、UICollectionViewCompositionalLayout:配置 collectionView。 79 | * UICollectionViewListCell: 配置 cell。 80 | * UICollectionViewDiffableDataSource:数据源。 81 | 82 | ### 完整代码 83 | ``` 84 | import UIKit 85 | 86 | class ViewController: UIViewController { 87 | private let viewModel = ContactListViewModel() 88 | private lazy var collectionView = makeCollectionView() 89 | private lazy var dataSource = makeDataSource() 90 | 91 | override func viewDidLoad() { 92 | super.viewDidLoad() 93 | 94 | collectionView.dataSource = dataSource 95 | view.addSubview(collectionView) 96 | updateList() 97 | } 98 | } 99 | 100 | private extension ViewController { 101 | func makeCollectionView() -> UICollectionView { 102 | let config = UICollectionLayoutListConfiguration(appearance: .sidebarPlain) 103 | let layout = UICollectionViewCompositionalLayout.list(using: config) 104 | return UICollectionView(frame: view.bounds, collectionViewLayout: layout) 105 | } 106 | 107 | func makeCellRegistration() -> UICollectionView.CellRegistration { 108 | UICollectionView.CellRegistration { cell, indexPath, contact in 109 | var config = cell.defaultContentConfiguration() 110 | config.text = contact.name 111 | config.secondaryText = contact.email 112 | cell.contentConfiguration = config 113 | cell.accessories = [.disclosureIndicator()] 114 | } 115 | } 116 | 117 | func makeDataSource() -> UICollectionViewDiffableDataSource { 118 | let cellRegistration = makeCellRegistration() 119 | return UICollectionViewDiffableDataSource( 120 | collectionView: collectionView) { (view, indexPath, item) -> UICollectionViewCell? in 121 | view.dequeueConfiguredReusableCell( 122 | using: cellRegistration, 123 | for: indexPath, 124 | item: item) 125 | } 126 | } 127 | 128 | func updateList() { 129 | var snapshot = NSDiffableDataSourceSnapshot() 130 | snapshot.appendSections(Section.allCases) 131 | snapshot.appendItems(viewModel.favorites, toSection: .favorites) 132 | snapshot.appendItems(viewModel.all, toSection: .all) 133 | dataSource.apply(snapshot) 134 | } 135 | } 136 | 137 | struct ContactListViewModel { 138 | var favorites = [Contact(name: "jack", email: "jack-email"), Contact(name: "rose", email: "rose-email")] 139 | var all = [Contact(name: "mark", email: "mark-email"), Contact(name: "jerry", email: "jerry-emial")] 140 | } 141 | 142 | enum Section: CaseIterable { 143 | case favorites 144 | case all 145 | } 146 | 147 | struct Contact: Hashable { 148 | var name: String 149 | var email: String 150 | } 151 | ``` 152 | 153 | 仅需 60 多行即可实现一个列表,这样的代码它不香吗? -------------------------------------------------------------------------------- /iOS/WWDC/WWDC2020/unsafe_swift.md: -------------------------------------------------------------------------------- 1 | ## Unsafe Swift 2 | 3 | ### 开胃菜 - unsafelyUnwrapped 4 | 相信大家对于如何解包都已经烂熟于心,比如以下的代码: 5 | ``` 6 | var name: String? = "Swift" 7 | if let n = name { 8 | print(n) 9 | } 10 | ``` 11 | 那么关于强制解包,不知道大家知不知道 `unsafelyUnwrapped` (反正看WWDC之前我是不知道🤦‍)? 下面看一下 `!` 与该属性有何不同: 12 | ``` 13 | var str: String? 14 | print(str.unsafelyUnwrapped) 15 | print(str!) 16 | ``` 17 | * `unsafelyUnwrapped` 的作用也是强制解包。在 optimized builds (-O)模式下,它不会去检查当前实例是否有值,如果访问的当前实例为 nil,则会触发无法预知的行为或者运行时错误;在 debug builds (-Onone)模式下,与 `!` 的行为一致。 18 | * 当调用 [unsafeBitCast(_:)](https://developer.apple.com/documentation/swift/1641250-unsafebitcast) 函数时,推荐使用 `unsafelyUnwrapped`,因为该属性的限制性更强,而且在 debug builds 时访问该属性仍会进行检查。 19 | * 该属性属于以安全性换取性能,谨慎使用。 20 | 21 | 22 | 总结:在日常开发中除极特殊情况,根本用不到😂。 23 | 24 | ### 何为 unsafe,何为safe 25 | 众所众知,Swift 是一门安全的编程语言,那么如何理解 unsafe 和 safe 呢? 26 | 27 | safe :系统会对所有的操作负责,并不是代表不会产生 crash,即 Safe code ≠ no crashes。在标准库中,很多操作在执行之前就会被检查是否有错误。比如下面的代码在编译期间就会报错: 28 | ``` 29 | var age: Int 30 | age = "" 31 | // Cannot assign value of type 'String' to type 'Int' 32 | ``` 33 | 对于强制解包(!)来说,它也是安全的,因为我们都能预知它产生什么行为-> Fatal error: Unexpectedly found nil while unwrapping an Optional。 34 | 35 | unsafe:系统假定程序员对所有的操作负责,所以它不会再进行检查。比如上述提到的`unsafelyUnwrapped`。 36 | 37 | ### 为什么需要 unsafe 38 | * 与 C 或者 OC 的代码进行交互 39 | * 控制 runtime 的性能 40 | 41 | ### unsafe pointer 42 | 官方例子: 43 | ``` 44 | let ptr = UnsafeMutablePointer.allocate(capacity: 1) 45 | ptr.initialize(to: 42) 46 | print(ptr.pointee) // 42 47 | ptr.deallocate() 48 | ptr.pointee = 23 // UNDEFINED BEHAVIOR,输出为 23。 49 | ``` 50 | 关于 UNDEFINED BEHAVIOR 自己的理解:在逻辑上 ptr 已经被释放,后面再引用该指针导致该指针不能被正常释放,造成内存泄漏。这种预期与代码逻辑的预期不一致:`ptr 并没有被释放`。 51 | 52 | #### 基于闭包 VS 隐式指针 53 | ``` 54 | var value = 42 55 | withUnsafeMutablePointer(to: &value) { p in 56 | p.pointee += 1 57 | } 58 | print(value) // 43 59 | ``` 60 | 基于闭包的优点:使 p 的生命周期更加明确,从而避免生命周期相关的问题。 61 | 62 | ``` 63 | var value2 = 42 64 | let p = UnsafeMutablePointer(&value2) // BROKEN -- dangling pointer! 65 | p.pointee += 1 66 | print(value2) 67 | ``` 68 | 隐式指针的缺点:p 的声明周期未控制,可能会造成无法预知的问题。比如下面的代码会造成 crash: 69 | ``` 70 | p.deallocate() // malloc: *** error for object 0x10000d668: pointer being freed was not allocated 71 | ``` 72 | 因为 p 已经被释放,所以在调用 deallocate() 会造成crash。 73 | 74 | 推荐使用基于闭包的方式。 75 | 76 | #### C 与 Swift 的指针映射 77 | Example: 78 | ``` 79 | // C: 80 | void process_integers(const int *start, size_t count); 81 | 82 | // Swift: 83 | func process_integers(_ start: UnsafePointer!, _ count: Int) 84 | ``` 85 | 86 | ![map](https://github.com/fengzhihao123/FZHBlog/blob/master/images/c-swift-map.png) 87 | ### Array 和 String 新增两个 unsafe 的初始化方法 88 | ![New API](https://github.com/fengzhihao123/FZHBlog/blob/master/images/new_api.png) 89 | ### 总结 90 | * 尽量少使用 unsafe,若使用 unsafe 的接口,谨遵规则每个接口的规则 91 | * 多测试 92 | 93 | 94 | * [WWDC - Video](https://developer.apple.com/videos/play/wwdc2020/10648) 95 | * [Link](https://wwdcnotes.com/notes/wwdc20/10648/) 96 | -------------------------------------------------------------------------------- /iOS/WWDC/WWDC2020/why is my app getting killed.md: -------------------------------------------------------------------------------- 1 | ### 造成 App 后台异常退出的原因 2 | * Crashes 3 | * 看门狗事件 4 | * CPU 资源限制 5 | * 内存资源限制 6 | * 内存清理 7 | * 后台任务超时 8 | 9 | ### Crashes 10 | Crashes 通常由以下三种情况造成: 11 | * 分段错误 12 | * 非法指令 13 | * 断言和未捕获的异常 14 | 如果向更进一步了解 Crashes 以及其解决方法,可以看下 WWDC2018 的[解读崩溃和崩溃日志](https://developer.apple.com/wwdc18/414)。 15 | 16 | ### 看门狗事件 17 | * App 关键切换时超时(切换不要超过 20 s),在模拟器不会受影响。 18 | - application(_:didFinishLaunchingWithOptions:) 19 | - applicationDidEnterBackground(_:) 20 | - applicationWillEnterForeground(_:) 21 | * 死锁、死循环、主线程同步执行代码。 22 | * 可以通过 `MXCrashDiagnostic` 来获取诊断报告。 23 | 24 | ### CPU 资源限制 25 | * 在后台高度占用 CPU 资源。 26 | * 如何查看后台占用 CPU 资源: 27 | - Xcode Organizer 28 | - MXCPUExceptionDignostic 29 | * 报告中会包含调用栈,以便定位代码问题。 30 | * 如果确实需要 CPU 高强度的工作,可以将工作代码一到 BGProcessingTask 中,它可以在设备夜间充电时运行同时不会受 CPU 资源限制。 31 | 32 | ### 内存占用过多 33 | * App 运行时占用内存过多。 34 | * App 前台和后台都会有内存限制。 35 | * 可以使用 Instruments 和 Memory Debugger 来定位问题。 36 | * 时刻谨记老的设备,因为越老的设备界限值越低。若 App 内存占用量控制在 200MB 以下会让你的 App 更加安全。 37 | 38 | ### 内存资源限制 39 | 当手机总内存占用过高时,会清除一些 App,以便前台运行的 App 更加流畅。 40 | * 这种情况下被清除,不代表你的 App 有 bug。 41 | * 这是最常见的 App 被终止的方式。 42 | * 如何减少在这种情况下被干掉的概率? 43 | - 在后台运行时,限制使用小于 50 MB的内存。 44 | - 将状态缓存到磁盘。 45 | - 清除内存中的图片资源已释放内存。 46 | - 清除内存中的缓存。 47 | 48 | * 用户不应该感知到 App 在后台被干掉,那如何减少这种感知? 49 | - 保存当前 view controller 的状态。 50 | - 保存多媒体播放状态。 51 | - 保存 textfield 中的内容。 52 | 53 | ### 后台任务超时 54 | * UIApplication.beginBackgroundTask(expirationHandler:)/UIApplication.endBackgroundTask(_:) 用来执行后台任务。 55 | * App 一旦进入后台就会有一个 30s 的计时器,30s 之后App会被系统干掉,所以后台的任务应在 30s 内完成。 56 | * 可以通过 MXBackgroundEixtData 来显示后台挂起的频率。 57 | 58 | * 如何规避后台任务超时? 59 | - 使用 `beginBackgroundTask(withName:expirationHandler:)` 来代替 `beginBackgroundTask(expirationHandler:)`,第一个函数的好处是能够在 App 后台多个任务中将可能没有结束的那个隔离出来。 60 | 61 | ### New MetricKit API 62 | ![image](https://github.com/fengzhihao123/FZHBlog/blob/master/images/new_metrickit_api.png) 63 | ### 总结 64 | * 通过减少 App 对 CPU 和内存的使用,来降低 App 被系统干掉的风险。 65 | * 即使 App 占用内存降到 50MB 一下,依然可能会被系统干掉。 66 | * App 进入后台执行任务时不应超过 30s。 67 | * 保存 App 进入后台时的各种状态以提供用户体验。 68 | -------------------------------------------------------------------------------- /images/Array-capacity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengzhihao123/FZHBlog/23c8cc51470ad7981dc0154ce7bddac332513ba1/images/Array-capacity.png -------------------------------------------------------------------------------- /images/c-swift-map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengzhihao123/FZHBlog/23c8cc51470ad7981dc0154ce7bddac332513ba1/images/c-swift-map.png -------------------------------------------------------------------------------- /images/embrace_swift_type_inference_after.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengzhihao123/FZHBlog/23c8cc51470ad7981dc0154ce7bddac332513ba1/images/embrace_swift_type_inference_after.png -------------------------------------------------------------------------------- /images/embrace_swift_type_inference_before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengzhihao123/FZHBlog/23c8cc51470ad7981dc0154ce7bddac332513ba1/images/embrace_swift_type_inference_before.png -------------------------------------------------------------------------------- /images/new_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengzhihao123/FZHBlog/23c8cc51470ad7981dc0154ce7bddac332513ba1/images/new_api.png -------------------------------------------------------------------------------- /images/new_metrickit_api.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fengzhihao123/FZHBlog/23c8cc51470ad7981dc0154ce7bddac332513ba1/images/new_metrickit_api.png --------------------------------------------------------------------------------