├── LICENSE ├── README.md ├── SiwftUI-Lab系列文章翻译 ├── 关于WWDC的10个代码段.md ├── 探究View树 part-1 PreferenceKey.md ├── 探究View树 part-2 AnchorPreferences.md ├── 探究View树 part-3 Nested Views.md └── 让GeometryReader来解决吧.md ├── SwiftDemo ├── .DS_Store ├── AdvanceSwiftAllDemo.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ └── guanghuiliao.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ └── guanghuiliao.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── AdvanceSwiftAllDemo │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── C2P1.swift │ ├── C2P2.swift │ ├── C2P3_C2P4.swift │ ├── C3P1.swift │ ├── C3P2.swift │ ├── C3P4.swift │ ├── C3P5.swift │ ├── C4P2.swift │ ├── C4P3.swift │ ├── C4P4.swift │ ├── C5P1.swift │ ├── C5P3Struct.swift │ ├── C5P4Copy_on_write.swift │ ├── C5P5_P6ClosureAndMemory.swift │ ├── C6.swift │ ├── C6P1Flexibility.swift │ ├── Info.plist │ └── ViewController.swift ├── pic ├── ProtocolInternals01.jpeg ├── String_words.JPG └── tips_11.png ├── swift新特性 ├── SwiftUI中的@ViewBuilder.md ├── Swift编译加速的Tips.md └── 通过@propertyWrapper让你的代码变的更简洁.md ├── 关于swift的一些心得和建议★★★★★.md ├── 第一章:介绍 └── 第一章:介绍.md ├── 第七章:字符串 ├── 7.1 不再固定宽度.md ├── 7.2 字符串和集合.md ├── 7.3 简单的正则表达式匹配器。 7.4 ExpressibleByStringLiteral.md ├── 7.5 String的内部结构.md ├── 7.6 编码单元的表示方式.md ├── 7.7 CustomStringConvertible 和 CustomDebugStringConvertible.md ├── 7.8 文本输出流.md └── 7.9 字符串的性能.md ├── 第三章:集合类型协议 ├── 3.1 序列.md ├── 3.2集合类型.md ├── 3.3索引.md ├── 3.4切片.md └── 3.5专门的集合类型.md ├── 第九章:泛型 ├── 9.1 重载 Overloading.md ├── 9.2 对集合采用泛型操作 Operating Generically on Collections.md ├── 9.3 使用泛型进行代码设计Designing with Generics.md └── 9.4 泛型的工作方式(How Generics Work) .md ├── 第二章:内建集合类型 ├── 2.1 数组.md ├── 2.2 字典.md └── 2.3 set 2.4 Range .md ├── 第五章:结构体和类 ├── 5.1_2值类型_可变性.md ├── 5.3 结构体.md ├── 5.4 写时复制.md ├── 5.5_6 闭包和可变性_内存.md └── 5.7_8 闭包和内存.md ├── 第八章:错误处理 ├── 8.1 result类型.md ├── 8.2 抛出和捕获.md ├── 8.3带有类型的错误.md ├── 8.4 将错误桥接到Objective-C.md ├── 8.5 错误和函数参数.md ├── 8.6 defer语法可以让代码更简洁 Clearing Up Using defer.md ├── 8.7 错误和可选值 Error and Optionals.md ├── 8.8 错误链.md └── 8.9 高阶函数和错误.md ├── 第六章:函数 ├── 6. 函数(总体介绍).md ├── 6.1 函数的灵活性.md ├── 6.2 局部函数和变量捕获.md ├── 6.3 函数作为代理 function as delegate.md ├── 6.4 inout参数和可变方法(inout parameter and mutating function).md ├── 6.5 计算属性和下标(computed property and subscript).md └── 6.6 自动闭包 6.7 总结.md ├── 第十一章:互用性 ├── 11.1 实践封装 CommonMark.md ├── 11.2 低层级类型概览 AnOverviewofLow-LevelTypes.md └── 11.3 函数指针 FunctionPointers .md ├── 第十章:协议 ├── 10.1 面向协议编程 Overload Resolution for Free Functions .md ├── 10.2 协议的两种类型 TwoTypesofProtocols.md ├── 10.3 带有 Self 的协议 Protocols with Self Requirements.md └── 10.4 协议内幕 Protocol Internals.md └── 第四章:可选值 ├── 4.1_3 序列_魔法数问题_可选值概览.md ├── 4.4 强制解包的时机.md ├── 4.5 多灾多难的隐式可选值.md └── 4.6 隐式解包可选值.md /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Guanghui Liao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SiwftUI-Lab系列文章翻译/关于WWDC的10个代码段.md: -------------------------------------------------------------------------------- 1 | # 关于WWDC的10个代码段 2 | 3 | 文章源地址:[https://medium.com/swlh/10-code-snippets-from-wwdc20-5dba158e2903](https://medium.com/swlh/10-code-snippets-from-wwdc20-5dba158e2903) 4 | 5 | 作者: Francesco Marisaldi 6 | 7 | 翻译: Liaoworking 8 | 9 | ##### WWDC2020带给了我们很多新特性和宣布了很多令人激动的消息。这里有10个代码段开始在下一个iOS版本里面支持。每个都不超过5行。 10 | 11 | ###1.SKOverlay 12 | [SKOverlay官方文档](https://developer.apple.com/documentation/storekit/skoverlay) 13 | 第一个可以使我们在其他的app中显示一个浮层,来快速的下载应用。你可以设置位置和代理,你可以通过代理监听到出现,消失和处理对应的错误。 14 | 15 | guard let scene = view.window?.windowScene else { return } 16 | let config = SKOverlay.AppConfiguration(appIdentifier: "your-app-id", position: .bottom) 17 | let overlay = SKOverlay(configuration: config) 18 | overlay.present(in: scene) 19 | 20 | 它和 ```SKStoreProductViewController```的不同之处是它只是一个浮层而不是全屏幕展示。专为app clips设计。 21 | 用```SKOverlay.AppClipConfiguration```来设置要响应的app和位置,图下图所示。 22 | 23 | 24 | ![image](https://upload-images.jianshu.io/upload_images/1724449-9e6d42fa6ea8eca7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 25 | 26 | ### 2.Configurations 27 | 28 | Configurations是一个用于设置视图和Cell样式的全新API。很灵活,因为它可以用在任何的UIView上,包括collectionView和tableView的cell,使用简单。 29 | 下面就是一个```UIListContentConfiguration```的使用例子: 30 | 31 | var config = UIListContentConfiguration.subtitleCell() 32 | config.image = UIImage(systemName:"tortoise") 33 | config.text = "Hello world!" 34 | config.secondaryText = "WWDC20" 35 | let list = UIListContentView(configuration: config) 36 | 37 | List content的设置有很多默认的设置,包括状态,内容和背景设置,此外,它替代了 UITableViewCell废弃的"textLabel","detailTextLabel","imageView"的属性,[具体文档](https://developer.apple.com/documentation/uikit/views_and_controls/configurations) 38 | 39 | ### 3. CollectionView 新增的 Lists 40 | 41 | 从iOS 14开始,collectionView就可以设置类似于tableView的列表样式(这意味着tableView的时代要到了尽头),示例代码如下: 42 | 43 | let config = UICollectionLayoutListConfiguration(appearance: .insetGrouped) 44 | let layout = UICollectionViewCompositionalLayout.list(using: config) 45 | 46 | 列表会有不同的样式,而且会有滑动手势,分割线,accessories,WWDC的[Lists in UICollectionView](https://developer.apple.com/videos/play/wwdc2020/10026/)会告诉你更多细节。 47 | 48 | ### 4.定位精确度的改变。 49 | Core Location框架这次也迎来了一些改变。可以允许用户选择分享给app的位置精确度的高或者低。 50 | 如果你需要高精读的定位,而用户分享给你的是低精度的怎么办?你可以用下面的代码来解决你的问题: 51 | 52 | let manager = CLLocationManager() 53 | manager.requestTemporaryFullAccuracyAuthorization(withPurposeKey: "YOUR-PURPOSE-KEY") { (error) in 54 | // Your code 55 | } 56 | 57 | 暂时性的高精度只对运行中的进程可以,你必须要通过在info.plist中添加```NSLocationTemporaryUsageDescriptionDictionary```key及对应的描述才可以。 如果有更多的需求,或者你感兴趣的话,可以通过WWDC中的[What’s new in location](https://developer.apple.com/wwdc20/10660)和[Design for location privacy](https://developer.apple.com/wwdc20/10162)两个session来了解。 58 | 59 | ### 5.行为追踪的授权 60 | 今年苹果对用户发隐私有很大的关注(其实最近几年年年都是...),不仅是定位和浏览器,而且应用的数据也会有限制。如果你获取设备的IDFA或者其他的敏感信息来追踪用户行为。你现在需要使用新的```AppTrackingTrasparency```框架。 61 | 62 | ATTrackingManager.requestTrackingAuthorization { (status) in 63 | // your code 64 | } 65 | // To know current status 66 | ATTrackingManager.trackingAuthorizationStatus 67 | 68 | 需要你在info.plist中去添加```NSUserTrackingUsageDescription``` key和对应的授权描述。用户可以在对于设置授权弹框不弹出。这样手机中所有app的这个授权弹框都不会弹出。[Build trust through better privacy](https://developer.apple.com/videos/play/wwdc2020/10676/) 这个session讲了更多相关的细节。 69 | 70 | 71 | ### 6.初始化UIControls可以有事件回调啦 72 | 73 | UIcontrols可以通过闭包来传递事件了,就不需要之前的selectors来绑定方法。 74 | 如下: 75 | 76 | let action = UIAction(title: "") { _ in print("Tapped!") } 77 | let button = UIButton(frame: .zero, primaryAction: action) 78 | 79 | 80 | ### 7. UIBarButtonItem触发菜单栏 81 | UIBarButtonItem点击可以触发显示菜单栏了。 82 | 苹果的用户交互指导建议多使用这样的方式。 83 | 使用代码如下 84 | 85 | 86 | let newFolder = UIAction(title: "New Folder", image: UIImage(systemName: "folder.badge.plus")) { _ in print("NewFolder")} 87 | let edit = UIAction(title: "Edit", image: UIImage(systemName: "pencil.circle")) { _ in print("Edit") } 88 | let menu = UIMenu(title: "", children: [newFolder, edit]) 89 | navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "ellipsis.circle"), menu: menu) 90 | 91 | 92 | ![image](https://upload-images.jianshu.io/upload_images/1724449-06bccec80525e91e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 93 | 94 | ### 8.UIColorPickerViewController 颜色选择器 95 | 96 | 类似于图片选择器,其代理方法包括```colorPickerViewControllerDidFinish(_:)```和```colorPickerViewControllerDidSelectColor(_:)``` 97 | 98 | 使用如下: 99 | 100 | let colorPicker = UIColorPickerViewController() 101 | colorPicker.delegate = self 102 | colorPicker.selectedColor = .orange 103 | present(colorPicker, animated: true, completion: nil) 104 | 105 | 106 | ### 9.UIPageControl and UIDatePicker新api 107 | 108 | 分页小点点可以设置图片来作为page indicators。日期选择器有全新的UI,有弹出式菜单显示和```.inline```样式 109 | 110 | let pageControl = UIPageControl() 111 | pageControl.preferredIndicatorImage = UIImage(systemName:"tortoise") 112 | pageControl.setIndicatorImage(UIImage(systemName:"hare"), forPage:2) 113 | 114 | let datePicker = UIDatePicker(frame: .zero) 115 | datePicker.preferredDatePickerStyle = .inline 116 | 117 | ![image](https://upload-images.jianshu.io/upload_images/1724449-65e7ec3e2fc549aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 118 | 119 | ### 10.新增针对于Mac的userInterfaceIdiom 120 | 121 | 之前在设备判断```UIDevice.current.userInterfaceIdiom```的时候只有```.iPhone```, ```.iPad```两个选项,在xcode12中新添加了```.mac``` 选项 122 | 无疑对多端开发更加友好。 123 | 124 | 125 | -------------------------------------------------------------------------------- /SiwftUI-Lab系列文章翻译/探究View树 part-1 PreferenceKey.md: -------------------------------------------------------------------------------- 1 | 文章源地址:[https://swiftui-lab.com/geometryreader-to-the-rescue/](https://swiftui-lab.com/geometryreader-to-the-rescue/) 2 | 3 | 作者: Javier 4 | 5 | 翻译: Liaoworking 6 | 7 | # 探究View树 part-1 PreferenceKey 8 | 9 | #### 在SwiftUI中我们一般不用关心子级视图内部发生了什么。不同的View各自管各自内部的事情。但总是会遇到一些特殊的需求。比较惨的是[文档](https://developer.apple.com/documentation/swiftui/view/3278633-preference)都讲的比较粗略。 探究View树的三篇文章会做个补充。我们将要去了解 **PreferenceKey** 的协议和相关的修改器(```modifier```):如 10 | **.preference(), 11 | .transformPreference(), 12 | .anchorPreference(), 13 | .transformAnchorPreference(), 14 | .onPreferenceChange(), 15 | .backgroundPreferenceValue() 16 | .overlayPreferenceValue().** 17 | 有很多,那我们开始吧~ 18 | 19 | SwiftUI有一个让我们去给View添加很多属性的机制。这些属性我们叫做 **偏好**(Preferences) 。 它们可以轻松的沿视图依次调用下去,甚至无论怎么修改偏好,添加的回调都会不受影响的执行。 20 | 21 | 有没有想过navigationView是如何通过 **.navigationBarTitle()** 来获取title。请注意 .navigationBarTitle() 并没有直接修改NavigationView。而是在沿着View的层级去调用。那么它是怎么做到的呢? 可能你已经猜到了。其实是用了偏好。在2019WWDC的SwiftUI专栏里有一个很简短的介绍。大概只有20秒。感兴趣的话可以查看[Session 216 (SwiftUI Essentials)](https://developer.apple.com/videos/play/wwdc2019/216/)直接跳到52:35。 22 | 23 | 我们已经找到有一些特殊的偏好 叫"anchored preferences(锚定偏好)", 可以利用它们来方便的检索子级View的所有几何学数据。在下中会详细介绍锚定偏好(anchored preferences) 24 | 25 | 26 | ### 独立的Views 27 | 28 | 我们将会用很短的时间去了解 **PreferenceKey** ,为了更好的了解今天的话题,我们先用一个没有使用偏好的例子开始。在例子中,先创建一个显示月份名的View。当月份标签被点击的时候,会在月份标签上面慢慢的显示一个边框(从之前选中的月份标签移除)。 29 | ![image](https://swiftui-lab.com/wp-content/uploads/2019/06/blog-tree-animation-1.gif) 30 | 例1 31 | 32 | 代码很简单,先创建我们的ContentView: 33 | 34 | import SwiftUI 35 | 36 | struct EasyExample : View { 37 | @State private var activeIdx: Int = 0 38 | 39 | var body: some View { 40 | VStack { 41 | Spacer() 42 | 43 | HStack { 44 | MonthView(activeMonth: $activeIdx, label: "January", idx: 0) 45 | MonthView(activeMonth: $activeIdx, label: "February", idx: 1) 46 | MonthView(activeMonth: $activeIdx, label: "March", idx: 2) 47 | MonthView(activeMonth: $activeIdx, label: "April", idx: 3) 48 | } 49 | 50 | Spacer() 51 | 52 | HStack { 53 | MonthView(activeMonth: $activeIdx, label: "May", idx: 4) 54 | MonthView(activeMonth: $activeIdx, label: "June", idx: 5) 55 | MonthView(activeMonth: $activeIdx, label: "July", idx: 6) 56 | MonthView(activeMonth: $activeIdx, label: "August", idx: 7) 57 | } 58 | 59 | Spacer() 60 | 61 | HStack { 62 | MonthView(activeMonth: $activeIdx, label: "September", idx: 8) 63 | MonthView(activeMonth: $activeIdx, label: "October", idx: 9) 64 | MonthView(activeMonth: $activeIdx, label: "November", idx: 10) 65 | MonthView(activeMonth: $activeIdx, label: "December", idx: 11) 66 | } 67 | 68 | Spacer() 69 | } 70 | } 71 | } 72 | 73 | 74 | 和自定义views: 75 | 76 | struct MonthView: View { 77 | @Binding var activeMonth: Int 78 | let label: String 79 | let idx: Int 80 | 81 | var body: some View { 82 | Text(label) 83 | .padding(10) 84 | .onTapGesture { self.activeMonth = self.idx } 85 | .background(MonthBorder(show: activeMonth == idx)) 86 | } 87 | } 88 | 89 | struct MonthBorder: View { 90 | let show: Bool 91 | 92 | var body: some View { 93 | RoundedRectangle(cornerRadius: 15) 94 | .stroke(lineWidth: 3.0).foregroundColor(show ? Color.red : Color.clear) 95 | .animation(.easeInOut(duration: 0.6)) 96 | } 97 | } 98 | 99 | 代码逻辑也很简单,当月份标签被点击,改变 ```@State``` 为最新点击的月份标签的序号。 而且每个月份边框的颜色都由自己的变量来控制。 如果月份标签被选中,边框会被设置成红色,否则边框就会变透明。这个例子很简答,每个View绘制自己的边框。 100 | 101 | --- 102 | 103 | ### 相互协作的Views 104 | 下面难度再升级一些,我们想让边框从一个月份移动到另外一个。 105 | ![image](https://swiftui-lab.com/wp-content/uploads/2019/06/blog-tree-animation-2.gif) 106 | 例子2 107 | 108 | 你可以先想想如何去实现,不像之前有12个边框,现在只有一个边框,你需要动画改变边框的位置和大小。 109 | 110 | 例子2中,边框并不是月份的一部分,你需要创建一个单独的边框View,并相应的改变位置和大小,这意味着必须有一种方式去跟踪每个月份的大小和位置。 111 | 112 | 如果你看过我上一批文章([GeometryReader to the Rescue](https://swiftui-lab.com/geometryreader-to-the-rescue/)), 113 | 你就已经有一种方式去解决这个问题了,如果你不知道GeometryReader是怎么工作的,可以先看看这篇文章。 114 | 115 | 解决这个问题的一种方式就是: 每一个月份标签都通过GeometryReader去获得自身的大小和位置。每个月份标签依次更新父级视图中的存放位置的数组(通过 **@Binding** )。 一旦父级视图找到了每一个子视图的位置和大小,边框就可以很容易的替换了。这个方案还不错,但子级视图修改数组的时候可能会产生问题。 116 | 117 | 对于某些布局,如果在构建视图的时候,修改其某个变量,其父级视图也会受到影响,反过来子级视图也会受到影响。这使我们正在构建的视图失效,有时可能需要再重新开始构建视图。 还有时候会变成一个循环。好的是SwiftUI视乎可以检测到这种情况,也不会产生崩溃。它会给你一个运行时的警告: **Modifying state during view update(当视图更新的时候修改视图)**. 快速修复这个问题的方法是延迟变量的改变,直到视图的更新完成: 118 | 119 | DispatchQueue.main.async { 120 | self.rects[k] = rect 121 | } 122 | 123 | 不过这好像有点取巧(hack), 虽然这起作用了,但只是一个暂时的解决方案。不确定以后会不会起作用。 有点对框架底层的原理下赌注的意思了。幸运的是 PreferenceKey 可以解决。 124 | 125 | 126 | ### PreferenceKey的介绍 127 | 128 | SwiftUI 提供给我们一个修改器让我们添加一些数据到某个具体的视图。我们可以通过顶级视图(ancestor view)查询这些数据。并且有多种方式去读取PreferenceKey。这取决于你的目的是怎样的。无论怎样,偏好似乎就是我们想要的,那我们先试试来解决我们的问题。 129 | 130 | 我们可以通过下面的例子来知道通过preferences来暴露哪些信息。 131 | 132 | 1.去标记一些view,这里我们通过Int值0..11去标记,其实你可以用任何值都可以标记的。 133 | 134 | 2.获取文本框的CGRect. 135 | 136 | 我们先命名一个遵守 **Equatable** 协议的MyTextPreferenceData的结构体。 137 | 138 | struct MyTextPreferenceData: Equatable { 139 | let viewIdx: Int 140 | let rect: CGRect 141 | } 142 | 143 | 然后我们定义一个遵循 **PreferenceKey** 的结构体MyTextPreferenceKey。 144 | 145 | struct MyTextPreferenceKey: PreferenceKey { 146 | typealias Value = [MyTextPreferenceData] 147 | 148 | static var defaultValue: [MyTextPreferenceData] = [] 149 | 150 | static func reduce(value: inout [MyTextPreferenceData], nextValue: () -> [MyTextPreferenceData]) { 151 | value.append(contentsOf: nextValue()) 152 | } 153 | } 154 | 155 | 我强烈建议你阅读一些PreferenceKey的文档,遵守协议后你必须要实现如下: 156 | 157 | * **value** 我们想要通过PreferenceKey获得什么类型的一个别名,例子中我们用的是[MyTextPreferenceData]数组。 158 | * **defaultValue** 没有显式设置首选项时,SwiftUI会用这个默认值。 159 | * **reduce** 用来覆盖在视图树中找到的所有键值对,是一个静态函数。通常你可以用来累加接收到的所有值。在我们的例子中,当SwiftUI遍历视图树时,会把所有preference键值对存储在一个数组中。下面我们会讲。你应该清楚 **值是按照视图树的顺序给reduce函数的** 我们会在另外一个例子中讨论。 160 | 161 | 我们现在有了 PreferenceKey 了,开始对之前的代码就行修改。 162 | 163 | 先修改MonthView, 通过GeometryReader来获取文字的大小和位置,这些值需要转换一下坐标系,才能绘制出正确的边框。视图可以通过修改器来命名它们的空间坐标系 ``.coordinateSpace(name: "name")``。 一旦我们转换了rect,我们也要相应的设置preference 164 | 165 | struct MonthView: View { 166 | @Binding var activeMonth: Int 167 | let label: String 168 | let idx: Int 169 | 170 | var body: some View { 171 | Text(label) 172 | .padding(10) 173 | .background(MyPreferenceViewSetter(idx: idx)).onTapGesture { self.activeMonth = self.idx } 174 | } 175 | } 176 | 177 | struct MyPreferenceViewSetter: View { 178 | let idx: Int 179 | 180 | var body: some View { 181 | GeometryReader { geometry in 182 | Rectangle() 183 | .fill(Color.clear) 184 | .preference(key: MyTextPreferenceKey.self, 185 | value: [MyTextPreferenceData(viewIdx: self.idx, rect: geometry.frame(in: .named("myZstack")))]) 186 | } 187 | } 188 | } 189 | 190 | 然后,我们创建一个单独的边框视图,该视图将更改其偏移量和frame以匹配与最后点击的视图相对应的矩形: 191 | 192 | RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green) 193 | .frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height) 194 | .offset(x: rects[activeIdx].minX, y: rects[activeIdx].minY) 195 | .animation(.easeInOut(duration: 1.0)) 196 | 197 | 最后,我们只要保证当preferences改变的时候,我们相应的关系rect数组。 例如当设备旋转,或者window的大小改变, 下面的代码都会被调用: 198 | 199 | .onPreferenceChange(MyTextPreferenceKey.self) { preferences in 200 | for p in preferences { 201 | self.rects[p.viewIdx] = p.rect 202 | } 203 | } 204 | 205 | 下面是完整的代码: 206 | 207 | import SwiftUI 208 | 209 | struct MyTextPreferenceKey: PreferenceKey { 210 | typealias Value = [MyTextPreferenceData] 211 | 212 | static var defaultValue: [MyTextPreferenceData] = [] 213 | 214 | static func reduce(value: inout [MyTextPreferenceData], nextValue: () -> [MyTextPreferenceData]) { 215 | value.append(contentsOf: nextValue()) 216 | } 217 | } 218 | 219 | struct MyTextPreferenceData: Equatable { 220 | let viewIdx: Int 221 | let rect: CGRect 222 | } 223 | 224 | struct ContentView : View { 225 | 226 | @State private var activeIdx: Int = 0 227 | @State private var rects: [CGRect] = Array(repeating: CGRect(), count: 12) 228 | 229 | var body: some View { 230 | ZStack(alignment: .topLeading) { 231 | RoundedRectangle(cornerRadius: 15).stroke(lineWidth: 3.0).foregroundColor(Color.green) 232 | .frame(width: rects[activeIdx].size.width, height: rects[activeIdx].size.height) 233 | .offset(x: rects[activeIdx].minX, y: rects[activeIdx].minY) 234 | .animation(.easeInOut(duration: 1.0)) 235 | 236 | VStack { 237 | Spacer() 238 | 239 | HStack { 240 | MonthView(activeMonth: $activeIdx, label: "January", idx: 0) 241 | MonthView(activeMonth: $activeIdx, label: "February", idx: 1) 242 | MonthView(activeMonth: $activeIdx, label: "March", idx: 2) 243 | MonthView(activeMonth: $activeIdx, label: "April", idx: 3) 244 | } 245 | 246 | Spacer() 247 | 248 | HStack { 249 | MonthView(activeMonth: $activeIdx, label: "May", idx: 4) 250 | MonthView(activeMonth: $activeIdx, label: "June", idx: 5) 251 | MonthView(activeMonth: $activeIdx, label: "July", idx: 6) 252 | MonthView(activeMonth: $activeIdx, label: "August", idx: 7) 253 | } 254 | 255 | Spacer() 256 | 257 | HStack { 258 | MonthView(activeMonth: $activeIdx, label: "September", idx: 8) 259 | MonthView(activeMonth: $activeIdx, label: "October", idx: 9) 260 | MonthView(activeMonth: $activeIdx, label: "November", idx: 10) 261 | MonthView(activeMonth: $activeIdx, label: "December", idx: 11) 262 | } 263 | 264 | Spacer() 265 | }.onPreferenceChange(MyTextPreferenceKey.self) { preferences in 266 | for p in preferences { 267 | self.rects[p.viewIdx] = p.rect 268 | } 269 | } 270 | }.coordinateSpace(name: "myZstack") 271 | } 272 | } 273 | 274 | struct MonthView: View { 275 | @Binding var activeMonth: Int 276 | let label: String 277 | let idx: Int 278 | 279 | var body: some View { 280 | Text(label) 281 | .padding(10) 282 | .background(MyPreferenceViewSetter(idx: idx)).onTapGesture { self.activeMonth = self.idx } 283 | } 284 | } 285 | 286 | struct MyPreferenceViewSetter: View { 287 | let idx: Int 288 | 289 | var body: some View { 290 | GeometryReader { geometry in 291 | Rectangle() 292 | .fill(Color.clear) 293 | .preference(key: MyTextPreferenceKey.self, 294 | value: [MyTextPreferenceData(viewIdx: self.idx, rect: geometry.frame(in: .named("myZstack")))]) 295 | } 296 | } 297 | } 298 | 299 | 300 | ### 明智地使用Preferences(首选项) 301 | 当我们使用preferences,可能会使用子级视图的几何信息来布局它们的一个顶层视图(ancestors),如果是这样的话,你应该注意。 如果顶层视图影响了子级视图的布局,反过来子级视图也会影响顶层视图,就会陷入一个递归循环中。 302 | 303 | 可能有时候程序会卡死,或者屏幕会闪动来持续的重新绘制。或者CPU会达到一个峰值,这些都会暗示你错误的使用了preferences。 304 | 305 | 例如你在VStack中有两个视图,上面的视图高度依据下面视图的y值。 可能就会给你带来循环。 306 | 307 | 为了解决这个问题,用一些布局工具使得顶层视图不要影响子级视图,一些好的方案就是: **ZStack, .overlay(), .background()** 308 | 或者几何影响(geometry effects). 309 | 我们将在即将发布的文章中去讨论 **几何影响** (GeometryEffect) 310 | 311 | ### 下一步是什么 312 | 这篇文章中我们通过GeometryReader来“窃取”了月份标签中的几何信息,然而我们可以通过锚定的偏好(Anchor Preferences)来更好的实现它。 在下面的[文章中](https://swiftui-lab.com/communicating-with-the-view-tree-part-2/)我们将继续学习它。而且我们将深入究竟SwiftUI是怎样遍历树的。其实也可以不通过``.onPreferenceChange()`` 来使用preferences。下篇文章中也有讲解。 313 | 314 | 当你一开始去使用preferences的时候,可能你的代码又乱又难阅读。我觉得你应该在View的extension中封装好preferences,我之前写过的一篇文章有讲过怎么去做。你可以查看[让你代码变的更好的View extension](https://swiftui-lab.com/view-extensions-for-better-code-readability/)。 315 | 316 | -------------------------------------------------------------------------------- /SiwftUI-Lab系列文章翻译/探究View树 part-2 AnchorPreferences.md: -------------------------------------------------------------------------------- 1 | 文章源地址:[https://swiftui-lab.com/communicating-with-the-view-tree-part-2/](https://swiftui-lab.com/communicating-with-the-view-tree-part-2/) 2 | 3 | 作者: Javier 4 | 5 | 翻译: Liaoworking 6 | 7 | # 探究View树 part-2 AnchorPreferences(锚定偏好) 8 | 9 | 在[第一部分](https://swiftui-lab.com/communicating-with-the-view-tree-part-1/)的文章中,我们介绍了偏好(preferences)的使用,它可以很有用的把信息向上传递(从子级视图传到祖级视图)。通过定义PreferenceKey的关联类型,可以获取到所有想要的数据。 10 | 11 | 在第二部分,我们将介绍 **锚定偏好** (Anchor Preferences 写的时候国内还没有对应的名词翻译,这里凭个人理解硬翻),在写这篇文章的时候还没有找到任何相关文档、博客或者文章来介绍如何使用这些很难理解的工具类。那就让我来介绍一下吧。 12 | 13 | 锚定偏好看字面意思并不好理解。但只要我们掌握了,就很难忘却了。还是通过第一部分里面的例子来讲。这里不会用到之前的空间坐标系来解决。我们将用其他方法来替换```.onPreferenceChange()```。 14 | 15 | 这里在简单提及例子里所做的事情:点击不同的月份的时候边框会从一个月份移动到另外一个月份上面,并带有动画效果。 16 | 17 | ![image](https://swiftui-lab.com/wp-content/uploads/2019/06/blog-tree-animation-2.gif) 18 | 19 | ### 锚定偏好 20 | 21 | 首先迎来的是**Anchor< T >**, 这是存放泛型T的不透明的类型。 这里的T可以是CGRect或者是CGPoint。我们一般使用Anchor来获得视图的大小,用Anchor来获取例top, topLeading, topTrailing, center, trailing, bottom, bottomLeading, bottomTrailing, leading属性。 22 | 23 | 因为这是不透明类型,所以我们不能单独使用它。还记得之前的文章[GeometryReader to the Rescue](https://swiftui-lab.com/geometryreader-to-the-rescue/)文章中GeometryProxy的通过下标getter方法么。现在你应该知道了,当使用Anchor的值作为 geometry proxy 的索引时,你就可以获得CGRect和CGPoint的值。此外,你还可以获取它们在GeometryReader视图中的空间坐标。 24 | 25 | 我们先通过修改PreferenceKey处理的数据开始吧,在这个例子中我们把CGRect替换成了Anchor 26 | 27 | struct MyTextPreferenceData { 28 | let viewIdx: Int 29 | let bounds: Anchor 30 | } 31 | 32 | 我们的PreferenceKey 保持不变 33 | 34 | struct MyTextPreferenceKey: PreferenceKey { 35 | typealias Value = [MyTextPreferenceData] 36 | 37 | static var defaultValue: [MyTextPreferenceData] = [] 38 | 39 | static func reduce(value: inout [MyTextPreferenceData], nextValue: () -> [MyTextPreferenceData]) { 40 | value.append(contentsOf: nextValue()) 41 | } 42 | } 43 | 44 | MonthView的代码就变的更简洁了,把MonthView的```.preference()```替换成```.anchorPreference()```。和其他方法不同,这里我们可以指定一个值(例子里面指定的是.bounds)。 那么我们transform这闭包中的Anchor就是修改视图的bounds。 和处理普通的偏好相似,我们用{$0}来创建MyTextPreferenceData值。这样的话我们就不需要在.background() 中使用GeometryReader来获取text View的bounds了。 45 | 46 | 代码如下: 47 | 48 | struct MonthView: View { 49 | @Binding var activeMonth: Int 50 | let label: String 51 | let idx: Int 52 | 53 | var body: some View { 54 | Text(label) 55 | .padding(10) 56 | .anchorPreference(key: MyTextPreferenceKey.self, value: .bounds, transform: { [MyTextPreferenceData(viewIdx: self.idx, bounds: $0)] }) 57 | .onTapGesture { self.activeMonth = self.idx } 58 | } 59 | } 60 | 61 | 最后,更新我们的ContentView,这里会有一些变化。对初学者来说,我们不再使用```.onPreferenceChange()```,而是使用```.backgroundPreferenceValue()```。这是一个类似于```.background()```的修改器。 62 | 但它有一个很大的好处就是: 63 | 我们可以获取到整个视图树的偏好(preference)数组。 64 | 这样的话,我们也可以通过获取到所有的月份视图的Bounds信息来计算出边框应该绘制在哪里。 65 | 66 | ### #warning() 67 | 68 | #### 在Xcode 11 beta5中,苹果悄悄的 用 **Equatable** 移除了 **Anchor** 的一致性。 如果你想要使用 **.onPreferenceChange()** , 你大概能想象到,需要你的preference key的值符合 **Equatable** 协议。幸运的是例子中没有使用到 **.onPreferenceChange()** , 自从Anchor的一致性被弃用之后我就一直希望在 GM版本发布之前恢复。 我提交了一个错误报告(FB6912036), 希望你也能这样。 69 | 70 | 71 | 仍然还有一个地方需要用到GeometryReader,通过它我们可以不用关心空间坐标,也让Anchor的值变的有用。 72 | 73 | 74 | 75 | struct ContentView : View { 76 | 77 | @State private var activeIdx: Int = 0 78 | 79 | var body: some View { 80 | VStack { 81 | Spacer() 82 | 83 | HStack { 84 | MonthView(activeMonth: $activeIdx, label: "January", idx: 0) 85 | MonthView(activeMonth: $activeIdx, label: "February", idx: 1) 86 | MonthView(activeMonth: $activeIdx, label: "March", idx: 2) 87 | MonthView(activeMonth: $activeIdx, label: "April", idx: 3) 88 | } 89 | 90 | Spacer() 91 | 92 | HStack { 93 | MonthView(activeMonth: $activeIdx, label: "May", idx: 4) 94 | MonthView(activeMonth: $activeIdx, label: "June", idx: 5) 95 | MonthView(activeMonth: $activeIdx, label: "July", idx: 6) 96 | MonthView(activeMonth: $activeIdx, label: "August", idx: 7) 97 | } 98 | 99 | Spacer() 100 | 101 | HStack { 102 | MonthView(activeMonth: $activeIdx, label: "September", idx: 8) 103 | MonthView(activeMonth: $activeIdx, label: "October", idx: 9) 104 | MonthView(activeMonth: $activeIdx, label: "November", idx: 10) 105 | MonthView(activeMonth: $activeIdx, label: "December", idx: 11) 106 | } 107 | 108 | Spacer() 109 | }.backgroundPreferenceValue(MyTextPreferenceKey.self) { preferences in 110 | return GeometryReader { geometry in 111 | ZStack { 112 | self.createBorder(geometry, preferences) 113 | }.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) 114 | } 115 | } 116 | } 117 | 118 | func createBorder(_ geometry: GeometryProxy, _ preferences: [MyTextPreferenceData]) -> some View { 119 | 120 | let p = preferences.first(where: { $0.viewIdx == self.activeIdx }) 121 | 122 | let bounds = p != nil ? geometry[p!.bounds] : .zero 123 | 124 | return RoundedRectangle(cornerRadius: 15) 125 | .stroke(lineWidth: 3.0) 126 | .foregroundColor(Color.green) 127 | .frame(width: bounds.size.width, height: bounds.size.height) 128 | .fixedSize() 129 | .offset(x: bounds.minX, y: bounds.minY) 130 | .animation(.easeInOut(duration: 1.0)) 131 | } 132 | } 133 | 134 | 135 | 和```.backgroundPreferenceValue()``` 相对应的是```.overlayPreferenceValue()```, 它们的作用相同,只不过一个是绘制背景,一个是绘制前景。 136 | 137 | ### 单个 PreferenceKey 和 多个锚定偏好 138 | 139 | 我们知道Anchor 的值不止有bounds,还有topLeading, center, bottom等值。可能有的情况下我们需要的不止一个Anchor 的值,然而,调用它并不像调用```.anchorPreference() ``` 一样容易。下面我们举例继续说明。 140 | 我们将使用两个不同的 Anchor,来获取月份标签的bounds, 其中一个左上角的Point 一个是右下角的 Point。而不是用Anchor。 141 | 提醒一下,使用Anchor是对这种特定问题的一个更好的解决方案。然而,我们用CGPoint方案只是为了知道如何获取一个视图的多个锚定偏好。 142 | 143 | 首先修改MyTextPreferenceData来容纳两个极端rect,要设置成可选型, 因为它们不能同时赋值。 144 | 145 | struct MyTextPreferenceData { 146 | let viewIdx: Int 147 | var topLeading: Anchor? = nil 148 | var bottomTrailing: Anchor? = nil 149 | } 150 | 151 | PreferenceKey 保持不变。 152 | 153 | struct MyTextPreferenceKey: PreferenceKey { 154 | typealias Value = [MyTextPreferenceData] 155 | 156 | static var defaultValue: [MyTextPreferenceData] = [] 157 | 158 | static func reduce(value: inout [MyTextPreferenceData], nextValue: () -> [MyTextPreferenceData]) { 159 | value.append(contentsOf: nextValue()) 160 | } 161 | } 162 | 163 | 月份标签没必要设置两个锚定偏好,但是如果我们在同一个视图中多次调用```.anchorPreference()```。 只有最后一次起作用。 相反我们需要调用 ```.anchorPreference()```, 然后再调用```.transformAnchorPreference()```,来补回缺失的信息。 164 | 165 | struct MonthView: View { 166 | @Binding var activeMonth: Int 167 | let label: String 168 | let idx: Int 169 | 170 | var body: some View { 171 | Text(label) 172 | .padding(10) 173 | .anchorPreference(key: MyTextPreferenceKey.self, value: .topLeading, transform: { [MyTextPreferenceData(viewIdx: self.idx, topLeading: $0)] }) 174 | .transformAnchorPreference(key: MyTextPreferenceKey.self, value: .bottomTrailing, transform: { ( value: inout [MyTextPreferenceData], anchor: Anchor) in 175 | value[0].bottomTrailing = anchor 176 | }) 177 | 178 | .onTapGesture { self.activeMonth = self.idx } 179 | } 180 | } 181 | 182 | 183 | 最后,我们相应的更新```.createBorder()```,所以它使用的是两个point来进行的计算,而不是rect. 184 | 185 | struct ContentView : View { 186 | 187 | @State private var activeIdx: Int = 0 188 | 189 | var body: some View { 190 | VStack { 191 | Spacer() 192 | 193 | HStack { 194 | MonthView(activeMonth: $activeIdx, label: "January", idx: 0) 195 | MonthView(activeMonth: $activeIdx, label: "February", idx: 1) 196 | MonthView(activeMonth: $activeIdx, label: "March", idx: 2) 197 | MonthView(activeMonth: $activeIdx, label: "April", idx: 3) 198 | } 199 | 200 | Spacer() 201 | 202 | HStack { 203 | MonthView(activeMonth: $activeIdx, label: "May", idx: 4) 204 | MonthView(activeMonth: $activeIdx, label: "June", idx: 5) 205 | MonthView(activeMonth: $activeIdx, label: "July", idx: 6) 206 | MonthView(activeMonth: $activeIdx, label: "August", idx: 7) 207 | } 208 | 209 | Spacer() 210 | 211 | HStack { 212 | MonthView(activeMonth: $activeIdx, label: "September", idx: 8) 213 | MonthView(activeMonth: $activeIdx, label: "October", idx: 9) 214 | MonthView(activeMonth: $activeIdx, label: "November", idx: 10) 215 | MonthView(activeMonth: $activeIdx, label: "December", idx: 11) 216 | } 217 | 218 | Spacer() 219 | }.backgroundPreferenceValue(MyTextPreferenceKey.self) { preferences in 220 | return GeometryReader { geometry in 221 | ZStack { 222 | self.createBorder(geometry, preferences) 223 | }.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) 224 | } 225 | } 226 | } 227 | 228 | func createBorder(_ geometry: GeometryProxy, _ preferences: [MyTextPreferenceData]) -> some View { 229 | let p = preferences.first(where: { $0.viewIdx == self.activeIdx }) 230 | 231 | let aTopLeading = p?.topLeading 232 | let aBottomTrailing = p?.bottomTrailing 233 | 234 | let topLeading = aTopLeading != nil ? geometry[aTopLeading!] : .zero 235 | let bottomTrailing = aBottomTrailing != nil ? geometry[aBottomTrailing!] : .zero 236 | 237 | 238 | return RoundedRectangle(cornerRadius: 15) 239 | .stroke(lineWidth: 3.0) 240 | .foregroundColor(Color.green) 241 | .frame(width: bottomTrailing.x - topLeading.x, height: bottomTrailing.y - topLeading.y) 242 | .fixedSize() 243 | .offset(x: topLeading.x, y: topLeading.y) 244 | .animation(.easeInOut(duration: 1.0)) 245 | } 246 | } 247 | 248 | 249 | ### 嵌套视图 250 | 到目前为止,我们已经在兄弟视图上使用了preferences。但在嵌套视图的使用上我们还有更多的挑战。```.transformAnchorPreference() ``` 就变的很重要了,如果你有嵌套的两个视图,而且两个都设置```.anchorPreference() ```,子级视图的将不会起作用。 为了解决这样个问题,你必须要指定子级视图的anchorPreference和父级视图的transformAnchorPreference。但是别慌, 我们会详细介绍的。 251 | 252 | ### 下一步是什么 253 | 在这一系列的最后一部分,将用一个不同的例子来说明。 我们将会有一个小的示意图。小的示意图将会读取视图树的表单来构造。 我们将会去修改表单的视图。而且会马上生效。它只是对这个表单视图树的preferences改变产生了反馈。 254 | 255 | 这里有个小图来解释: 256 | ![image](https://swiftui-lab.com/wp-content/uploads/2019/06/minimap.gif) 257 | 258 | 我相信这个系列的最后一部分你会来。如果你想要被提醒一下,可以在[Twitter](https://twitter.com/SwiftUILab)上关注我,下次见啦~ 259 | 260 | -------------------------------------------------------------------------------- /SiwftUI-Lab系列文章翻译/探究View树 part-3 Nested Views.md: -------------------------------------------------------------------------------- 1 | 文章源地址:[https://swiftui-lab.com/communicating-with-the-view-tree-part-3/) 2 | 3 | 作者: Javier 4 | 5 | 翻译: Liaoworking 6 | 7 | # 探究View树 part-3 嵌套视图 8 | 9 | ### 处理嵌套视图的偏好(Preferences) 10 | 11 | 在之前的部分我们介绍了SwiftUI的锚定偏好(anchor preferences) , 现在到了最后一部分啦~ 把所有知识点放到一起,继续学习SwiftUI处理嵌套视图的偏好(preferences)。顺便添加一些Anchor的其他用法。就从下面的例子开始吧。 12 | 13 | 我们的目标是创建一个小的示意图来展示一些状态的表格。 14 | ![image](https://swiftui-lab.com/wp-content/uploads/2019/06/minimap.gif) 15 | 16 | 在例子中需要注意: 17 | 18 | * 左边小的示意图是缩小版的右边表格。不同的颜色代表不同的title view, 文本字段和文本字段的容器。 19 | * 随着文字的变多,小示意图也会表现出来。 20 | * 当我们添加新的视图的时候,小示意图也会改变。 21 | * 当表格的frame改变的时候, 小示意图也会改变。 22 | * 文本框的颜色红色代表没有输入,黄色小于3个,绿色大于等于3个。 23 | 24 | --- 25 | 26 | 注意小示意图并不知道任何关于表单的信息,它只是因为视图层级的偏好(preferences)的改变而改变。 27 | 28 | ### 那就开始吧~ 29 | 30 | 因为这里的视图有多种类型,需要来将它们区分,这里我们用一个枚举。 31 | 32 | enum MyViewType: Equatable { 33 | case formContainer // 主容器 34 | case fieldContainer // 包括标签和文本框的容器 35 | case field(Int) //文本框,包括内容长度 36 | case title // 表单标题 37 | case miniMapArea // 小示意图后面的视图 38 | } 39 | 40 | 41 | 然后我们定义一个MyPreferenceData类,用来处理偏好中设置的数据。它有两个属性(vtype 和 bounds),还有一些会使用到的方法: 42 | 43 | struct MyPreferenceData: Identifiable { 44 | let id = UUID() // forEach必须要用的属性 45 | let vtype: MyViewType 46 | let bounds: Anchor 47 | 48 | // 配置小示意图的颜色 49 | func getColor() -> Color { 50 | switch vtype { 51 | case .field(let length): 52 | return length == 0 ? .red : (length < 3 ? .yellow : .green) 53 | case .title: 54 | return .purple 55 | default: 56 | return .gray 57 | } 58 | } 59 | 60 | // 如果当前view必须要显示在小示意图中 返回true 61 | // 只有文本框 文本框容器 标题才显示在小示意图中 62 | func show() -> Bool { 63 | switch vtype { 64 | case .field: 65 | return true 66 | case .title: 67 | return true 68 | case .fieldContainer: 69 | return true 70 | default: 71 | return false 72 | } 73 | } 74 | } 75 | 76 | 定义PreferenceKey 77 | 78 | struct MyPreferenceKey: PreferenceKey { 79 | typealias Value = [MyPreferenceData] 80 | 81 | static var defaultValue: [MyPreferenceData] = [] 82 | 83 | static func reduce(value: inout [MyPreferenceData], nextValue: () -> [MyPreferenceData]) { 84 | value.append(contentsOf: nextValue()) 85 | } 86 | } 87 | 88 | 有趣的地方开始了,我们有很多的区域,每一个区域前面都有一个textLabel,并被一个容器包裹。我们把这些重复的视图封装成MyFormField类。与此同时也相应的设置好偏好。文本框是VStack的一部分。我们需要两个嵌套视图的bounds. 不可以使用```.anchorPreference()```两次,在VStack中调用```anchorPreference() ```会阻止TextField中的调用。可以在VStack中掉用[```.transformAnchorPreference()```](https://developer.apple.com/documentation/swiftui/view/3278673-transformanchorpreference)来添加数据,而不是替换数据。 89 | 90 | // 包含标题、文本框的圆角视图 91 | struct MyFormField: View { 92 | @Binding var fieldValue: String 93 | let label: String 94 | 95 | var body: some View { 96 | VStack(alignment: .leading) { 97 | Text(label) 98 | TextField("", text: $fieldValue) 99 | .textFieldStyle(RoundedBorderTextFieldStyle()) 100 | .anchorPreference(key: MyPreferenceKey.self, value: .bounds) { 101 | return [MyPreferenceData(vtype: .field(self.fieldValue.count), bounds: $0)] 102 | } 103 | } 104 | .padding(15) 105 | .background(RoundedRectangle(cornerRadius: 15).fill(Color(white: 0.9))) 106 | .transformAnchorPreference(key: MyPreferenceKey.self, value: .bounds) { 107 | $0.append(MyPreferenceData(vtype: .fieldContainer, bounds: $1)) 108 | } 109 | } 110 | } 111 | 112 | ContentView把所有的View都放到了一起,这里我们设置了三个偏好,稍后会再小示意图中用到。这三个偏好分别存储的是图表标题的bounds,图表区域和示意图区域。 113 | 114 | struct ContentView : View { 115 | @State private var fieldValues = Array(repeating: "", count: 5) 116 | @State private var length: Float = 360 117 | @State private var twitterFieldPreset = false 118 | 119 | var body: some View { 120 | 121 | VStack { 122 | Spacer() 123 | 124 | HStack(alignment: .center) { 125 | 126 | // 存放小示意图的View 127 | // 我们将获取它的大小、位置来确定小示意图的元素正确显示 128 | Color(white: 0.7) 129 | .frame(width: 200) 130 | .anchorPreference(key: MyPreferenceKey.self, value: .bounds) { 131 | return [MyPreferenceData(vtype: .miniMapArea, bounds: $0)] 132 | } 133 | .padding(.horizontal, 30) 134 | 135 | // 表单容器 136 | VStack(alignment: .leading) { 137 | // 标题 138 | VStack { 139 | Text("Hello \(fieldValues[0]) \(fieldValues[1]) \(fieldValues[2])") 140 | .font(.title).fontWeight(.bold) 141 | .anchorPreference(key: MyPreferenceKey.self, value: .bounds) { 142 | return [MyPreferenceData.init(vtype: .title, bounds: $0)] 143 | } 144 | Divider() 145 | } 146 | 147 | // 开关和滑条 148 | HStack { 149 | Toggle(isOn: $twitterFieldPreset) { Text("") } 150 | 151 | Slider(value: $length, in: 360...540).layoutPriority(1) 152 | }.padding(.bottom, 5) 153 | 154 | // 文本框的第一行 155 | HStack { 156 | MyFormField(fieldValue: $fieldValues[0], label: "First Name") 157 | MyFormField(fieldValue: $fieldValues[1], label: "Middle Name") 158 | MyFormField(fieldValue: $fieldValues[2], label: "Last Name") 159 | }.frame(width: 540) 160 | 161 | // 文本框的第二行 162 | HStack { 163 | MyFormField(fieldValue: $fieldValues[3], label: "Email") 164 | 165 | if twitterFieldPreset { 166 | MyFormField(fieldValue: $fieldValues[4], label: "Twitter") 167 | } 168 | 169 | 170 | }.frame(width: CGFloat(length)) 171 | 172 | }.transformAnchorPreference(key: MyPreferenceKey.self, value: .bounds) { 173 | $0.append(MyPreferenceData(vtype: .formContainer, bounds: $1)) 174 | } 175 | 176 | Spacer() 177 | 178 | } 179 | .overlayPreferenceValue(MyPreferenceKey.self) { preferences in 180 | GeometryReader { geometry in 181 | MiniMap(geometry: geometry, preferences: preferences) 182 | } 183 | } 184 | 185 | Spacer() 186 | }.background(Color(white: 0.8)).edgesIgnoringSafeArea(.all) 187 | } 188 | } 189 | 190 | 191 | 最后,我们的小示意图会遍历所有的偏好,并绘制小示意图中的每一个元素。 192 | 193 | struct MiniMap: View { 194 | let geometry: GeometryProxy 195 | let preferences: [MyPreferenceData] 196 | 197 | var body: some View { 198 | // 获得表单容器的偏好 199 | guard let formContainerAnchor = preferences.first(where: { $0.vtype == .formContainer })?.bounds else { return AnyView(EmptyView()) } 200 | 201 | // 获得小示意图的偏好 202 | guard let miniMapAreaAnchor = preferences.first(where: { $0.vtype == .miniMapArea })?.bounds else { return AnyView(EmptyView()) } 203 | 204 | // 计算表单的数据 用来显示在小示意图中 205 | let factor = geometry[formContainerAnchor].size.width / (geometry[miniMapAreaAnchor].size.width - 10.0) 206 | 207 | // 确定表单的位置 208 | let containerPosition = CGPoint(x: geometry[formContainerAnchor].minX, y: geometry[formContainerAnchor].minY) 209 | 210 | // 确定小示意图的位置 211 | let miniMapPosition = CGPoint(x: geometry[miniMapAreaAnchor].minX, y: geometry[miniMapAreaAnchor].minY) 212 | 213 | // ------------------------------------------------------------------------------------------------- 214 | // iOS 13 Beta 5 正式版发布日志 已知问题: 215 | // 复杂的ForEach view可能会有编译报错 216 | // 解决方案: 抽一个新的View出来 217 | // ------------------------------------------------------------------------------------------------- 218 | // 由于 beta 5编译报错的bug,封装成AnyView. 219 | return AnyView(miniMapView(factor, containerPosition, miniMapPosition)) 220 | } 221 | 222 | func miniMapView(_ factor: CGFloat, _ containerPosition: CGPoint, _ miniMapPosition: CGPoint) -> some View { 223 | ZStack(alignment: .topLeading) { 224 | // 创建小的示意图视图 225 | // 首选项以相反的顺序遍历 226 | // 将被父视图覆盖 227 | ForEach(preferences.reversed()) { pref in 228 | if pref.show() { // 一些不想要显示的视图 229 | self.rectangleView(pref, factor, containerPosition, miniMapPosition) 230 | } 231 | } 232 | }.padding(5) 233 | } 234 | 235 | func rectangleView(_ pref: MyPreferenceData, _ factor: CGFloat, _ containerPosition: CGPoint, _ miniMapPosition: CGPoint) -> some View { 236 | Rectangle() 237 | .fill(pref.getColor()) 238 | .frame(width: self.geometry[pref.bounds].size.width / factor, 239 | height: self.geometry[pref.bounds].size.height / factor) 240 | .offset(x: (self.geometry[pref.bounds].minX - containerPosition.x) / factor + miniMapPosition.x, 241 | y: (self.geometry[pref.bounds].minY - containerPosition.y) / factor + miniMapPosition.y) 242 | } 243 | 244 | } 245 | 246 | ### 关于视图树的一句话 247 | 248 | 现在让我们停下来思考一下:偏好(preference)闭包在嵌套视图中的执行顺序。例如先看看小示意图的实现,你可能会注意到ForEach以相反的顺序运行循环,否则文本框容器会最后才绘制,来覆盖对应的小示意图的文本框。 所以了解偏好的设置就变的很重要了。 249 | 250 | > 首先请注意:并没有关于SwiftUI遍历视图树顺序的文档,PreferenceKey类中的reduce方法声明中提到值是以视图树的顺序排列。但是没有告诉我们这个顺序是什么,我们能确认的是这并不是随机顺序而且每次刷新的时候都保持一致。 251 | > **下面我讲的所有和关于闭包中的运行顺序相关的,都是通过专门的实验弄清楚的。我几乎每个地方都打断点了,都说的通,我也对此很有信心** 252 | 253 | 下面的图表简单的说明了视图层级,为了让视图更容易理解,简单的视图就忽略了,红色的箭头表示``` anchorPreference() ```和```transformAnchorPreference()``` 闭包的执行顺序。注意,只有SwiftUI认为必须的才会调用,并不是所有的闭包都会被调用。例如视图的bounds并没有改变```.anchorPreference()```可能就不会调用。当不确定的时候,打个断点或者打印一下状态来调试一下。 254 | 255 | ![image](https://swiftui-lab.com/wp-content/uploads/2019/07/view-tree.png) 256 | 257 | 如图,SwiftUI遵循下面两个原则: 258 | 259 | * 1.同级的视图的遍历顺序和它们在代码中的出现顺序相同。 260 | * 2.子级视图的闭包执行时机要比父级视图早。 261 | 262 | ### Anchor的其他使用。 263 | 正如我们所看到的,Anchor.Source可以被一下静态变量获得到,如.bounds, .topLeading, .bottom等。我们通常将它传递给anchorPreference() 修改器。然而你可以通过静态变量Anchor.Source创建你自己的Anchor.Source和Anchor.Source,如下: 264 | 265 | let a1 = Anchor.Source.rect(CGRect(x: 0, y: 0, width: 100, height: 50)) 266 | let a2 = Anchor.Source.point(CGPoint(x: 10, y: 30)) 267 | let a3 = Anchor.Source.unitPoint(UnitPoint(x: 10, y: 30)) 268 | 269 | 270 | 你可能会问我们什么时候用这些,你可以把值传递给偏好,如果已存在的静态变量对你都没啥用处,但是当处理弹框的时候用它就会特别的方便。 271 | 272 | .popover(isPresented: $showPopOver, 273 | attachmentAnchor: .rect(Anchor.Source.rect(CGRect(x: 0, y: 0, width: 100, height: 50))), 274 | arrowEdge: .leading) { ... } 275 | 276 | ### 总结: 277 | 恭喜,这一系列总算到最后了,希望你能享受享受这些工具而且用来创作炫酷的app。有着无限的可能性。欢迎评论,或者给我email和Twitter上关注我。 278 | 279 | 欢迎关注来获取更多文章。 280 | 281 | -------------------------------------------------------------------------------- /SiwftUI-Lab系列文章翻译/让GeometryReader来解决吧.md: -------------------------------------------------------------------------------- 1 | # 让GeometryReader来解决吧 2 | 3 | 文章源地址:[https://swiftui-lab.com/geometryreader-to-the-rescue/](https://swiftui-lab.com/geometryreader-to-the-rescue/) 4 | 5 | 作者: Javier 6 | 7 | 翻译: Liaoworking 8 | 9 | ##### 大多数情况下,SwiftUI都会发挥其[神奇的布局](https://swiftui-lab.com/layout-magic/)的特性。但是有时候,我们需要对自定义视图的布局进行更多操作。目前我们有几种工具。第一个需要我们去探索的就是**GeometryReader**。 10 | 11 | #### 父级视图想要什么? 12 | 当我们创建自定义视图时,一般不用担心旁边视图的布局或size。如果你想要创建一个正方形。只要用一个Rectangle,就会以父级想要的size和position去画出一个正方形。 13 | 14 | 在下面的示例中,我们有一个frame为150×100的VStack。上面部分是Text,剩余空间都给了MyRectangle()。如图所示都被蓝色颜色填充: 15 | 16 | struct ContentView : View { 17 | var body: some View { 18 | 19 | VStack { 20 | 21 | Text("Hello There!") 22 | MyRectangle() 23 | 24 | }.frame(width: 150, height: 100).border(Color.black) 25 | 26 | } 27 | } 28 | 29 | struct MyRectangle: View { 30 | var body: some View { 31 | Rectangle().fill(Color.blue) 32 | } 33 | } 34 | 35 | ![image](https://swiftui-lab.com/wp-content/uploads/2019/06/blog-1.png) 36 | 37 | 正如你所看到的,MyRectangle(),不用去设置size,它只有一个任务,就是画矩形。让SwiftUI自己去管理好父级期望的子视图的大小和位置。 这个例子里Vstack就是父级视图。 38 | 39 | ``` 如果你想要知道更多关于父级视图如何确定子视图的位置和大小。我强烈推荐你看看2019WWDC session 237```[Building Custom Views With SwiftUI](https://developer.apple.com/videos/play/wwdc2019/237/) 40 | 41 | 父级视图会自动为子视图找到合适的尺寸和位置。但是如果你想要自定义绘制一个矩形,大小是父级视图的一半。位置位于父级视图右边距里5像素的视图。 42 | 其实也并不复杂,这个时候可以用GeometryReader作为解决方案。 43 | 44 | ### 子视图做了什么? 45 | 先看看Apple官方文档如何介绍的GeometryReader: 46 | 47 | A container view that defines its content as a function of its own size and coordinate space. 48 | 一个容器视图,根据其自身大小和坐标空间定义其内容。 49 | 50 | 这个解释已经算很详细了。 51 | 52 | 那这句话是什么意思呢? 简单来讲GeometryReader就是另外一种view。惊不惊喜? 在SwiftUI中几乎```所有东西```都是View。 在下面的例子中,GeometryReader让你定义了它的content。 但是与其他View 不同。你可以拿到一些你在其他View中拿不到的信息。 53 | 54 | 上面说到想要绘制一个大小是父级视图的一半。位置位于父级视图右边距里5像素的视图。现在我们有了GeometryReader, 这就很简单了 55 | 56 | ![image](https://swiftui-lab.com/wp-content/uploads/2019/06/blog-2.png) 57 | 58 | struct ContentView : View { 59 | var body: some View { 60 | 61 | VStack { 62 | 63 | Text("Hello There!") 64 | MyRectangle() 65 | 66 | }.frame(width: 150, height: 100).border(Color.black) 67 | 68 | } 69 | } 70 | 71 | struct MyRectangle: View { 72 | var body: some View { 73 | GeometryReader { geometry in 74 | Rectangle() 75 | .path(in: CGRect(x: geometry.size.width + 5, 76 | y: 0, 77 | width: geometry.size.width / 2.0, 78 | height: geometry.size.height / 2.0)) 79 | .fill(Color.blue) 80 | 81 | } 82 | } 83 | } 84 | 85 | 86 | ### GeometryProxy 87 | 上面的例子中,闭包中的geometry是一个**GeometryProxy**类的变量。我们可以通过[Inspecting the View Tree(检查视图树结构)](https://swiftui-lab.com/communicating-with-the-view-tree-part-1/)这篇文章去了解更多相关内容。 88 | 89 | 在GeometryProxy类中有两个计算型属性,一个方法,和一个下标取值。 90 | 91 | public var size: CGSize { get } 92 | public var safeAreaInsets: EdgeInsets { get } 93 | public func frame(in coordinateSpace: CoordinateSpace) -> CGRect 94 | public subscript(anchor: Anchor) -> T where T : Equatable { get } 95 | 96 | 97 | **size**属性是父级视图建议的大小 98 | 99 | GeometryProxy 把 **safeAreaInsets**也暴露给了我们。 100 | 101 | **frame**方法暴露给我们了父级视图建议区域的大小位置,可以通过**.local,.global,.named()**来获取不同的坐标空间。 .named() 用来获取一个被命名的坐标空间。我们可以通过这个命名来获取其他view坐标空间。 [Inspecting the View Tree](https://swiftui-lab.com/communicating-with-the-view-tree-part-1/) 这篇文章中有具体的使用方法 102 | 103 | 最后,我们可以通过**下标取值**来获取一个**锚点**。这个是GeometryReader的一个炫酷的功能。但也比较繁琐,我将在[second part of Inspecting the View Tree](https://swiftui-lab.com/communicating-with-the-view-tree-part-2/)有讲解。看完后就会有一个了解。可以获取视图树中任何子级视图的size和x,y. 是不是很强大,那你得先学啊。 104 | 105 | ### 吸收另外一个View的Geometry 106 | 107 | GeometryReader 功能已经相当强大,但它如果与 **.background()**或 **.overlay()**的modifier相结合使用,功能就会更强大。 108 | 109 | 在我见过的教程中 background 都是以下面这种形式使用的:``Text("hello").background(Color.red)`` 110 | 第一眼看,都会以为``Color.red``是一个颜色参数,它设置了背景色是红色,其实并不是,``Color.red``是一个View!它的功能就是把父级视图所建议的区域填充为红色。它的父级就是背景。而且背景修改了Text。所以建议``Color.red``所填充的区域就是``Text("Hello")``所在的区域。是不是很优美? 111 | 112 | .overlay 修改器也是同样的道理,只是它并不是绘制背景,而是绘制前景而已。 113 | 114 | 我们已经知道了,我们可以给任意一个view使用.Color()方法还有 .background()方法。下面我们将结合GeometryReader,画一个每个角指定不同的半径的矩形的例子来演示如何利用它们。 115 | 116 | 117 | ![image](https://swiftui-lab.com/wp-content/uploads/2019/06/blog-3.png) 118 | 119 | 具体实现如下: 120 | 121 | struct ContentView : View { 122 | var body: some View { 123 | HStack { 124 | Text("SwiftUI") 125 | .foregroundColor(.black).font(.title).padding(15) 126 | .background(RoundedCorners(color: .green, tr: 30, bl: 30)) 127 | 128 | Text("Lab") 129 | .foregroundColor(.black).font(.title).padding(15) 130 | .background(RoundedCorners(color: .blue, tl: 30, br: 30)) 131 | 132 | }.padding(20).border(Color.gray).shadow(radius: 3) 133 | } 134 | } 135 | 136 | struct RoundedCorners: View { 137 | var color: Color = .black 138 | var tl: CGFloat = 0.0 139 | var tr: CGFloat = 0.0 140 | var bl: CGFloat = 0.0 141 | var br: CGFloat = 0.0 142 | 143 | var body: some View { 144 | GeometryReader { geometry in 145 | Path { path in 146 | 147 | let w = geometry.size.width 148 | let h = geometry.size.height 149 | 150 | // We make sure the redius does not exceed the bounds dimensions 151 | let tr = min(min(self.tr, h/2), w/2) 152 | let tl = min(min(self.tl, h/2), w/2) 153 | let bl = min(min(self.bl, h/2), w/2) 154 | let br = min(min(self.br, h/2), w/2) 155 | 156 | path.move(to: CGPoint(x: w / 2.0, y: 0)) 157 | path.addLine(to: CGPoint(x: w - tr, y: 0)) 158 | path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false) 159 | path.addLine(to: CGPoint(x: w, y: h - br)) 160 | path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false) 161 | path.addLine(to: CGPoint(x: bl, y: h)) 162 | path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false) 163 | path.addLine(to: CGPoint(x: 0, y: tl)) 164 | path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false) 165 | } 166 | .fill(self.color) 167 | } 168 | } 169 | } 170 | 171 | 另外,我们对自定义的Overlay设置透明度为0.5,设置在Text上。 172 | 代码如下: 173 | 174 | Text("SwiftUI") 175 | .foregroundColor(.black).font(.title).padding(15) 176 | .overlay(RoundedCorners(color: .green, tr: 30, bl: 30).opacity(0.5)) 177 | Text("Lab") 178 | .foregroundColor(.black).font(.title).padding(15) 179 | .overlay(RoundedCorners(color: .blue, tl: 30, br: 30).opacity(0.5)) 180 | 181 | ### 关于鸡和鸡蛋的问题 182 | 当你开始使用GeometryReader, 你就会发现所谓的鸡和鸡蛋的问题。 183 | 因为GeometryReader需要给子级试图提供可用空间,它首先需要尽可能多的占用空间。 但是子类可能会设置一个小的空间,这时候GeometryReader还是尽可能的保持大。 184 | 一旦子级试图确定了其所需空间, 你可能会被迫缩小GeometryReader。这时候子级试图就会GeometryReader计算出的新的大小做出反应。 一个循环就产生了。 185 | 186 | 所以 当遇到是子级试图依赖父级试图的大小,还是父级试图依赖于子级试图的大小。 可能GeometryReader并不适合解决你的布局问题。由此,你可以看看我的下一篇文章[Preferences and Anchor Preferences.](https://swiftui-lab.com/communicating-with-the-view-tree-part-1/) 187 | 188 | ### 总结 189 | 190 | 今天所学的GeometryReader让我们的自定义view知道了它们所需的大小和位置。 我们还学习了获取其他view的geometry。 191 | 这只是很第一篇官方并没有提及的讲SwiftUI中的布局工具的文章,接下来我们将会深度研究view的数层次,和子级试图如何把数据向上传递。[点我查看](https://swiftui-lab.com/communicating-with-the-view-tree-part-1/) 192 | 193 | -------------------------------------------------------------------------------- /SwiftDemo/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liaoworking/Advanced-Swift/350f73da9e24b9dff87213999d331411f1f41ae1/SwiftDemo/.DS_Store -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo.xcodeproj/project.xcworkspace/xcuserdata/guanghuiliao.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liaoworking/Advanced-Swift/350f73da9e24b9dff87213999d331411f1f41ae1/SwiftDemo/AdvanceSwiftAllDemo.xcodeproj/project.xcworkspace/xcuserdata/guanghuiliao.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo.xcodeproj/xcuserdata/guanghuiliao.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | AdvanceSwiftAllDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 10/23/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C2P1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C2P1.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 10/23/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | ///内建集合类型--数组 11 | class C2P1: NSObject { 12 | 13 | var demoArray = ["🌰","🍎","🍐","🍇","🥚","🌽","🌺","I"] 14 | 15 | 16 | /// 集合的一些高级用法 17 | func advacedArrayFunc() { 18 | ///enumerated() 用法 19 | for (idx, obj) in demoArray.enumerated() { 20 | print(idx) // 元素所在的idx 21 | print(obj) //元素 22 | } 23 | 24 | 25 | ///寻找指定元素的位置 index 26 | if let idx = demoArray.index(where: { (obj) -> Bool in 27 | if obj == "I"{ 28 | return true 29 | } 30 | return false 31 | }) { 32 | print(idx)//7 33 | } 34 | 35 | 36 | ///所有元素进行变形 map 37 | demoArray = demoArray.map { (obj) -> String in 38 | return "hi~\(obj)" 39 | } 40 | for obj in demoArray { 41 | print(obj)// hi~🌰 42 | } 43 | 44 | 45 | ///筛选出符合要求的元素集合 filter 46 | demoArray = demoArray.filter { (obj) -> Bool in 47 | if obj == "🌰" || obj == "🍎" || obj == "I"{ 48 | return true 49 | }else{ 50 | return false 51 | } 52 | } 53 | print(demoArray)//["🌰", "🍎", "I"] 54 | 55 | 56 | ///reduce 基础思想是将一个序列转换为一个不同类型的数据,期间通过一个累加器(Accumulator)来持续记录递增状态。 57 | ///TODO!这个函数的精髓我还不知道怎么清晰描述。欢迎参透的同学pr一下😆。 58 | //[1,2,3].reduce(into: <#T##Result#>, <#T##updateAccumulatingResult: (inout Result, Int) throws -> ()##(inout Result, Int) throws -> ()#>) 59 | 60 | /// 两个数组变形合并 flatMap 61 | let fruit = ["🍎","🍐","🍌"] 62 | let animal = ["🐷"] 63 | 64 | let result = fruit.flatMap { (f) -> [String] in 65 | let newArray = animal.map({ (a) -> String in 66 | return (a+"eat"+f) 67 | }) 68 | return newArray 69 | } 70 | print(result) //["🐷eat🍎", "🐷eat🍐", "🐷eat🍌"] 71 | } 72 | 73 | 74 | 75 | ///切片 76 | func slice() { 77 | let fruit = ["🍎","🍐","🍌"] 78 | let slice = fruit[1.. 81 | 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C2P2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C2P2.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 10/24/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C2P2: NSObject { 12 | 13 | func advanceDictFunc() { 14 | 15 | var dict = ["name":"liaoWorking","age":"17"] 16 | 17 | 18 | /// 得到更新键值对之前的值 updateValue 19 | let oldValue = dict.updateValue("18", forKey: "age") 20 | print(oldValue) // Optional("17") 21 | print(dict["age"]) //Optional("18") 22 | 23 | 24 | ///字典的合并 merge 25 | let newDict = ["name":"Jane","age":"19","gender":"M"] 26 | dict.merge(newDict) { (dictValue, newDictValue) -> String in 27 | print(dictValue) // liaoworking 相同key时候的dictValue 28 | print(newDictValue) //Jane 相同key时候的newDictValue 29 | 30 | return newDictValue //返回你觉得应该选择的value 我这里默认都是newDictValue 31 | } 32 | print(dict) 33 | 34 | 35 | ///字典的map方法 36 | let mapDict = dict.mapValues { (value) -> String in 37 | return "new"+value 38 | } 39 | print(mapDict)//["name": "newliaoWorking", "age": "new18"] 40 | 41 | 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C2P3_C2P4.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C2P3-C2P4.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 10/25/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C2P3_C2P4: NSObject { 12 | 13 | 14 | func baseSetFunc() { 15 | let numSet:Set = [1,2,3,4,5] 16 | let otherSet:Set = [2,6] 17 | 18 | ///并集 19 | let unionSet = numSet.union(otherSet) 20 | print(unionSet) //[5, 6, 2, 3, 1, 4] 21 | 22 | ///交集 23 | let intersectionSet = numSet.intersection(otherSet) 24 | print(intersectionSet) //[2] 25 | 26 | ///补集 27 | let subtractingSet = numSet.subtracting(otherSet) 28 | print(subtractingSet) //[5, 3, 1, 4] 29 | 30 | } 31 | 32 | 33 | func creatRange() { 34 | let singleNum = 0..<10//不包括10 35 | 36 | let lowerLetters = Character("a")...Character("z")//包括z 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C3P1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C3P1.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by liaoworking on 2018/10/27. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C3P1: NSObject { 12 | 13 | func aboutSequence() { 14 | 15 | ///myfirstSquence 我的第一个自定义集合 16 | for prefixStr in PrefixSequence(string: "Hi~LiaoWorking!") { 17 | print(prefixStr) 18 | // H 19 | // Hi 20 | // Hi~ 21 | // Hi~L 22 | // Hi~Li 23 | // Hi~Lia 24 | // Hi~Liao 25 | // Hi~LiaoW 26 | // Hi~LiaoWo 27 | // Hi~LiaoWor 28 | // Hi~LiaoWork 29 | // Hi~LiaoWorki 30 | // Hi~LiaoWorkin 31 | // Hi~LiaoWorking 32 | // Hi~LiaoWorking! 33 | } 34 | 35 | /// 常用的两个方法 特别是代替c语言风格for循环,下表步长无线性关系的。 36 | /// 具体项目中我也没具体遇到过。。so 不写demo了啊~ 37 | // sequence(first: <#T##T#>, next: <#T##(T) -> T?#>) 38 | // sequence(state: <#T##State#>, next: <#T##(inout State) -> T?#>) 39 | } 40 | 41 | /// 通过引用语义的特性写斐波那契 42 | func fibsIterator() -> AnyIterator { 43 | var startNum = (0, 1) 44 | return AnyIterator{ 45 | let nextNum = startNum.0 46 | startNum = (startNum.1 , startNum.0 + startNum.1) 47 | return nextNum 48 | } 49 | } 50 | 51 | } 52 | 53 | ///下面的顺序按照书上的顺序来的~ 54 | 55 | /// 迭代器 56 | struct FibsNumIterator:IteratorProtocol { 57 | typealias Element = Int 58 | var startNum = (0, 1) 59 | mutating func next() -> Int? { 60 | let nextNum = startNum.0 61 | startNum = (startNum.1, startNum.0 + startNum.1) 62 | return nextNum 63 | } 64 | } 65 | 66 | 67 | ///step1.创建一个迭代器 68 | struct PrefixStrIterator:IteratorProtocol { 69 | var string: String 70 | var offset: String.Index 71 | init(string:String) { 72 | self.string = string 73 | offset = string.startIndex 74 | } 75 | mutating func next() -> String? { 76 | guard offset < string.endIndex else { return nil} 77 | offset = string.index(after: offset) 78 | return String(string[string.startIndex.. PrefixStrIterator { 87 | return PrefixStrIterator(string: string) 88 | } 89 | } 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C3P2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C3P2.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 10/29/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | /// 集合类型:Collection 12 | class C3P2: NSObject { 13 | 14 | func collectionFunc() { 15 | 16 | } 17 | 18 | } 19 | 20 | 21 | /// 自己写一个最简单的将元素入队和出队的类型 22 | protocol Queue{ 23 | ///self中所持有的元素类型 24 | associatedtype Element 25 | ///把newElement 加入队列 26 | mutating func enqueue(_ newElement: Element) 27 | ///从self出队一个元素 28 | mutating func dequeue() -> Element? 29 | } 30 | 31 | 32 | ///FIFO( First Input First Output) 33 | struct FIFOQueue:Queue { 34 | 35 | fileprivate var left: [Int] = [] 36 | fileprivate var right: [Int] = [] 37 | 38 | ///把元素添加到队尾 39 | /// 复杂度O(1) 40 | mutating func enqueue(_ newElement: Int) { 41 | right.append(newElement) 42 | } 43 | 44 | 45 | /// 从队列首部移除一个元素 46 | /// 队列为nil时候返回空 47 | /// - 复杂度: 平摊 O(1) 48 | mutating func dequeue() -> Int? { 49 | if left.isEmpty { 50 | left = right.reversed() 51 | right.removeAll() 52 | } 53 | return left.popLast() 54 | } 55 | } 56 | 57 | 58 | extension FIFOQueue:Collection { 59 | ///告诉开始和结束的idx 60 | public var startIndex: Int { return 0 } 61 | public var endIndex: Int { return left.count + right.count} 62 | 63 | ///返回指定位置的下一个位置 64 | public func index(after i: Int) -> Int { 65 | precondition( i < endIndex) 66 | return i + 1 67 | } 68 | 69 | public subscript(position: Int) -> Int { 70 | precondition((0.. 21 | 22 | 23 | MemoryLayout.size(ofValue: [1,2,3,4,5]) //8 24 | ///切片的大小是列表的大小加上子范围的大小。 25 | MemoryLayout.size(ofValue: [1,2,3,4,5].dropFirst()) //32 26 | 27 | 28 | let cities = ["shangHai","Beijing","NewYork","Chicago","Tokyo","Hongkong"] 29 | 30 | let slice = cities[2...4] 31 | cities.startIndex //0 32 | cities.endIndex //6 33 | slice.startIndex //2 34 | slice.endIndex //5 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C3P5.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C3P5.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by liaoworking on 2018/11/3. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | ///专门的集合类型 11 | //class C3P5: Collection { 12 | // 13 | //} 14 | 15 | //extension C3P5: BidirectionalCollection{ 16 | // 17 | //} 18 | // 19 | //extension C3P5: RandomAccessCollection{ 20 | // 21 | //} 22 | // 23 | //extension C3P5: MutableCollection{ 24 | // 25 | //} 26 | // 27 | //extension C3P5: RangeReplaceableCollection{ 28 | // 29 | //} 30 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C4P2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C4P2.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 11/5/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C4P2: NSObject { 12 | func optionFunc() { 13 | 14 | ///返回值为Optional的实质 15 | let strArray = ["one","two","three"] 16 | switch strArray.index(of: "four") { 17 | case .none: 18 | print("返回值为空") //返回值为空 19 | case .some(let idx): 20 | print(idx) 21 | print("有值") 22 | } 23 | 24 | 25 | } 26 | 27 | 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C4P3.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C4P3.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 11/5/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C4P3: NSObject { 12 | 13 | func somethingAboutOptional() { 14 | 15 | ///if var 的改变不影响原值 16 | let dict:[String:String]? 17 | dict = ["key1":"value1"] 18 | if var varDict = dict { 19 | varDict["key1"] = "value2" 20 | } 21 | print(dict) //Optional(["key1": "value1"]) 22 | 23 | 24 | ///while let 25 | // while let line = printLine() { 26 | // print(line) 27 | // } 28 | 29 | 30 | let testOptionalChainingObj = testOptionalChaining() 31 | testOptionalChainingObj.voidCallback = { 32 | print("Hi~ I am callback~") 33 | } 34 | testOptionalChainingObj.testCall() 35 | 36 | 20.half?.half?.half?.half 37 | 38 | 39 | var number: Int? 40 | number = nil 41 | String(number ?? 5) 42 | 43 | 44 | 45 | ///flatMap在可选值中的用法,感觉这个用法很有用啊 46 | let urlString = "http://www.objc.io/logo.png" 47 | let view = URL(string: urlString) 48 | .flatMap { (url) -> Data? in 49 | try? Data(contentsOf: url) 50 | } 51 | .flatMap { (data) -> UIImage? in 52 | UIImage(data: data) 53 | } 54 | .map { (image) -> UIImageView in 55 | UIImageView(image: image) 56 | } 57 | 58 | 59 | let view2 = URL(string: urlString) 60 | .flatMap { 61 | try? Data(contentsOf: $0) 62 | } 63 | .flatMap { 64 | UIImage(data: $0) 65 | } 66 | .map { 67 | UIImageView(image: $0) 68 | } 69 | 70 | if let view2 = view2 { 71 | UIView().addSubview(view2) 72 | } 73 | 74 | 75 | 76 | 77 | ///flatMap 过滤nil 78 | let numbers = ["1","2","3","4","liaoworking"] 79 | 80 | var sum = 0 81 | for case let i? in numbers.map({ 82 | Int($0) 83 | }) { 84 | sum += i// Int($0)为nil就不走这里了 85 | } 86 | // sum的值为10 87 | 88 | ///当我们用?? 把nil替换成0 89 | numbers.map { Int($0) }.reduce(0) { $0 + ($1 ?? 0)} //10 90 | 91 | ///在标准库中flatMap的作用可能正是你想要 92 | numbers.flatMap { Int($0) }.reduce(0, +) // 10 93 | 94 | 95 | 96 | /// switch case 97 | for i in numbers { 98 | switch Int(i) { 99 | case 0: 100 | print("0") 101 | case nil: 102 | print("can't convert to int") 103 | default: 104 | print("一个大数") 105 | } 106 | } 107 | } 108 | 109 | 110 | } 111 | 112 | 113 | /// 测试可选链 114 | class testOptionalChaining: NSObject { 115 | 116 | var voidCallback:(()-> Void)? 117 | 118 | func testCall() { 119 | voidCallback?() 120 | //不推荐的写法(直到写笔记的时候才发现自己之前这样写都写复杂了,一点swift的优点都没体现出来😂) 121 | if voidCallback != nil { 122 | voidCallback!() 123 | } 124 | 125 | } 126 | 127 | 128 | } 129 | 130 | 131 | extension Int { 132 | 133 | var half: Int? { 134 | guard self > 1 else {return nil} 135 | return self/2 136 | } 137 | 138 | } 139 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C4P4.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C4P4.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 11/12/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C4P4: NSObject { 12 | 13 | func useOperators() { 14 | 15 | let ages = ["liaoWorking":17,"wangzhuxian":16] 16 | ///有强制解包 17 | ages.keys.filter { name in ages[name]! < 50 }.sorted() 18 | ///巧妙的避开了强制解包 19 | ages.filter { (_, age) in age < 50 } 20 | .map { (name, _) in name } 21 | .sorted() 22 | 23 | 24 | 25 | // ///crash output 26 | // let s = "foo" 27 | // let a = Int(s) !! "crash here~" 28 | 29 | 30 | } 31 | 32 | // func !! (wrapped:T?, failureText: @autoclosure ()-> String) -> T { 33 | // <#function body#> 34 | // } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C5P1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C5P1.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by liaoworking on 2018/11/17. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C5P1: NSObject { 12 | 13 | func valueType() { 14 | 15 | let mArray:NSMutableArray = [1,2,3,4,5,6,7,8] 16 | for _ in mArray {///边遍历边操作数组是危险的 这里会崩溃 17 | mArray.removeAllObjects() 18 | print("上") 19 | } 20 | 21 | 22 | let array:[Int] = [1,2,3,4,5,6,7,8] 23 | for _ in array { 24 | mArray.removeAllObjects() 25 | } 26 | print(mArray.count) 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C5P3Struct.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C5P3Struct.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 11/21/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C5P3Struct: NSObject { 12 | 13 | var xPoint = Point(x: 8, y: 0){ 14 | didSet{ 15 | print("哇哦~我被调用了") 16 | } 17 | } 18 | 19 | 20 | func structIntroduction() { 21 | let p = Point(x: 1, y: 1) 22 | Point.origin 23 | xPoint.x = 10 24 | let y = Point(x: 10, y: 1) + Point(x: 1, y: 10) 25 | print(y) 26 | } 27 | 28 | } 29 | 30 | 31 | struct Point { 32 | var x:Int 33 | var y:Int 34 | 35 | static func +(lhs: Point, rhs: Point)-> Point{ 36 | return Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 37 | } 38 | 39 | func triple(x:Int){ 40 | // x = x * 3 //error x is let 41 | } 42 | 43 | func triple( x:inout Int) { 44 | x = x * 3 // ojbk 45 | } 46 | 47 | 48 | } 49 | 50 | 51 | extension Point { 52 | static let origin = Point(x:0, y:0) 53 | } 54 | 55 | extension Point { 56 | mutating func translateY(by offset: Int) { 57 | y = y + offset 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C5P4Copy_on_write.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C5P4Copy_on_write.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 11/28/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C5P4Copy_on_write: NSObject { 12 | 13 | func CopyOnWriteDemo() { 14 | 15 | var x = [1,2,4] 16 | var y = x 17 | x.append(5) //1,2,4,5 18 | y.removeLast() //1,2 19 | 20 | var a = Box(NSMutableData()) 21 | isKnownUniquelyReferenced(&a)//true 22 | 23 | var b = a 24 | isKnownUniquelyReferenced(&a)//false 25 | isKnownUniquelyReferenced(&b)//false 26 | 27 | 28 | let someBytes = MyData(NSData(base64Encoded: "wAEP/w==", options: [])!) 29 | var empty = MyData(NSData()) 30 | var emptyCopy = empty 31 | for _ in 0..<5 { 32 | empty.append(someBytes) 33 | 34 | } 35 | //empty 36 | //emptyCopy <> 37 | 38 | 39 | } 40 | 41 | } 42 | 43 | 44 | 45 | final class Box { 46 | var unbox:A 47 | init(_ value:A) { 48 | self.unbox = value 49 | } 50 | } 51 | 52 | struct MyData { 53 | fileprivate var _data: Box 54 | var _dataForWriting: NSMutableData { 55 | mutating get { 56 | if !isKnownUniquelyReferenced(&_data) {//检查对_data的引用是否是唯一性 57 | _data = Box(_data.unbox.mutableCopy() as! NSMutableData) 58 | print("Making a copy") 59 | } 60 | return _data.unbox 61 | } 62 | } 63 | init(_ data: NSData) { 64 | self._data = Box(data.mutableCopy() as! NSMutableData) 65 | } 66 | } 67 | 68 | extension MyData { 69 | mutating func append(_ other: MyData) { 70 | _dataForWriting.append(other._data.unbox as Data) 71 | } 72 | } 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C5P5_P6ClosureAndMemory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C5P5_P6ClosureAndMemory.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 12/2/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C5P5_P6ClosureAndMemory: NSObject { 12 | 13 | func Demo() { 14 | 15 | } 16 | } 17 | 18 | 19 | 20 | class View { 21 | unowned var window: UIView 22 | init(window: UIView) { 23 | self.window = window 24 | } 25 | 26 | deinit { 27 | print("Deinit View") 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C6.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C6.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 12/4/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C6: NSObject { 12 | 13 | 14 | 15 | func function() { 16 | loveSomeBodyFunc = loveSomeBody 17 | loveSomeBodyFunc?("liaoWorking")//I love liaoWorking 18 | doSomeThing(things: loveSomeBodyFunc) 19 | loveSomeBodyFunc = lovingYou() 20 | } 21 | 22 | 23 | func loveSomeBody(name:String) { 24 | print("I love \(name)") 25 | } 26 | ///函数作为参数 27 | func doSomeThing(things: ((String)-> Void)?) { 28 | things?("NObody") 29 | } 30 | 31 | var loveSomeBodyFunc:((String) ->Void)? 32 | 33 | ///函数作为返回值 34 | func lovingYou() -> ((String) ->Void)?{ 35 | return loveSomeBody 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/C6P1Flexibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // C6P1Flexibility.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 12/6/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class C6P1Flexibility: NSObject { 12 | 13 | func flexibilityDemo() { 14 | 15 | var mArray = [3, 1, 2] 16 | mArray.sort() ///1 2 3 17 | mArray.sort(by: >) ///3 2 1 18 | 19 | print(mArray) 20 | 21 | 22 | let animal = ["fish", "dog", "elephant"] 23 | ///反向比较字符串的大小 我们可以嵌套任意的比较函数 让排序功能更强大! 24 | let okAnimal = animal.sorted { (lhs, rhs) -> Bool in 25 | let l = lhs.reversed() 26 | let r = rhs.reversed() 27 | ///按顺序比较两个字符串的大小 abc > abb 28 | return l.lexicographicallyPrecedes(r) 29 | } 30 | print(okAnimal) 31 | 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SwiftDemo/AdvanceSwiftAllDemo/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // AdvanceSwiftAllDemo 4 | // 5 | // Created by Guanghui Liao on 10/23/18. 6 | // Copyright © 2018 liaoworking. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | C6Function() 16 | } 17 | 18 | /// Array 19 | func C2P1Demo() { 20 | C2P1().advacedArrayFunc() 21 | C2P1().slice() 22 | } 23 | 24 | /// Dictionary 25 | func C2P2Demo() { 26 | C2P2().advanceDictFunc() 27 | } 28 | 29 | /// Set Range 30 | func C2P3_C2P4Demo() { 31 | C2P3_C2P4().baseSetFunc() 32 | C2P3_C2P4().creatRange() 33 | } 34 | 35 | 36 | /// Sequence 37 | func C3P1Demo() { 38 | C3P1().aboutSequence() 39 | } 40 | 41 | /// Slice 42 | func C3P4Demo() { 43 | C3P4().sliceFunc() 44 | } 45 | 46 | /// 可选值 47 | func C4P2Demo() { 48 | C4P2().optionFunc() 49 | } 50 | 51 | /// 可选值的一些用法 52 | func C4P3Demo() { 53 | C4P3().somethingAboutOptional() 54 | } 55 | 56 | func C4P4Demo() { 57 | C4P4().useOperators() 58 | } 59 | 60 | func C5P1Demo() { 61 | C5P1().valueType() 62 | } 63 | 64 | func C5P3StructDemo() { 65 | // C5P3Struct().structIntroduction() 66 | C5P4Copy_on_write().CopyOnWriteDemo() 67 | } 68 | 69 | func C6Function() { 70 | // C6().function() 71 | C6P1Flexibility().flexibilityDemo() 72 | } 73 | override func didReceiveMemoryWarning() { 74 | super.didReceiveMemoryWarning() 75 | // Dispose of any resources that can be recreated. 76 | } 77 | 78 | 79 | } 80 | 81 | -------------------------------------------------------------------------------- /pic/ProtocolInternals01.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liaoworking/Advanced-Swift/350f73da9e24b9dff87213999d331411f1f41ae1/pic/ProtocolInternals01.jpeg -------------------------------------------------------------------------------- /pic/String_words.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liaoworking/Advanced-Swift/350f73da9e24b9dff87213999d331411f1f41ae1/pic/String_words.JPG -------------------------------------------------------------------------------- /pic/tips_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Liaoworking/Advanced-Swift/350f73da9e24b9dff87213999d331411f1f41ae1/pic/tips_11.png -------------------------------------------------------------------------------- /swift新特性/SwiftUI中的@ViewBuilder.md: -------------------------------------------------------------------------------- 1 | 2 | ## 什么是@ViewBuilder? 3 | 4 | ##### 从字面意思去理解 ViewBuilder 就是```视图构建```,其主要使用场景就是构建视图。 5 | ##### 在Apple的[官方文档](https://developer.apple.com/documentation/swiftui/viewbuilder)中有这样一句话 6 | 7 | > allowing those closures to provide multiple child views 8 | > 允许闭包中提供多个子视图 9 | 10 | 就是对@ViewBuilder的最好的解释。 11 | 12 | #### 不使用@ViewBuilder时你只能传递一个View在闭包里,使用@ViewBuilder你可以传递多个View到闭包里面 13 | 14 | ## 结合ViewBuilder和便利构造函数使代码更优美。 15 | 16 | ##### 可以在项目中通过ViewBuilder注解和便利构造函数把许多具体相同特点的View封装起来,并且分离逻辑代码和视图,提升代码的可复用性,并增强可读性。 17 | ##### 看了下面的例子你可能就会对@ViewBuilder结合便利构造器的使用有一个更好的理解。 18 | 19 | 如果我们想要设置一个Text的背景为红色,圆角为5。我们可能这样去写 20 | 21 | Text("Liaoworking") 22 | .background(Color.red) 23 | .cornerRadius(5) 24 | 25 | 后来产品加需求了: 图片也是红色的背景,圆角为5: 26 | 27 | Image("Liaoworking") 28 | .background(Color.red) 29 | .cornerRadius(5) 30 | 31 | 再后来产品又加需求:一些自定义视图也是红色的背景,圆角为5: 32 | 这里假设我们的自定义视图类为MyView 33 | 34 | MyView() 35 | .background(Color.red) 36 | .cornerRadius(5) 37 | 38 | 聪明的你肯定想到了用一个View的extension来统一处理。 39 | 40 | extension View { 41 | func addRedBGWithRoundCorner() -> some View { 42 | self 43 | .background(Color.red) 44 | .cornerRadius(5) 45 | } 46 | } 47 | 48 | //调用: 49 | Text("Liaoworking").addRedBGWithRoundCorner() 50 | 51 | 这样做的确可以达到相同的效果,而且代码也会简洁不少。 52 | 53 | 这个时候你还可以用```@ViewBuilder```注解来创建你的自定义视图达到相同的效果: 54 | 55 | struct RedBackgroundAndCornerView: View { 56 | let content: Content 57 | 58 | init(@ViewBuilder content: () -> Content) { 59 | self.content = content() 60 | } 61 | 62 | var body: some View { 63 | content 64 | .background(Color.red) 65 | .cornerRadius(5) 66 | } 67 | } 68 | 69 | 使用方法如下: 70 | 71 | RedBackgroundAndCornerView { 72 | Text("liaoworking") 73 | } 74 | 75 | 76 | 两种封装的最后效果都是一样的,你可能会觉得,```View的extension封装要比@ViewBuilder 封装 好太多```。代码都少写很多。 77 | 78 | 但是突然有一天,你的产品经理决定再加一个功能,上面的这些视图点击后都自动隐藏,你会怎么写? 79 | 80 | @State var needHideText: Bool = false 81 | 82 | Text("Liaoworking") 83 | .addRedBGWithRoundCorner() 84 | .opacity(needHideText ? 0.0 : 1.0) 85 | .onTapGesture { 86 | self.needHideText = true 87 | } 88 | 89 | 90 | 然后到了Image,还有MyView, ```对于每一个需要隐藏的对象,你都得创建一个类似于needHideText的变量来控制显示隐藏逻辑```。 到最后你的重复代码可能会越来越多。 91 | 92 | 因为extension```无法去存储控制隐藏逻辑的变量```,这个时候@ViewBuilder的先天优势马上就体现出来了。 93 | 94 | 我们只需要将逻辑代码在@ViewBuilder中写一次,所有的View就具有了相同的特性。 95 | 96 | struct RedBackgroundAndCornerView: View { 97 | let content: Content 98 | @State var needHidden: Bool = false 99 | 100 | init(@ViewBuilder content: () -> Content) { 101 | self.content = content() 102 | } 103 | 104 | var body: some View { 105 | content 106 | .background(Color.red) 107 | .cornerRadius(5) 108 | .opacity(needHidden ? 0.0 : 1.0) 109 | .onTapGesture { 110 | self.needHidden = true 111 | } 112 | } 113 | } 114 | 115 | // 所有被RedBackgroundAndCornerView包裹的View就都具有了点击后隐藏的功能了。 116 | 117 | RedBackgroundAndCornerView { 118 | Text("liaoworking") 119 | // 如果不使用@ViewBuilder 这里会报错 120 | // @ViewBuilder使闭包拥有提供多个视图的特性。 121 | Text("liaoworking") 122 | } 123 | 124 | RedBackgroundAndCornerView { 125 | Image("liaoworking") 126 | } 127 | 128 | RedBackgroundAndCornerView { 129 | MyView("liaoworking") 130 | } 131 | 132 | 133 | ### 总结: 134 | @ViewBuilder是一个封装可复用view逻辑的利器。它最大的好处就是把你```逻辑代码和你的视图剥离开```。让代码的```可维护性和易读性有很大提升```。我在之前的项目里一开始写过很多垃圾代码,后来知道了@ViewBuilder,这的确在对相同逻辑View的封装和使用上有了很大的便捷。 135 | 136 | -------------------------------------------------------------------------------- /swift新特性/Swift编译加速的Tips.md: -------------------------------------------------------------------------------- 1 | 网上关于Swift编译加速的文章挺多,这里就不赘述。 2 | 这里推荐全方位无死角讲编译优化的文档[[Optimizing-Swift-Build-Times](https://github.com/fastred/Optimizing-Swift-Build-Times) 3 | ](https://github.com/fastred/Optimizing-Swift-Build-Times) 4 | 5 | 还有优化的神器[BuildTimeAnalyzer-for-Xcode](https://github.com/RobertGummesson/BuildTimeAnalyzer-for-Xcode) 6 | 7 | 下面就针对于具体代码层面的编译优化谈一些心得和感悟。 8 | 9 | ## 如何找出项目中编译耗时的代码? 10 | 11 | 在XCode 10的时候Swift就支持了[监控的编译超时的警告](https://github.com/apple/swift/commit/18c75928639acf0ccf0e1fb6729eea75bc09cbd5)。 12 | 它能帮助我们找到项目中需要编译优化的函数,并量化具体的优化时间。 13 | 14 | 在```Build Settings ➔ Swift Compiler - Custom Flags ➔ Other Swift Flags``` 中添加. 15 | 16 | ///为warning的编译时间阈值 17 | -Xfrontend -warn-long-function-bodies= 18 | 19 | -Xfrontend -warn-long-expression-type-checking= 20 | 21 | ![添加编译警告](https://upload-images.jianshu.io/upload_images/1724449-d8ce839f34848a13.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 22 | 23 | 测试机型: A12z Apple DTK(所以编译时间会看起来短很多) 24 | 25 | ## Let's Start 26 | 27 | ### tip1: 使用 + 拼接可选字符串会极其耗时。 28 | ### 建议:改写成 "\\(string1)\\(string2)"的形式 29 | 30 | ![红框中为编译耗时代码](https://upload-images.jianshu.io/upload_images/1724449-7b68c6c34a3dfa5b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 31 | 32 | 耗时代码:编译耗时372ms 33 | 34 | let finalResult = (dbWordModel?.vocabularyModel?.justSentenceExplain ?? "") + "
" + (dbWordModel?.vocabularyModel?.justSentence ?? "") 35 | 36 | 37 | 38 | 优化后: 编译时间20ms ```18.5倍```的编译性能提升🤯。 39 | 40 | let finalResult = "\(dbWordModel?.vocabularyModel?.justSentenceExplain ?? "")
\(dbWordModel?.vocabularyModel?.justSentence ?? "")" 41 | 42 | 43 | ### Tip2: 可选值使用```??```赋默认值再嵌套其他运算会极其耗时。 44 | ##### 优化方法: 使用guard let 解包。 45 | 还是上面的例子 46 | 优化后: 编译耗时 63ms 5.9倍的编译性能提升。 47 | 48 | guard let dbSentenceExp = dbWordModel?.vocabularyModel?.justSentenceExplain, 49 | let dbSentence = dbWordModel?.vocabularyModel?.justSentence else { return } 50 | let finalResult = "\(dbSentenceExp)
\(dbSentence)" 51 | 52 | 53 | ### Tip3: 将长计算式代码拆分 最后组合计算。 54 | 55 | ![image.png](https://upload-images.jianshu.io/upload_images/1724449-f4eb78a6e9984d72.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 56 | 57 | 耗时代码:编译时长736ms 58 | 59 | let totalTime = (timeArray.first?.float()?.int ?? 0) * 60 + (timeArray.last?.float()?.int ?? 0) 60 | 61 | 62 | 优化拆分后: 耗时22ms ```33.4倍```编译性能提升🤯 63 | 64 | let firstPart: Int = (timeArray.first?.float()?.int ?? 0) 65 | let lastPart: Int = (timeArray.last?.float()?.int ?? 0) 66 | let totalTime = firstPart * 60 + lastPart 67 | 68 | 69 | ### Tip4: 与或非和>=,<=,==逻辑运算嵌套Optional会比较耗时。 70 | 建议: 使用guard let + 拆分 进行优化。 71 | 72 | ![image](https://upload-images.jianshu.io/upload_images/1724449-c330901e132bcea1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 73 | 74 | 耗时代码: 10420ms 编译器已经无法编译并报错。 75 | 76 | if (homeMainVC?.scrollview.contentOffset.y ?? 0) >= ((homeMainVC?.headHeight ?? 0) - (homeMainVC?.ignoreTopSpeace ?? 0)) { 77 | 78 | } 79 | 80 | 优化后: 21ms ```496倍```编译性能提升🤯 81 | 82 | let leftValue: CGFloat = homeMainVC?.scrollview.contentOffset.y ?? 0 83 | let rightValue: CGFloat = (homeMainVC?.headHeight ?? 0.0) - (homeMainVC?.ignoreTopSpeace ?? 0.0) 84 | if leftValue == rightValue { 85 | } 86 | 87 | ### Tip5: 手动增加类型推断会降低编译时间。 88 | 89 | 在Tip4的基础上我们二次优化: 90 | ![image](https://upload-images.jianshu.io/upload_images/1724449-2a6c6fd1e4fe2c7a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 91 | 92 | 优化前:21ms 93 | 94 | let leftValue = homeMainVC?.scrollview.contentOffset.y ?? 0 95 | let rightValue = (homeMainVC?.headHeight ?? 0.0) - (homeMainVC?.ignoreTopSpeace ?? 0.0) 96 | 97 | 优化后:增加了类型推断 16ms 98 | 99 | 100 | let leftValue: CGFloat = homeMainVC?.scrollview.contentOffset.y ?? 0 101 | let rightValue: CGFloat = (homeMainVC?.headHeight ?? 0.0) - (homeMainVC?.ignoreTopSpeace ?? 0.0) 102 | 103 | 104 | ### 结论: 105 | 项目中的一百多个编译优化之后性能在MacBook pro 15寸 16款 i7 上面编译速度提升了34s 262s提升到了228s, 在公司性能较差的打包机上提升可能会更快。 106 | 107 | ### 感悟: 108 | 有的时候在```编译性能```和```代码的可阅读性```之间需要有一个权衡取舍。 109 | 一昧的追求编译性能而舍弃代码的可阅读性不可取。 110 | 电脑性能比较差的情况下浪费大部分时间在编译上也不可取。 111 | M1芯片的电脑另说。 112 | 113 | 114 | 附录: 115 | [Measuring Swift compile times in Xcode 9](https://www.jessesquires.com/blog/2017/09/18/measuring-compile-times-xcode9/) 116 | -------------------------------------------------------------------------------- /swift新特性/通过@propertyWrapper让你的代码变的更简洁.md: -------------------------------------------------------------------------------- 1 | 2 | ### 什么是@propertyWrapper? 3 | 4 | ##### 从字面意思去理解 property Wrapper 就是```属性包裹器```(我初二英语水平硬翻,写的时候国内好像还没有一个统一的叫法。有知道学名的同学提醒下谢啦~)。 5 | ##### 它的```作用对象```是```属性``` 6 | ##### 其```主旨```就是:通过```property Wrapper```机制,对一些类似的```属性```的实现代码做同一封装。 7 | ##### 说简单点: 通过@propertyWrapper可以移除掉一些重复或者类似的代码。 8 | 9 | ![@propertyWrapper 的使用步骤 ](https://upload-images.jianshu.io/upload_images/1724449-29d018764bf6c310.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 10 | 11 | propertyWrapper这个知识点不难,比较新而已。只要自己复制下面的Demo在```playGround```里跑一遍看一下就会了。 12 | 13 | 14 | ## 下面用UserDefaults保存是否显示新手引导的例子来让大家有一个直观的了解 15 | 16 | #### 没有@propertyWrapper 的时候。。😔 17 | 18 | extension UserDefaults { 19 | 20 | public enum Keys { 21 | static let hadShownGuideView = "had_shown_guide_view" 22 | } 23 | 24 | var hadShownGuideView: Bool { 25 | set { 26 | set(newValue, forKey: Keys.hadShownGuideView) 27 | } 28 | get { 29 | return bool(forKey: Keys.hadShownGuideView) 30 | } 31 | } 32 | } 33 | 34 | /// 下面的就是业务代码了。 35 | let hadShownGuide = UserDefaults.standard.hadShownGuideView 36 | if !hadShownGuide { 37 | /// 显示新手引导 并保存本地为已显示 38 | showGuideView() /// showGuideView具体实现略。 39 | UserDefaults.standard.hadShownGuideView = true 40 | } 41 | 42 | 43 | 可是项目中有很多地方需要UserDefaults保存本地数据,数据量多了这样的```重复代码```就很多了。 44 | 45 | 46 | #### 有@propertyWrapper 的时候。。😁 47 | 48 | @propertyWrapper /// 先告诉编译器 下面这个UserDefault是一个属性包裹器 49 | struct UserDefault { 50 | ///这里的属性key 和 defaultValue 还有init方法都是实际业务中的业务代码 51 | ///我们不需要过多关注 52 | let key: String 53 | let defaultValue: T 54 | 55 | init(_ key: String, defaultValue: T) { 56 | self.key = key 57 | self.defaultValue = defaultValue 58 | } 59 | /// wrappedValue是@propertyWrapper必须要实现的属性 60 | /// 当操作我们要包裹的属性时 其具体set get方法实际上走的都是wrappedValue 的set get 方法。 61 | var wrappedValue: T { 62 | get { 63 | return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue 64 | } 65 | set { 66 | UserDefaults.standard.set(newValue, forKey: key) 67 | } 68 | } 69 | } 70 | 71 | ///封装一个UserDefault配置文件 72 | struct UserDefaultsConfig { 73 | ///告诉编译器 我要包裹的是hadShownGuideView这个值。 74 | ///实际写法就是在UserDefault包裹器的初始化方法前加了个@ 75 | /// hadShownGuideView 属性的一些key和默认值已经在 UserDefault包裹器的构造方法中实现 76 | @UserDefault("had_shown_guide_view", defaultValue: false) 77 | static var hadShownGuideView: Bool 78 | } 79 | 80 | ///具体的业务代码。 81 | UserDefaultsConfig.hadShownGuideView = false 82 | print(UserDefaultsConfig.hadShownGuideView) // false 83 | UserDefaultsConfig.hadShownGuideView = true 84 | print(UserDefaultsConfig.hadShownGuideView) // true 85 | 86 | 我把@propertyWrapper 的具体用法和知识点已经写到了demo中。 ```PlayGround```中跑一跑就很稳了。 87 | 88 | 当添加新的key去保存数据的时候只用在UserDefaultsConfig 中添加新的属性和包裹器就行 89 | 90 | struct UserDefaultsConfig { 91 | @UserDefault("had_shown_guide_view", defaultValue: false) 92 | static var hadShownGuideView: Bool 93 | ///保存用户名称 94 | @UserDefault("username", defaultValue: "unknown") 95 | static var username: String 96 | } 97 | 98 | Over. 99 | 100 | 101 | ##### 参考资料: 102 | [最早关于Property Wrapper 的提案:swift-evolution 0258 ](https://github.com/DougGregor/swift-evolution/blob/property-wrappers/proposals/0258-property-wrappers.md) (特别长一段英文,略略略) 103 | [Property wrappers to remove boilerplate code in Swift](https://www.avanderlee.com/swift/property-wrappers/) 104 | [nshipster-propertywrapper](https://nshipster.com/propertywrapper/) 105 | -------------------------------------------------------------------------------- /第一章:介绍/第一章:介绍.md: -------------------------------------------------------------------------------- 1 | # 第一章:介绍 2 | 3 | ### 一本书的第一章都是一些博大精深的东西,哈哈反正是讲了很多swift这门语言的一些基础概念和特点。这里先不说。 在以后的章节里会对应一一讲解。略略略。。。 4 | -------------------------------------------------------------------------------- /第七章:字符串/7.1 不再固定宽度.md: -------------------------------------------------------------------------------- 1 | # 第七章:字符串(String) 2 | 3 | 注:下面关于characters的介绍在swift 3.2以后废弃了。String可直接使用characters 的方法。 4 | 5 | ## 7.1 不再固定宽度 6 | 这一节主要是讲字符串```String底层的东西``` 7 | 我们先明白几个知识点: 8 | 9 | #### 知识点1:swift中String 是一个结构体 我们主要使用String.Characters, 它是```Character```的集合,有着类似Array的特性。 10 | 11 | #### 知识点2:Unicode拥有```可变长度```的特性 :原因是不同语言的字符存放字节数不同,若都统一长度,```效率太低``` 12 | 13 | 字符串的展示Demo 14 | 关于```é``` 15 | 我们可以用两种```Unicode```的方式表示字符```é``` 16 | ![image.png](https://upload-images.jianshu.io/upload_images/1724449-0cc74da6a3428980.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 17 | 18 | 19 | strA.utf8.elementsEqual(strB.utf8)//false 20 | 21 | ##### 注: 只比较编码单元的最大好处是:```效率高很多```。 22 | 23 | 具体的数据比较这里没有做。 不过书上在快很多后面打了个感叹号。那应该的确是快很多。😄 24 | 25 | ## 字符蔟(cù)和标准等价 (grapheme cluster and canonically equivalent) 26 | 27 | #### 知识点3: 字符蔟: Character 中的```编码点```组合在一起可以组成单个字符蔟(这个就比较偏字符生成的底层了 大概有个印象就行, 以后有需要了可以看看相关文章) 28 |  [苹果官方Characters and Grapheme Clusters 文档](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Strings/Articles/stringsClusters.html) 29 | 30 | #### 知识点4:标准等价: 在上面的Demo中。不管是何种方式生成的é,只看两个都是显示为é, 在 Unicode的规范中把这个就叫做“```标准等价```” 31 | 32 | #### 知识点5: 做```国际化```的同学们。OC字符串的比较更推荐用compare方法, isEqual方法用上面的不同生成方法比较就会得到false的结果。 33 | demo如下。 34 | 35 | ![image.png](https://upload-images.jianshu.io/upload_images/1724449-aa1553965541c163.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 36 | 37 | 38 | -------------------------------------------------------------------------------- /第七章:字符串/7.2 字符串和集合.md: -------------------------------------------------------------------------------- 1 | # 第七章:字符串(String) 2 | 3 | 注:下面关于characters的介绍在swift 3.2以后废弃了。String可直接使用。 4 | 5 | ## 7.2 字符串和集合 6 | 这一节主要讲String和数组的联系,一些特性,以及像split、map等高级用法的Demo 7 | #### String为嘛```不是集合```的原因: 8 | String如果属于集合的话,会给人一种String的集合方法```完全安全```且符合Unicode规范的暗示。然鹅,在某些情况下遍历字符串可能会出现错误的结果。 9 | 引申:不建议直接给String 添加集合特性的一些协议(第二章中提到的集合协议)。因为这样并不安全。 10 | 11 | #### 为嘛String 不支持通过下标操作符Str[i]来替换字符。 12 | 这个就和上一节降到的String不固定宽度有关。可能插入的新字符串宽度不相同,这个字符串后面的字符串的位置就要往前或者往后移动。这样是很耗性能的。 所以就算是一个字符串也必须要使用replaceRange 13 | 14 | 15 | 16 | #### 知识点1:避免给String添加像Str[i] 的索引的类扩展, 这样低效,复杂度是O(n的平方)。~~~如果真的想用的话直接用Str.characters[i]就行。 String的集合特性全靠characters属性夯。~~~ 17 | 18 | 19 | ### 字符串与切片 20 | 21 | #### 回顾一下:在array上做切片操作返回的不是array,而是arraySlice 22 | 23 | #### 我们可以通过自定义split 达到如下的效果:拆分出句子中的单词成一个数组 24 | "hello,world!".split(separators:" !,").map(String.init) //["hello","world"] 25 | 26 | 其中的split(separators:)方法为自定义的。swift标准库中并没有。主要是知道有这个用法就行。 后期补充的playground会把这些demo都补充起来。如果项目中有对于的需求,到时候再去补充一下对应的知识点也不迟。 27 | 28 | -------------------------------------------------------------------------------- /第七章:字符串/7.3 简单的正则表达式匹配器。 7.4 ExpressibleByStringLiteral.md: -------------------------------------------------------------------------------- 1 | # 字符串 2 | 3 | ## 7.3 简单的正则表达式匹配器。 7.4 ExpressibleByStringLiteral 4 | 这一节主要是用一个Demo来证明```字符串的切片也是字符串```这一点是多么的有用! 5 | ### 然鹅。。。swift 3.2后```废弃了Characters```,我还能怎么样。。 我写半天没啥用。。 6 | 我找两个有用的知识点写写啊。 7 | 刚刚仔细研读了半天,发现也没啥知识点。。 溜了溜了。。 8 | 9 | 下面是的是7.4中的一些知识点: 10 | #### 知识点1:String(“abc”) 和 “abc”是不同的。“abc”是字符串```字面量``` 11 | 什么叫字面量? 12 | let aBool = true 13 | 14 | let aString = “abc” 15 | 16 | let aNumber = 3 17 | 18 | 像上面这种```不用定义类型 而直接知道类型```的就是字面量。 19 | 20 | 我们可以通过```BooleanLiteralConvertible```协议来写自己的Bool类型。类似的```String``` ```Array``` 都有对应的方法。具体实现很简单。不赘述。大概知道有这个用法。需要的时候查资料就行。 21 | 22 | #### 知识点2: 关于ExpressibleByStringLiteral的```骚操作``` 23 | ExpressibleByStringLiteral有什么用? 24 | 通过字面意思(字符串)```实例化指定类```。 25 | 在实际开发中,我们需要创建一个URL应该如下 26 | 27 | let urls = URL(string: "https://www.liaoworking.com") 28 | 29 | 现在我们可以通过ExpressibleByStringLiteral 来用一种意想不到的方式去创建一个URL 30 | 31 | extension URL: ExpressibleByStringLiteral { 32 | 33 | public init(stringLiteral value: String) { 34 | guard let url = URL(string: "\(value)") else { 35 | preconditionFailure("This url: \(value) is not invalid") 36 | } 37 | self = url 38 | } 39 | 40 | 下一次当你创建URL的时候可以这样 ,有没有```狂拽炫酷吊炸天😄``` 41 | 42 | let url: URL = "https://www.liaoworking.com" 43 | 44 | 不过在实际开发中当然```不建议这样去用```啦~ 阅读成本太高。 45 | ```你信不信你的工友拿手机抡你哈哈。``` 46 | 47 | 今天是圣诞夜🎄,各位swift路上的工友,圣诞快乐。 48 | 49 | 2020.05.26补充: 50 | 前一段时间看到有大佬分享 利用ExpressibleByStringLiteral 协议来更方便的把String 转化成Date 51 | 如下: 52 | 53 | let date: Date = “2020-05-26 23:32:15” 54 | 55 | 感觉这是一个很好的使用场景。大家感兴趣的话也可以写一个试试~ 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /第七章:字符串/7.5 String的内部结构.md: -------------------------------------------------------------------------------- 1 | # 字符串 2 | 3 | ## 7.5 String的内部结构 4 | 书中String是```老版本的```。在swift 4 后 String又拥有了```集合```的特性。 5 | 这一节我直总结一下苹果的最新文档来讲。就不按照书上的说了。 6 | 7 | ##### 知识点1: 多行String : 用三个引号来扩住的就是 8 | 9 | let banner = """ 10 | __, 11 | ( o /) _/_ 12 | `. , , , , // / 13 | (___)(_(_/_(_ //_ (__ 14 | /) 15 | (/ 16 | """ 17 | 18 | ##### 知识点2:String能无缝转换成NSStirng的原因是NSString是不可变的,转化只是copy了String 解码给了NSStinrg。 19 | 20 | ##### 知识点3:emoji在不同的编码单元(utf8,utf16)中长度不同。 21 | let flag = "🇵🇷" 22 | print(flag.count) 23 | // Prints "1" 24 | print(flag.unicodeScalars.count) 25 | // Prints "2" 26 | print(flag.utf16.count) 27 | // Prints "4" 28 | print(flag.utf8.count) 29 | // Prints "8" 30 | 31 | -------------------------------------------------------------------------------- /第七章:字符串/7.6 编码单元的表示方式.md: -------------------------------------------------------------------------------- 1 | # 字符串 2 | 3 | ## 7.6 编码单元的表达方式 4 | 有时候我们需要操作编码单元来实现一些需求,如将字符渲染到UTF-8的网页中。 5 | 而且操作编码单元比操作字符效率会高很多。 6 | ##### 书中讲了一个demo 统计一句英文中的单词数量 7 | #### 这个也是我半年前去```拼多多```面试,iOS主管给我出的面试题。现在看完是豁然开朗。 8 | 话不多说,直接上源码 9 | ![alert text](https://raw.githubusercontent.com/Liaoworking/Advanced-Swift/master/pic/String_words.JPG) 10 | 11 | 其大概过程就是先将原来的字符串分割,分割结果不是Character,也不是String,而是```String.UnicodeScalarView的切片数组```,然后通过map转化成String达到想要的结果。 12 | 13 | ##### 那什么是unicodeScalars呢(目前先只做了解)? 14 | 我们可以使用unicodeScalars属性遍历一个Unicode标量编码的字符串。这个属性是 UnicodeScalarsView类型,UnicodeScalarsView是一个UnicodeScalar类型的集合。每一个Unicode标 量都是一个任意21位Unicode码位。 15 | 16 | #### 字符串和编码相关的可以参考[这篇文章](https://justcoding.iteye.com/blog/2077323) 17 | 18 | 19 | ##### 今天是2019年第一天 每一天都要加油鸭! 20 | 21 | -------------------------------------------------------------------------------- /第七章:字符串/7.7 CustomStringConvertible 和 CustomDebugStringConvertible.md: -------------------------------------------------------------------------------- 1 | # 字符串 2 | 3 | ## 7.7 CustomStringConvertible 和 CustomDebugStringConvertible 4 | 本小节内容基本简单,就是讲了这两个简单协议的用法 5 | 6 | ##### 这两个协议主要就是类似于Objective-C中的重写description方法 7 | 继承协议 实现```description``` 和 ```debugDescription``` 属性 即可打印出想要的数据内容 8 | 9 | 具体使用见下Demo 10 | 11 | struct Person:CustomStringConvertible,CustomDebugStringConvertible { 12 | var age: Int 13 | var name: String 14 | var job: String 15 | 16 | var description: String { 17 | return "\(age) \(name) \(job)" 18 | } 19 | 20 | var debugDescription: String { 21 | return "\(name) \(age) \(job)" 22 | } 23 | } 24 | 25 | let meetings = Person(age: 18, name: "liaoWorking", job: "iOSDeveloper") 26 | print(meetings) 27 | /** 28 | * "18 liaoWorking iOSDeveloper\n" 29 | */ 30 | debugPrint(meetings) 31 | /** 32 | * "liaoWorking 18 iOSDeveloper\n" 33 | */ 34 | 35 | ##### 知识点1: 书中建议任何稍微复杂一些的类型都应该实现 ```CustomStringConvertible```协议,日常debug调试可阅读性会好很多。 36 | 37 | 38 | -------------------------------------------------------------------------------- /第七章:字符串/7.8 文本输出流.md: -------------------------------------------------------------------------------- 1 | # 第七章 字符串 2 | ## C7P8 文本输出流 3 | 4 | ### 这一节主要讲了关于自定义一些print打印输出的两个重要协议 5 | ```TextOutputStream``` 和 ```TextOutputStreamable``` 就是标准库内置的一个标准```输出流协议```。 6 | 7 | 大概意思就是对你的```print方法做一些加工```。 8 | 9 | ##### 首先我们先看TextOutputStream 协议。 10 | 11 | 下面是标准库中对 TextOutputStream 协议的解释和说明,使用场景就是对我们打印的内容做一些转化 举例中的```ASCIILogger()``` 系统库中并不存在,只是做说明写的一个。 12 | 13 | /// The `ASCIILogger` type's `write(_:)` method processes its string input by 14 | /// escaping each Unicode scalar, with the exception of `"\n"` line returns. 15 | /// By sending the output of the `print(_:to:)` function to an instance of 16 | /// `ASCIILogger`, you invoke its `write(_:)` method. 17 | /// 18 | /// let s = "Hearts ♡ and Diamonds ♢" 19 | /// print(s) 20 | /// // Prints "Hearts ♡ and Diamonds ♢" 21 | /// 22 | /// var asciiLogger = ASCIILogger() 23 | /// print(s, to: &asciiLogger) 24 | /// // Prints "Hearts \u{2661} and Diamonds \u{2662}" 25 | public protocol TextOutputStream { 26 | 27 | /// Appends the given string to the stream. 28 | mutating func write(_ string: String) 29 | } 30 | 31 | 实现TextOutputStream协议只需要实现其```write```方法。 调用```print(“”, to: &打印转换的实例对象)```这个方法即可。 32 | 33 | ```ASCIILogger```结构体的具体实现 34 | 35 | struct ASCIILogger: TextOutputStream { 36 | mutating func write(_ string: String) { 37 | let ascii = string.unicodeScalars.lazy.map { scalar in 38 | scalar == "\n" 39 | ? "\n" 40 | : scalar.escaped(asASCII: true) 41 | } 42 | print(ascii.joined(separator: ""), terminator: "") 43 | } 44 | } 45 | 46 | 47 | 48 | #### 知识点: String ```默认实现```了TextOutputStream协议 49 | 50 | [swift官方文档](https://swiftdoc.org/v4.2/protocol/textoutputstream/)对TextOutputStream有很详细的说明,感兴趣的同学可以看看😄 51 | 52 | 53 | -------------------------------------------------------------------------------- /第七章:字符串/7.9 字符串的性能.md: -------------------------------------------------------------------------------- 1 | # 字符串 2 | 3 | ## 7.9 字符串的性能 4 | 5 | #### Swift String中有很多字符类型的属性 如```utf16(utf16View类型)```,```utf8(utf8View类型)```,```unicodeScalars(UnicodeScalarsView类型)```,```characters(已废弃)``` 6 | 7 | ##### 知识点:为什么是以View结尾的? 8 | 9 | 在api文档中对他们的介绍是:```字符串所包含的编码单元的视图``` (A view of a string's contents as a collection of code units. ) 10 | 11 | /// 你可以用不同的编码去遍历字符串中的每一个字符 12 | 13 | for codeUnit in "liaoWorking".utf16 { 14 | print("\(codeUnit) ") 15 | /** 16 | 108 17 | 105 18 | 97 19 | 111 20 | 87 21 | 111 22 | 114 23 | 107 24 | 105 25 | 110 26 | 103 27 | */ 28 | } 29 | 30 | 其中不同的字符类型属性效率不同 ```utf16效率最高``` 31 | 32 | 33 | 书中提到如果你能确保自己所做的操作可以```正确处理 UTF-16 的数据```,那选用 UTF-16 视图将会给你带来相当不错的```性能提升```。 34 | 35 | 这里有一篇关于[字符串不错的文章](https://justcoding.iteye.com/blog/2077323),推荐给大家。 36 | 37 | -------------------------------------------------------------------------------- /第三章:集合类型协议/3.1 序列.md: -------------------------------------------------------------------------------- 1 | # 集合类型协议 2 | --- 3 | ## 3.1序列: sequence 4 | #### 知识点1:想要创建一个自定义的序列 只要遵循```sequence```协议。并实现```makeIterator```方法。 5 | 6 | #### makeIterator方法的返回值是遵循```IteratorProtocol```的迭代器(是一个自定义类或者结构体)。 7 | 8 | #### 这样来说一个自定义序列的构建过程就是这样的 ```Iterator->Sequence->集合``` 9 | 10 | 11 | ### 迭代器:Iterator 12 | --- 13 | 里面只有一个方法```next( )``` 14 | 15 | public protocol IteratorProtocol { 16 | ///迭代器产生的值的类型 17 | ///如 subViews的迭代原生类型就是View 可以不用写,编译器根据next()的返回值自动判断。 18 | associatedtype Element 19 | 20 | ///你只需要在每次调用的时候返回下一个值,结束时返回nil 21 | public mutating func next() -> Self.Element? 22 | } 23 | 24 | next( )方法是用 ```mutating``` 关键字修饰。 几乎所有的迭代器都要求是```可变状态```,可以用于改变结构体中记录迭代器位置的变量值(如下面demo中的startNum) 25 | 26 | 27 | 下面我们用迭代器写一个斐波那契数列(0,1,1,2,3,5,8,13.....)。 28 | 29 | struct FibsNumIterator:IteratorProtocol { 30 | var startNum = (0, 1) 31 | mutating func next() -> Int? { 32 | let nextNum = startNum.0 33 | startNum = (startNum.1, startNum.0 + startNum.1) 34 | return nextNum 35 | } 36 | } 37 | 38 | 这个迭代器不返回nil, 就会产生无穷的数组。 程序会因为类型溢出而crash。 39 | 40 | 注:迭代器是```单向结构```,只能按照增加的方向前进,不能倒退或者重置。 41 | 42 | 43 | ### 准守序列(sequence)协议 44 | --- 45 | 下面我们通过一个打印一个字符串所有前缀的demo 通过```自定义迭代器IteratorProtocol```和```自定义集合Sequence```去写一个属于你的集合~ 46 | 47 | 48 | #### 第一步: 创建一个```迭代器```(Iterator) 49 | 50 | struct PrefixStrIterator:IteratorProtocol { 51 | var string: String 52 | var offset: String.Index 53 | init(string:String) { 54 | self.string = string 55 | offset = string.startIndex 56 | } 57 | ///写协议方法 58 | mutating func next() -> String? { 59 | guard offset < string.endIndex else { return nil} 60 | offset = string.index(after: offset) 61 | return String(string[string.startIndex.. PrefixStrIterator { 73 | return PrefixStrIterator(string: string) 74 | } 75 | } 76 | 77 | #### 第三步: run~ 78 | 79 | ///myfirstSquence 我的第一个集合 80 | for prefixStr in PrefixSequence(string: "Hi~LiaoWorking!") { 81 | print(prefixStr) 82 | // H 83 | // Hi 84 | // Hi~ 85 | // Hi~L 86 | // Hi~Li 87 | // Hi~Lia 88 | // Hi~Liao 89 | // Hi~LiaoW 90 | // Hi~LiaoWo 91 | // Hi~LiaoWor 92 | // Hi~LiaoWork 93 | // Hi~LiaoWorki 94 | // Hi~LiaoWorkin 95 | // Hi~LiaoWorking 96 | // Hi~LiaoWorking! 97 | } 98 | 99 | ### 迭代器和值语义 100 | --- 101 | 先说说值语义和引用语义,这两个定义无论在今后的学习还是日常开发细节中会经常遇到~ ```敲黑板!``` 102 | 103 | | | 作用对象 | 特性| 104 | |:-------:|:------------- | :----------| 105 | | 值语义 | struct, Int, String, Double等。 | 赋值时不存在引用,复制了一份过去了| 106 | | 引用语义 | class | 赋值时存在引用| 107 | 注:但```AnyIterator```这个是引用对象。。(协议章中的类型抹消的内容我们再具体说~) 108 | 109 | ### 基于函数的迭代器和序列 110 | --- 111 | 继续写一个Demo: ```AnyIterator```还有一个初始化方法就是直接接受next()函数来当做参数。然后通过引用语义的特性,我们可以不创建新的类型就写一个斐波那契迭代器 112 | 113 | /// 通过引用语义的特性写斐波那契 114 | func fibsIterator() -> AnyIterator { 115 | var startNum = (0, 1) 116 | return AnyIterator{ 117 | let nextNum = startNum.0 118 | startNum = (startNum.1 , startNum.0 + startNum.1) 119 | return nextNum 120 | } 121 | } 122 | 这个是高阶知识点,的确没有第二章的生动。了解一下也挺好啊~~ 123 | 124 | ### 无限序列 125 | --- 126 | 序列可以是```无限```的,而集合是```有限```的。 127 | 128 | ### 不稳定序列 129 | --- 130 | Sequence文档明确指出序列并不保证能被多次遍历。 131 | 原因是Sequence协议并不关心里面的序列元素会不会销毁。 132 | 133 | ##### 知识点1:这就是为什么在集合类型中.first 在序列中并不存在。 134 | 135 | ### 序列sequence和迭代器Iterator的关系 136 | 迭代器Iterator可以看成即将返回的元素组成的```不稳定```序列sequence 137 | 138 | 139 | ### 子序列 140 | --- 141 | Sequence还有一个关联类型 SubSequence 142 | 143 | 在返回原序列Sequence的```切片slice操作```中,SubSequence会被当做返回值的子类。 144 | 145 | SubSequence 有一些常用方法 146 | 147 | prefix 148 | suffix 149 | dropFirst 150 | dropLast 151 | split 152 | 153 | 在项目中其实经常会遇到SubSequence,如string的SubSequence等等。。 154 | -------------------------------------------------------------------------------- /第三章:集合类型协议/3.2集合类型.md: -------------------------------------------------------------------------------- 1 | # 集合类型协议 2 | 3 | ## 3.2集合类型:Collection 4 | --- 5 | ### 这一节只要目的是通过```自定义一个FIFO```(First Input First Output)的集合去了解一些细节。 6 | Collection是稳定的序列(可```多次遍历```且```保持一致```),可通过下标获取其中的元素。 7 | #### 知识点1:Collection遵循了```Sequence```协议。 8 | 9 | #### 知识点2:```String```,```Data```,```IndexSet```也准守了Collection协议。 10 | 11 | #### 知识点3:swift数组可以当做栈 用append入栈,popLast出栈 12 | 注:数组是在连续的内存中持有元素,移除非末尾元素时,后面的元素都会移动填补空白,复杂度为O(n)。 13 | 当你移除数组非末尾元素的时候需要从```性能方面```去考虑一下。 14 | #### 队列可以结合使用 push和remove(at:0) 15 | 16 | 17 | ### 为队列设计协议:Iterator 18 | --- 19 | /// 自己写一个最简单的将元素入队和出队的类型 20 | protocol Queue{ 21 | // self中所持有的元素类型 22 | associatedtype Element 23 | // 把newElement 加入队列 24 | mutating func enqueue(_ newElement: Element) 25 | // 从self出队一个元素 26 | // 返回值是可选值? 队列为空时这样的做法是安全的 27 | mutating func dequeue() -> Element? 28 | } 29 | 30 | 这就表述了```队列的定义```。 31 | 32 | 33 | ### 队列的实现 34 | 下面我们将准守上面的Queue协议,写一个FIFO( First Input First Output)队列 35 | 36 | /// FIFO( First Input First Output) 37 | struct FIFOQueue:Queue { 38 | 39 | fileprivate var left: [Int] = [] 40 | fileprivate var right: [Int] = [] 41 | 42 | ///把元素添加到队尾 43 | /// 复杂度O(1) 44 | mutating func enqueue(_ newElement: Int) { 45 | right.append(newElement) 46 | } 47 | 48 | 49 | /// 从队列首部移除一个元素 50 | /// 队列为nil时候返回空 51 | /// - 复杂度: 平摊 O(1) 52 | mutating func dequeue() -> Int? { 53 | if left.isEmpty { 54 | left = right.reversed() 55 | right.removeAll() 56 | } 57 | return left.popLast() 58 | } 59 | } 60 | 61 | 入队添加到"右", 62 | 出队逻辑直接看代码就成。 63 | 64 | 65 | 66 | 67 | ### 准守Collection协议 68 | --- 69 | 前面我们已经有一个FIFO队列了,下面可以为它添加Collection协议啦~ 在swift2.0的时候Collection协议有 70 | 4个关联类型 71 | 4个属性 72 | 7个实例方法 73 | 2个下标方法 74 | 75 | 在swift4.2中有略微改进。。具体可参照[Collection 苹果官方文档](https://developer.apple.com/documentation/swift/collection) 76 | 77 | 下面我们就开始让我们的FIFO去遵守Collection吧~ 78 | 79 | extension FIFOQueue:Collection { 80 | 81 | public var startIndex: Int { return 0 } 82 | public var endIndex: Int { return left.count + right.count} 83 | 84 | public func index(after i: Int) -> Int { 85 | precondition( i < endIndex) 86 | return i + 1 87 | } 88 | 89 | public subscript(position: Int) -> Int { 90 | precondition((0..失效可能有下面两种情况 17 | 1.索引本身有效,但指向了另外的元素 18 | 2.索引本身已经无效。 19 | 此时通过索引访问就会```崩溃``` 20 | 21 | 22 | ### 索引步进 23 | --- 24 | 数组里索引的步进是一些简单的```加法运算``` 25 | 而字符在swift中尺寸是可变的```(第七章第一节会提到)```。 26 | 所以从swift3开始索引的遍历有了很大的改变。主要是为了```性能``` 27 | 28 | 29 | ### 链表 30 | --- 31 | 一直以为日常的iOS开发中不会遇到链表。直到读到本书才发现是自己知识面狭隘。本小结内容稍多。放到最后来补齐这一节的集体内容。 32 | 33 | //mark- TODO 34 | 35 | 36 | -------------------------------------------------------------------------------- /第三章:集合类型协议/3.4切片.md: -------------------------------------------------------------------------------- 1 | # 集合类型协议 2 | 3 | ## 3.4切片:Slice 4 | 5 | ##### 所有的集合类型都有切片操作的默认实现。 6 | --- 7 | dropFirst的具体实现。 8 | 9 | ///下面的操作实际等于 [1,2,3,4,5].dropFirst() 10 | let list = [1,2,3,4,5] 11 | let onePastStart = list.index(after: list.startIndex) 12 | let firstDropped = list[onePastStart..```,并不是集合类型。 它是基于任意集合的一个```轻量级的封装``` 前面的笔记也有类似的提及。 16 | 17 | 18 | 19 | #### 知识点1:slice除了保存```对原有集合的引用```,还存储了切片边界的```开始索引```和```终止索引```。 20 | 21 | 22 | #### 知识点2:(书中原文写到)列表本身是由几个索引组成的。其切片大小是原列表的几倍 (具体为嘛是4倍我查资料没查到,还望知道的同学分享一下。) 23 | MemoryLayout.size(ofValue: [1,2,3,4,5]) //8 24 | MemoryLayout.size(ofValue: [1,2,3,4,5].dropFirst()) //32 25 | 26 | 27 | ### 自定义切片 28 | --- 29 | 可以通过自定义切片达到尺寸优化的功能。 30 | 31 | 切片与原集合共享存储区域,这样会带来一个问题: 32 | 如果我们将一个2GB的数据读入数组中,只取一个很小的切片,那么这整个2GB的缓冲区一直存在于内存中。直到集合和切片都销毁时才销毁。 33 | 在苹果文档中特别警告:```只应该把切片用作临时计算的目的 不应该长时间存在。``` 34 | 35 | 36 | 37 | ### 切片与原集合共享索引 38 | --- 39 | 40 | slice的角标不一定是以0开始的。如下例: 41 | 42 | let cities = ["shangHai", 43 | "Beijing", 44 | "NewYork", 45 | "Chicago", 46 | "Tokyo", 47 | "Hongkong"] 48 | 49 | let slice = cities[2...4] 50 | cities.startIndex //0 51 | cities.endIndex //6 52 | slice.startIndex //2 53 | slice.endIndex //5 54 | 55 | 这个时候我们访问 slice[0]就直接崩溃了。 56 | 这也就是在swift中遍历常使用 for ```obj``` in Array 而不是 for ```idx``` in Array 57 | 58 | -------------------------------------------------------------------------------- /第三章:集合类型协议/3.5专门的集合类型.md: -------------------------------------------------------------------------------- 1 | # 集合类型协议 2 | 3 | ## 3.5专门的集合类型:Special Collection 4 | 对于collection来说,有两个有意思的限制 5 | 1.无法将索引后退移动。 6 | 2.没有提供插入,移动,替换元素的功能。 7 | 虽然只是有一部分集合可以使用他们,但作为标准库。就只写一些通用的方法。 8 | 9 | 10 | 在标准库中有四个针对于collection 补充的协议 11 | 12 | 13 | //一个既可以前向又可以后向遍历的集合 14 | BidirectionalCollection 15 | //一个可以高效随机存取索引遍历的集合 16 | RandomAccessCollection 17 | //一个可以下标赋值的集合 18 | MutableCollection 19 | //一个可以将任意子范围的元素用别的集合中的元素进行替换的集合 20 | RangeReplaceableCollection 21 | 22 | 23 | #### BidirectionalCollection: 24 | 它提供了```sufix()```, ```removeLast()``` 和 ```reversed()``` 几个我们看起来很眼熟的函数很熟悉 25 | 26 | #### RandomAccessCollection 27 | 和BidirectionalCollection ```index(_:offsetBy:)```` 去通过渐进的方式去遍历, 相比 RandomAccessCollection 可以 ```直接在两个索引之间``来移动。 28 | 例如计算 startIndex 和 endIndex 的间距。 RandomAccessCollection可以在```常数时间内```计算出count,相对高效,而其他就会慢很多。 29 | 30 | #### MutableCollection 31 | 它支持原地的元素更改。 32 | 一般的集合只能改变集合的元素值,无法改变``集合的长度``或``元素的顺序``。 33 | MutableCollection 只多了一个必须要实现的方法```subscript``` ,而且必须要实现其内部的set方法 34 | ///协议方法 35 | public subscript(position: Self.Index) -> Self.Element { get set } 36 | 37 | 38 | 39 | public subscript(position: Element) -> Element { 40 | get { 41 | return Element 42 | } 43 | set { 44 | //必须要提供 45 | } 46 | } 47 | 48 | 49 | ~~注:编译器不会让我们向一个已经存在的Collection通过扩展```添加下标的setter方法``` 原因有二:~~ 50 | ~~1.要提供setter就要提供getter~~ 51 | ~~2.无法重新定义已存在的getter方法~~ 52 | ~~所以我们只能重新写一个协议去替换collection 所以要重新去写getter setter方法。~~ 53 | 54 | ##### 知识点1:集合类型 Array Dict Set 中只有```Array```满足这个协议。 55 | 56 | #### RangeReplaceableCollection 57 | 需要添加或者移除元素可以用这个协议 58 | 有两个要求: 59 | 1.一个```空的初始化方法```(在泛型函数中很好用,因为泛型允许一个函数创建相同类型的 新的空集合) 60 | 2.```replaceSubrange(_:with:)```方法 参数为范围和要替换的集合。 61 | 62 | #### 组合能力 63 | 我们可以将上面这些特殊的集合协议``组合起来```,来达到我们想要的效果。 64 | 65 | # 本章回顾 66 | 集合类型主要是由 ```Sequence``` 和 ```Collection``` 协议构成了 67 | -------------------------------------------------------------------------------- /第九章:泛型/9.1 重载 Overloading.md: -------------------------------------------------------------------------------- 1 | # 第九章:泛型 Generics 2 | 3 | 4 | ## 9.1 重载 Overloading 5 | 6 | ### 自由函数的重载 Overload Resolution for Free Functions 7 | 这一小段讲的主要是```泛型的重载```相关知识点 8 | 9 | /// 泛型打印view的相关信息 10 | /// 11 | /// - Parameter view: view 12 | func log(_ view: View) { 13 | print("It's a \(type(of: view)), frame: \(view.frame)") 14 | } 15 | 16 | ///对泛型的重写 17 | func log(_ view: UILabel) { 18 | let text = view.text ?? "(empty)" 19 | print("It's a label, text: \(text)") 20 | } 21 | 22 | let label = UILabel(frame: .zero) 23 | label.text = "liaoworking is handsome~~" 24 | let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 101)) 25 | 26 | log(label) //It's a label, text: liaoworking is handsome~~ 27 | log(button) //It's a UIButton, frame: (0.0, 0.0, 100.0, 101.0) 28 | 29 | 上面这个demo没毛病对吧,只要我们重载了一个泛型方法, 在打印对于的类型时就调用这个方法。 30 | 那我们看看上面方法的```下面的使用场景``` 31 | 32 | let views = [label, button] // Type of views is [UIView] for view in views { 33 | 34 | for view in views { 35 | log(view) 36 | } 37 | 38 | /* 39 | It's a UILabel, frame: (20.0, 20.0, 200.0, 32.0) 40 | It's a UIButton, frame: (0.0, 0.0, 100.0, 50.0) 41 | */ 42 | 43 | 咦~为嘛在for循环中就```无法去区分方法了```? 44 | 原因:泛型的重载在```编译时期```就确定的。并不是在runtime时候动态确定。 就会有上面的差异。 45 | 觉得疑惑的同学可以长按键盘的option,然后用鼠标点一下 ```views```,xcode的类型提示也会给你答案。 46 | 47 | ### 运算符的重载 Overload Resolution for Operators 48 | 49 | #### 场景:我们想要自己封装一个```幂运算```的方法 50 | 51 | // precedencegroup 运算符优先级定义关键字 52 | precedencegroup tt{ 53 | // 结合方向 54 | associativity: left 55 | // 运算优先级:高于乘法类型 56 | higherThan: MultiplicationPrecedence 57 | } 58 | // 定义中缀符 ** 对于的有前缀prefix,和后缀postfix OC中的i++就是后缀 59 | infix operator **: tt 60 | // 内部实现 61 | func **(lhs: Double, rhs: Double) -> Double { 62 | return pow(lhs, rhs) 63 | } 64 | func **(lhs: Float, rhs: Float) -> Float { 65 | return powf(lhs, rhs) 66 | } 67 | // 2乘以2的三次方。 68 | 2*2.0**3.0 69 | 70 | 71 | ##### 先写一个幂运算的泛型demo 72 | func **(lhs: i, rhs: i) -> i { 73 | let result = Double(lhs.max) ** Double(rhs.max) 74 | return numericCast(IntMax(result)) 75 | } 76 | 77 | 2**3 ///Error: Ambiguous use of operator 78 | 79 | 可在实际编译过程中却编译不过 80 | 81 | ```原因:``` 对于重载运算符,编译器会```使用非泛型```版本,不考虑泛型版本 82 | 83 | ```解决:``` 至少将```一个参数显示的声明```为Int类型,或者将```返回值声明```为Int类型即可 84 | 85 | let intResult: Int = 2 ** 3 //8 86 | 87 | 这种编译器的行为只是对```运算符```生效,swift团队对于```性能上的考量```选择了这种可以降低类型检查器的复杂度的方案。 88 | 89 | 90 | ### 使用泛型约束进行重载 91 | 92 | ##### 书中举的例子是检查一个集合是不是另外一个集合的```子集``` 93 | swift标准库中有一个方法```isSubSet```提供了这功能,但只是满足于Set这种满足了```SetAlgebra```协议的类型。 94 | 95 | 我们自己去实现这个功能时注意函数的复杂度,for循环遍历会使复杂度变成```O(m * n)```. 96 | 97 | 如果序列元素满足Hashable我们可以通过```Set(array)```数组转化为Set,再去用isSubSet去获得结果。 98 | 99 | 书中给出的最优解决方案是如下 100 | 101 | ///不需要是同一种类型 只需要other遵守Sequence的任意类型就可以了,其复杂度也是成线性增长。 102 | extension Sequence where Iterator.Element: Hashable {//泛型版本 103 | func isSubset(of other: S) -> Bool 104 | where S.Iterator.Element == Iterator.Element { 105 | let otherSet = Set(other) 106 | for element in self { 107 | guard otherSet.contains(element) else { 108 | return false 109 | } 110 | } 111 | return true 112 | } 113 | 114 | 这样我们写的函数就有```更多的可能性```,可以传入一个数字的countableRange来进行检查 115 | 116 | [5,4,3].isSubSet(of:1...10)//true 117 | 118 | 119 | ### 使用闭包对行为进行参数化---这小段的知识点对于```函数的封装```有很大的启发。 120 | 121 | 针对于上面的demo我们```二次引申```: 122 | 让isSubset针对于```不是Equatable```的类型也适用, 123 | 我们可以传一个返回值是bool的函数来表明元素是否相等。 124 | swift标准库中的```contains```方法就是这么做的 125 | 其具体使用如下: 126 | 127 | let isEven = {$0 % 2 == 0} 128 | (0..<5).contains(where:isEven) //true 129 | [1,3,5,7,9].contains(where:isEven) == false 130 | 131 | ##### 我们可以类似于contain的使用去写一个更灵活的isSubset 132 | 133 | extension Sequence { 134 | func isSubset(of other: S, 135 | by areEquivalent: (Iterator.Element, S.Iterator.Element) -> Bool) 136 | -> Bool { 137 | for element in self { 138 | guard other.contains(where: {areEquivalent(element, $0)}) else{return false} 139 | } 140 | return true 141 | } 142 | } 143 | 144 | 145 | 只要你提供闭包```能够处理的比较操作```,两个序列的元素就算```不是同种类型```的也可以进行对比。 146 | 147 | let ints = [1,2] 148 | let strings = ["1","2","3"] 149 | ints.isSubset(of:Strings) { String($0) == $1 } //true 150 | 151 | 这一节知识点有点繁琐。 没必要一次性弄懂,多体会慢慢在实际项目中运用就可以啦~ 152 | 153 | -------------------------------------------------------------------------------- /第九章:泛型/9.2 对集合采用泛型操作 Operating Generically on Collections.md: -------------------------------------------------------------------------------- 1 | #### 2 | # 第九章:泛型 Generics 3 | 4 | 5 | ## 9.2 对集合采用泛型操作 Operating Generically on Collections 6 | 本小节主要是通过泛型```二分法```,```序列随机排列```,```判断子序列```的位置的算法的几个Demo,来讲了一些应该注意的知识点。 7 | 8 | 本小节代码量有些多,不要求全部掌握。```通读一遍即可```,以后在实际开发中有遇到算法相关的泛型封装可以回来看看~ 9 | 10 | ### 二分法查找 A Binary Search 11 | 书中先对```RandomAccessCollection```协议随机存取协议(笔记第三章第五节有讲,忘了的同学可以回头看看) 封装一个二分法的extension方法, 12 | 13 | extension RandomAccessCollection where Index == Int, IndexDistance == Int{ //只满足Int的二分查找 14 | public func binarySearch(for value: Iterator.Element, 15 | areInIncreasingOrder: (Iterator.Element, Iterator.Element) -> Bool) 16 | -> Index? { 17 | var left = 0 18 | var right = count - 1 19 | 20 | while left <= right { 21 | let mid = (left + right) / 2 22 | let candidate = self[mid] 23 | 24 | if areInIncreasingOrder(candidate, value) { 25 | left = mid + 1 26 | }else if areInIncreasingOrder(value, candidate) { 27 | right = mid - 1 28 | }else {只有左右两个相等才会返回中间值 29 | return mid 30 | } 31 | } 32 | return nil 33 | } 34 | } 35 | let binaryInt = [1,3,2,6,4] 36 | let bin = binaryInt[1..<3] 37 | bin.binarySearch(for: 2, areInIncreasingOrder: <) 38 | print(binaryInt ?? 000) 39 | 40 | 但会引入一个```严重的bug```,并不是所有的集合都以整数为索引的,Dictionary、Set、String都有自己的索引类型。 41 | 还有一个是以整数为索引的并```不一定都以0开始``` 例如 Array[3..<5] 的startIndex就是3 ,如果使用就会在运行时```崩溃```。 为此我们进行二次修改。 42 | 43 | ### 泛型二分查找 Generic Binary Search 44 | 我们二次修改上面的方法,来满足泛型的要求 45 | 46 | 泛型二分查找 47 | extension RandomAccessCollection { 48 | public func binarySearch(for value: Iterator.Element, 49 | areInIncreasingOrder: (Iterator.Element, Iterator.Element) -> Bool) 50 | -> Index? { 51 | guard !isEmpty else { 52 | return nil 53 | } 54 | / left和right不再是整数类型了,而是对于的索引值 55 | var left = startIndex 56 | var right = index(before: endIndex) 57 | while left <= right { 58 | let dist = distance(from: left, to: right) 计算left到right的距离 59 | let mid = index(left, offsetBy: dist/2) 60 | let candidate = self[mid] 61 | 62 | if areInIncreasingOrder(candidate, value) { 63 | left = index(after: mid) 64 | }else if areInIncreasingOrder(value, candidate) { 65 | right = index(before: mid) 66 | }else { 67 | return mid 68 | } 69 | } 70 | return nil 71 | } 72 | } 73 | extension RandomAccessCollection where Iterator.Element: Comparable { 74 | func binarySearch(for value: Iterator.Element) -> Index? { 75 | return binarySearch(for: value, areInIncreasingOrder: <) 76 | } 77 | } 78 | 79 | let a = ["a", "c", "d", "g"] 80 | let r = a.reversed() 81 | r.binarySearch(for: "d", areInIncreasingOrder: >) == r.startIndex 82 | let s = a[1..<3] 83 | s.startIndex 84 | s.binarySearch(for: "d") 85 | 86 | 这样对二分法的泛型改造就结束啦~ 87 | 88 | ### 集合随机排列 Shuffing Collections 89 | 我们先用代码实现一下 [Fisher-Yates](https:en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)洗牌算法。 90 | 91 | /集合随机排列 92 | extension Array { 93 | mutating func shuffle(){ 94 | for i in 0..<(count - 1) { 95 | let j = Int(arc4random_uniform(UInt32(count - i))) + i 96 | guard i != j else {保证不会将一个元素与自己交换 97 | continue 98 | } 99 | 100 | self.swapAt(i, j)将两个元素交换 101 | } 102 | } 103 | 104 | func shuffled() -> [Element] {不可变版本 105 | var clone = self 106 | clone.shuffle() 107 | return clone 108 | } 109 | } 110 | 111 | 112 | 和前面的二分法算法一样,这里还是```依赖整数索引```才能使用。 我们用泛型进行二次改造 113 | 114 | extension MutableCollection where Self: RandomAccessCollection { 115 | mutating func shuffle() { 116 | var i = startIndex 117 | let beforeEndIndex = index(before: endIndex) 118 | while i < beforeEndIndex { 119 | let dist = distance(from: i, to: endIndex) 120 | let randomDistance = IndexDistance.distance(dist) 121 | let j = index(i, offsetBy: randomDistance) 122 | guard i != else { 123 | continue 124 | } 125 | swap(&self[i], &self[j] 126 | formIndex(after: &i)) 127 | } 128 | } 129 | } 130 | 131 | ///二次封装 132 | extension Sequence { 133 | func shuffled() -> [Iterator.Element] { 134 | var clone = Array(self) 135 | clone.shuffle() 136 | return clone 137 | } 138 | } 139 | 140 | 141 | ### Subsequence和泛型算法 SubSequence and Generic Algorithms 142 | 这一小结最后一个Demo。。 143 | 144 | #### 求子集合的位置。 145 | 146 | extension Collection where Iterator.Element: Equatable { 147 | func search(for pattern: Other) -> Index? 148 | where Other.Iterator.Element == Iterator.Element { 149 | return indices.first(where: { (idx) -> Bool in 150 | suffix(from: idx).starts(with: pattern) 151 | }) 152 | } 153 | } 154 | let text = "it was the best of times" 155 | text.search(for: ["w","a","s"]) 156 | 157 | 注:如果知道被搜索的序列和待搜索的序列都满足随机存取(RandomAccessCollection)的索引,我们可以二次优化。 158 | 159 | extension RandomAccessCollection 160 | where Iterator.Element: Equatable, 161 | Indices.Iterator.Element == Index, 162 | SubSequence.Iterator.Element == Iterator.Element, 163 | SubSequence.Indices.Iterator.Element == Index { 164 | func search(for pattern: Other) -> Index? 165 | where Other.IndexDistance == IndexDistance, 166 | Other.Iterator.Element == Iterator.Element{ 167 | //如果匹配集合比原集合长 直接退出 168 | guard !isEmpty && pattern.count <= count else { 169 | return nil 170 | } 171 | //否则可以取到容纳匹配模式的最后一个索引。 172 | let stopSearchIndex = index(endIndex, offsetBy: -pattern.count) 173 | //检查从当前位置切片是否可以以匹配模式开头。 174 | return prefix(upTo: stopSearchIndex).indices.first(where: { (idx) -> Bool in 175 | suffix(from: idx).starts(with: pattern) 176 | }) 177 | } 178 | } 179 | let numbers = 1..<100 180 | numbers.search(for: 80..<90) 181 | 182 | 可能单纯的看一遍并不能很好的理解,感兴趣的小伙伴可以用playground写一下试试。 183 | 184 | overover~ 185 | 186 | 187 | -------------------------------------------------------------------------------- /第九章:泛型/9.3 使用泛型进行代码设计Designing with Generics.md: -------------------------------------------------------------------------------- 1 | # 第九章:泛型 Generics 2 | 3 | ## 9.3 使用泛型进行代码设计 Designing with Generics 4 | 本小节就比较```实用```了,对```网络请求以及数据转模型```的实际场景使用了```泛型的二次封装```。 5 | 这样的编程思想还是很酷的,我们也可以在自己的项目中推广一下 6 | 7 | ##### 普通的网络请求已经数据转模型的代码 8 | 9 | func loadUsers(callback: ([User]?) -> ()) { 10 | ///获取数据 11 | let usersURL = webserviceURL.appendingPathComponent("/users") let data = try? Data(contentsOf: usersURL) 12 | ///数据转模型 13 | let json = data.flatMap { 14 | try? JSONSerialization.jsonObject(with: $0, options: []) } 15 | let users = (json as? [Any]).flatMap { jsonObject in jsonObject.flatMap(User.init) 16 | } 17 | ///回调 18 | callback(users) 19 | } 20 | 21 | 需要注意的是 22 | 23 | 1️⃣网络请求可能失败 24 | 25 | 2️⃣JSON解析可能失败 26 | 27 | 3️⃣数据转模型可能失败 28 | 这三种情况都可能会返回nil, 可以通过flapMap来排除nil的情况 29 | 30 | ### 提取共通功能 31 | 上面的Demo是对[User]模型的网络请求, 如果我们需要请求订单量[Bill]的网络请求,又需要写一个新的方法重复写很多代码。 32 | ```这样不可取``` 33 | 我们可以把函数中的User提取出来, URLString作为参数传入来封装一个泛型方法 loadResource, 声明为``` A ```的泛型 34 | 35 | func loadResource
(at path: String, parse: (Any) -> A?, callback: (A?) -> ()) 36 | { 37 | let resourceURL = webserviceURL.appendingPathComponent(path) let data = try? Data(contentsOf: resourceURL) 38 | let json = data.flatMap { 39 | try? JSONSerialization.jsonObject(with: $0, options: []) } 40 | callback(json.flatMap(parse)) 41 | } 42 | 43 | 44 | 封装完毕之后我们对请求[User]的网络请求就可以这样写了 45 | 46 | func loadUsers(callback: ([User]?) -> ()) { 47 | loadResource(at: "/users", parse: jsonArray(User.init), callback: callback) 48 | } 49 | 50 | 上面用了一个辅助函数,```jsonArray``` 主要作用是: 它首先尝试将一个 Any 转换为一个 Any 的数组,接着 对每个元素用提供的解析函数进行解析,如果期间任何一步发生了错误,则返回 nil: 51 | 52 | func jsonArray(_ transform: @escaping (Any) -> A?) -> (Any) -> [A]? 53 | { 54 | return { array in 55 | guard let array = array as? [Any] else { return nil 56 | } 57 | return array.flatMap(transform) } 58 | } 59 | 60 | ### 二次优化--创建泛型数据类型 61 | 62 | 上面的loadResource函数中 path 和 parse ```耦合非常紧``` , 一旦path改变了 parse也会一起改变。 63 | 我们可以封装成一个```结构体```,来```描述要加载的资源``` 64 | 65 | struct Resource { 66 | let path: String 67 | let parse: (Any) -> A? 68 | } 69 | 70 | 接下来 我们可以给 我们已经封装好的结构体 Resource 结构体来定义一个```extension``` 让Resource 直接调用网络请求数据转模型的方法 71 | 72 | extension Resource { 73 | func loadSynchronously(callback: (A?) -> ()) { 74 | let resourceURL = webserviceURL.appendingPathComponent(path) 75 | let data = try? Data(contentsOf: resourceURL) 76 | let json = data.flatMap { 77 | try? JSONSerialization.jsonObject(with: $0, options: []) } 78 | callback(json.flatMap(parse)) } 79 | } 80 | 81 | 82 | #### 最终我们封装好的战果😎~ 83 | ///网络请求一个用户模型数组就变的如此方便。 84 | let usersResource: Resource<[User]> = Resource(path: "/users", parse:jsonArray(User.init)) 85 | userResourse.loadSynchronously = { userModels in 86 | ///我取到了我想要的 userModels 模型数组啦~~ 87 | } 88 | 89 | 90 | ##### 感悟 91 | 这一小节是真的有用,对于我们实际开发及代码风格有很大的启发。 下次代码优化的时候或许可以试试这样的封装思想。 92 | 93 | -------------------------------------------------------------------------------- /第九章:泛型/9.4 泛型的工作方式(How Generics Work) .md: -------------------------------------------------------------------------------- 1 | # 第九章:泛型 Generics 2 | 3 | ## 9.4 泛型的工作方式(How Generics Work) 4 | 本小节讲了一些关于泛型```底层```的知识点和一些```特殊情况下```的使用。可以当做扩充知识面去学习。 5 | 6 | #### 我们先看看一个最简单的泛型函数的底层实现 7 | 8 | func min(_ x: T, _ y: T) -> T { 9 | return y Int { 39 | returny(_ x: T, _ y: T) -> T { 73 | returny Bool in 46 | if obj == "I"{ 47 | return true 48 | } 49 | return false 50 | }) { 51 | print("\(idx)")//7 52 | } 53 | 5.所有元素进行变形 array.```map``` {someTransformation($0)} 54 | 55 | demoArray = demoArray.map { (obj) -> String in 56 | return "hi~\(obj)" 57 | } 58 | for obj in demoArray { 59 | print(obj)// hi~🌰 60 | } 61 | 62 | 6.筛选符合某个标准的元素 array.```filter``` {someCriteria($0)} 63 | 64 | demoArray = demoArray.filter { (obj) -> Bool in 65 | if obj == "🌰" || obj == "🍎" || obj == "I"{ 66 | return true 67 | }else{ 68 | return false 69 | } 70 | } 71 | print(demoArray)//["🌰", "🍎", "I"] 72 | 73 | 7.两个数组变形合并 ```flatMap``` 74 | 75 | let fruit = ["🍎","🍐","🍌"] 76 | let animal = ["🐷"] 77 | 78 | let result = fruit.flatMap { (f) -> [String] in 79 | let newArray = animal.map({ (a) -> String in 80 | return (a+"eat"+f) 81 | }) 82 | return newArray 83 | } 84 | print(result) //["🐷eat🍎", "🐷eat🍐", "🐷eat🍌"] 85 | 86 | ##### 感悟:```swift不鼓励你去做索引计算(书中原话。。)``` 当你在项目中写道array[idx]时,可以思考一下有没有更好的方法去解决。 87 | 88 | 89 | ### 切片 slice: 90 | 获取某个范围中的元素,我们可以使用```切片``` 91 | 例如:获取除了第一个元素以外的元素集合 92 | 93 | let fruit = ["🍎","🍐","🍌"] 94 | let slice = fruit[1.. 97 | 98 | ##### 我们得到的类型是```ArraySlice``` 而不是Array,其实切片只是Array的一种表现形式。我们在开发过程中可以把它当做数组来看。 类型转换直接Array(```ArraySlice```)就ojbk~ 99 | 100 | -------------------------------------------------------------------------------- /第二章:内建集合类型/2.2 字典.md: -------------------------------------------------------------------------------- 1 | # 内建集合类型 2 | 3 | ## 2.2字典 4 | ### 字典的可变性: 5 | var dict = ["name":"liaoWorking","age":"17"] 6 | 7 | 8 | #### 知识点1:书中提到了可以用updateValue(_:forKey:) 获取到```更新新值之前如果有值的旧值```。 9 | 10 | /// 得到更新键值对之前的值 updateValue 11 | let oldValue = dict.updateValue("18", forKey: "age") 12 | print(oldValue) // Optional("17") 13 | print(dict["age"]) //Optional("18") 14 | 15 | 16 | 注:这个方法在实际项目中的使用场景还没怎么想到😂 欢迎大家补充场景。 我也是看书之后才发现还有这种操作。 17 | 18 | 19 | ### 有用的字典方法 20 | 21 | 字典的合并 ```merge``` 22 | 23 | let newDict = ["name":"Jane","age":"19","gender":"M"] 24 | dict.merge(newDict) { (dictValue, newDictValue) -> String in 25 | print(dictValue) // liaoworking 相同key时候的dictValue 26 | print(newDictValue) //Jane 相同key时候的newDictValue 27 | 28 | return newDictValue //返回你觉得应该选择的value 我这里默认都是newDictValue 29 | } 30 | print(dict)["name": "Jane", "age": "19", "gender": "M"] 31 | 32 | 注:闭包里面的处理是逻辑是当两个dict 有相同的key return出我们觉得合适的value. 33 | 34 | 35 | 字典value的处理 ```mapValues``` 36 | 37 | 38 | ///字典的map方法 39 | let mapDict = dict.mapValues { (value) -> String in 40 | return "new"+value 41 | } 42 | print(mapDict)//["name": "newliaoWorking", "age": "new18"] 43 | 44 | 45 | 这里的使用逻辑和数组中的map类似 不赘述。 46 | 47 | ### Hashable 要求 48 | #### 知识点1:字典的本质是哈希表,通过键的hashValue来确定每个键的位置,所以key必须要准守Hashable协议。 49 | ##### 关于hashable的知识点 这里推荐[nshipster的一篇文章](https://nshipster.cn/hashable/) 50 | 51 | -------------------------------------------------------------------------------- /第二章:内建集合类型/2.3 set 2.4 Range .md: -------------------------------------------------------------------------------- 1 | # 内建集合类型 2 | 3 | ## 2.3 Set 4 | 5 | ### 基本定义:包含```无序```且```不会重复```的元素集合。可以看成是只有key没有value的字典。 6 | 7 | ```项目使用场景```: 我现在的项目中有一个需求是保存用户学习课程的日期信息。用set的形式去存储这些信息最为方便。。 8 | 9 | ### 集合代数: 10 | 和数学上的集合概念类似,set也有交并补的方法 11 | 12 | ```并集``` union() 13 | ```交集``` intersection() 14 | ```补集``` subtracting() 15 | 16 | 使用方法比较简单,这里直接把三个方法写到一起了 17 | 18 | 19 | let numSet:Set = [1,2,3,4,5] 20 | let otherSet:Set = [2,6] 21 | 22 | ///并集 23 | let unionSet = numSet.union(otherSet) 24 | print(unionSet) //[5, 6, 2, 3, 1, 4] 25 | 26 | ///交集 27 | let intersectionSet = numSet.intersection(otherSet) 28 | print(intersectionSet) //[2] 29 | 30 | ///补集 31 | let subtractingSet = numSet.subtracting(otherSet) 32 | print(subtractingSet) //[5, 3, 1, 4] 33 | 34 | 35 | ### 索引集合和字符集合 36 | --- 37 | 38 | Foundation框架中实现了SetAlgebra协议(拥有Set能力的协议)的一共有三类:```Set``` ```IndexSet``` ```CharacterSet``` 39 | 40 | ```SetAlgebra``` 可以做什么? 41 | 提供了一些基础的数学运算:如 ```==``` 、```contains```(是否包含),```交并补```、```subtract```(剔除)等。 42 | 43 | [apple官方文档](https://developer.apple.com/documentation/swift/setalgebra?changes=_2) 44 | 45 | #### IndexSet 46 | --- 47 | 表示一个有正整数组成的集合,我们其实可有用Set 达到同等效果。 48 | 但IndexSet更加 ```高效``` ,内部使用了 ```一组范围列表``` 进行实现。 49 | eg. 50 | 51 | 存储1到1000 52 | set可能是[1,2,3,4,......,1000] 53 | IndexSet的内部只是真正存储了1,1000 首位和末位两个数字。 它会存储```连续的范围```。So 会更加高效 54 | 55 | 56 | #### CharacterSet 57 | --- 58 | ##### 是一个高效存储 ```Unicode``` 的字符集合。后面讲String的时候会具体讲的~ 59 | 60 | ## 2.4 Range 61 | 62 | 定义:```两个值的区间``` 63 | ```..<``` 表示不包含上边界的半开范围 64 | ```...``` 表示包含上下边界的闭合范围 65 | 66 | let singleNum = 0..<10//不包括10 67 | 68 | let lowerLetters = Character("a")...Character("z")//包括z 69 | 70 | 注: 上面创建的两个都是```可数范围(有限个数)```CountableRange。这种类型类型是可以被迭代的。 71 | -------------------------------------------------------------------------------- /第五章:结构体和类/5.1_2值类型_可变性.md: -------------------------------------------------------------------------------- 1 | # 结构体和类 2 | ```类 class```是引用类型 3 | 4 | ```结构体 struct```、```枚举 enum``` 都是值类型 5 | 6 | 7 | class中,我们可以通过```继承来共享代码。``` 8 | 9 | struct、enum```无法继承```。正好符合swift的```面相协议编程```的特性。 10 | 11 | 12 | ## 5.1值类型(value type) 13 | 定义:将内存存储在栈内(这句我百度的),持有者唯一的类型。 14 | 通俗的来说就是:值变量被复制时,本身会被复制,而不是引用被复制。 15 | ps. 这里讲的比较抽象。前面在讲[迭代器](https://github.com/Liaoworking/Advanced-Swift/blob/master/%E7%AC%AC%E4%B8%89%E7%AB%A0%EF%BC%9A%E9%9B%86%E5%90%88%E7%B1%BB%E5%9E%8B%E5%8D%8F%E8%AE%AE/3.1%20%E5%BA%8F%E5%88%97.md#%E8%BF%AD%E4%BB%A3%E5%99%A8%E5%92%8C%E5%80%BC%E8%AF%AD%E4%B9%89)的时候已经有讲过了。忘却的同学可以回头看看~ 16 | 17 | ##### 那么什么时候使用值类型?: 18 | 19 | 我们```不用关心其生命周期```的优先使用值引用 20 | 21 | 当我们改变复制出来的struct对象时,只能改变它自己的复制,这个就叫做```值语义 (value semantics)``` 22 | 23 | 对于class对象来说,它是通过```传递引用```来工作了,所以可以有多个持有者。 24 | 25 | ##### 值引用的优点: 26 | 27 | ```不可能存在循环引用!!!```(因为它只有一个持有者) 这个就很酷了,可以在项目中少很多考虑。 28 | 29 | ## 5.2可变性 30 | 很多bug的起因都是```可变性```引起的 如下面的demo。 31 | Swift 可以让我们在写出```安全代码```的同时,保留直观的```可变代码的⻛格``` 32 | 33 | ///边遍历边操作数组是危险的 这里会崩溃 34 | var mArray:NSMutableArray = [1,2,3,4,5,6,7,8] 35 | for _ in mArray { 36 | mArray.removeAllObjects() 37 | } 38 | 39 | ///下面是安全的 removeAllObjects方法调用了8次 40 | ///因为不论如何移除,数组的迭代器的复制依然持有最 开始的三个元素。 41 | 42 | var array:[Int] = [1,2,3,4,5,6,7,8] 43 | for _ in array { 44 | mArray.removeAllObjects() 45 | } 46 | print(mArray.count) //0 47 | 48 | 49 | -------------------------------------------------------------------------------- /第五章:结构体和类/5.3 结构体.md: -------------------------------------------------------------------------------- 1 | # 结构体和类 2 | 3 | 4 | 5 | ## 5.3结构体(struct) 6 | ##### 知识点: swift会自动会结构体的```成员变量添加初始化方法```。如下: 7 | 8 | struct Point { 9 | var x:Int 10 | var y:Int 11 | } 12 | 13 | let p = Point(x: 1, y: 1) 14 | ///因为是指语义,所以用let定义结构体变量时,无法去改变变量的属性 15 | p.x = 10 //error 16 | 17 | 18 | 在swift5.1以后如果结构体的常量```有默认值```。 19 | 系统会提供有默认值属性和没有默认值属性的两种初始化方法 20 | 21 | struct People { 22 | var name: String 23 | var age: String 24 | var school: String = "DHU" 25 | } 26 | 27 | People(name: String, age: String, school: String) 28 | People(name: String, age: String) 29 | 30 | 31 | tips: 如若有一个经常使用的结构体值,可以以```静态属性定义在扩展内```,如下 32 | 33 | extension Point { 34 | static let origin = Point(x:0, y:0) 35 | } 36 | Point.origin 37 | 38 | 结构体中还能```包含其他结构体属性```。 39 | 40 | ##### 知识点2: 41 | 42 | 结构体的一个属性被赋值时, 它的didSet方法会被调用。 43 | 44 | var xPoint = Point(x: 8, y: 0){ 45 | didSet{ 46 | print("哇哦~我被调用了") 47 | } 48 | } 49 | 50 | xPoint.x = 10//哇哦~我被调用了 51 | 52 | 被调用的原因: 结构体的某个深层次的属性被改变时,等于是改变了结构体,```重新为它赋值```。 所以 didSet会被调用。 53 | 54 | 知识点3:在swift中 ```Array```是结构体 当改变数组中某个元素的属性时 数组的didSet也会被调用。 55 | 56 | ### 操作符重载 57 | 将两个结构体相加,可以对+操作符进行```重载``` 58 | 59 | static func +(lhs: Point, rhs: Point)-> Point{ 60 | return Point(x: lhs.x + rhs.x, y: lhs.y + rhs.y) 61 | } 62 | 63 | let y = Point(x: 10, y: 1) + Point(x: 1, y: 10) // Point(x: 11, y: 11) 64 | 65 | 这里的重载为嘛会用```static``` 查资料还没有查到,知道的同学可以补充上来 thx~ 66 | 67 | 68 | 69 | 知识点4:struct中如果需要改变self,或者需要```改变self中任何属性```。 则需要使用```mutating``` 关键字 70 | 71 | extension Point { 72 | mutating func translateY(by offset: Int) { 73 | ///这里的y实质上等于是self.y 74 | y = y + offset 75 | } 76 | } 77 | 78 | mutating 关键字的作用就是标记```self是可以改变的``` 79 | 80 | 知识点5:swift会自动将属性的```setter```标记为mutating 81 | 82 | 知识点6:很多情况下 一个方法会有可变和不可变的区分 83 | 如数组的 84 | ```sort()``` 是可变方法 85 | ```sorted()``` 不可变方法会返回一个新的数组。 86 | swift中还有很多类似的方法。(感觉这里可以出一个很好的面试题,说说两个方法的区别。) 87 | 88 | ### 了解mutating关键字 89 | 了解mutating关键字要先了解```inout```行为。 90 | 91 | func triple(x:Int){ 92 | x = x * 3 //error x is let 93 | } 94 | ///inout 关键字可以将原来的值覆盖 95 | func triple( x:inout Int) { 96 | x = x * 3 // ojbk 97 | } 98 | 99 | mutating 关键字实质上就是```隐式的将self标记成inout了``` cool~ 100 | 101 | 所以我们就明白为什么 重载 ```+=``` 左边的参数会被标记成```inout```了。(划重点 这个可以当成面试题。) 102 | 103 | 104 | -------------------------------------------------------------------------------- /第五章:结构体和类/5.4 写时复制.md: -------------------------------------------------------------------------------- 1 | # 结构体和类 2 | 3 | 4 | 5 | ## 5.4写时复制 6 | #### 定义:结构体的引用在改变的一瞬间是唯一的,不会有复制发生,内存的改变将在原地进行。 7 | 不太好理解的话我们一起看下面这个demo 8 | 9 | var x = [1,2,4] 10 | var y = x 11 | x.append(5) //1,2,4,5 12 | y.removeLast() //1,2 13 | 14 | 这时,把x赋值给y时会发生复制。这时候两个数组的```引用```指向的是```内存中的同一个位置```。共享存储部分。 15 | 当```改变x```时这个共享会被检查到。 16 | 内存将会被复制出来。 17 | 我们就独立的改变了两个变量。 18 | 耗性能的元素复制操作只会在```必要的时候```发送。这个就叫做```写时复制```。 19 | 20 | ####通俗来说: 复制时用的是一个内存地址,当某一个集合改变时恰到好处的复制了一份出来。 21 | 22 | 已经用```官方```,```非官方```,```简洁```的语句解释了三遍了朋友我相信你肯定懂的! 23 | 24 | 25 | 26 | 27 | ##### 知识点: 复制结构体变量。里面进行的浅复制。对象本身不会被复制。只有引用会被复制。 28 | 29 | ### 写时复制的高效方法 30 | ##### 1.首先要知道一个对象是否是唯一引用的,通过系统提供的isKnownUniquelyReferenced(&obj)函数来获取。 31 | //会返回一个Bool值告诉你是否唯一 对于OC的对象直接返回false 32 | isKnownUniquelyReferenced(&object: T) 33 | 34 | final class Box { 35 | var unbox:A 36 | init(_ value:A) { 37 | self.unbox = value 38 | } 39 | } 40 | 41 | var a = Box(NSMutableData()) 42 | isKnownUniquelyReferenced(&a)//true 43 | var b = a 44 | isKnownUniquelyReferenced(&a)//false 45 | isKnownUniquelyReferenced(&b)//false 46 | 47 | ##### 2.然后判断为false时执行写是复制(这里详读了一遍感觉比较艰深晦涩,大家可在参考下面的Demo理解)。 48 | struct MyData { 49 | fileprivate var _data: Box 50 | var _dataForWriting: NSMutableData { 51 | mutating get { 52 | if !isKnownUniquelyReferenced(&_data) {//检查对_data的引用是否是唯一性 53 | _data = Box(_data.unbox.mutableCopy() as! NSMutableData) 54 | print("Making a copy") 55 | } 56 | return _data.unbox 57 | } 58 | } 59 | init(_ data: NSData) { 60 | self._data = Box(data.mutableCopy() as! NSMutableData) 61 | } 62 | } 63 | 64 | extension MyData { 65 | mutating func append(_ other: MyData) { 66 | _dataForWriting.append(other._data.unbox as Data) 67 | } 68 | } 69 | 70 | let someBytes = MyData(NSData(base64Encoded: "wAEP/w==", options: [])!) 71 | var empty = MyData(NSData()) 72 | var emptyCopy = empty 73 | for _ in 0..<5 { 74 | empty.append(someBytes) 75 | 76 | } 77 | empty // 78 | emptyCopy // <> 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /第五章:结构体和类/5.5_6 闭包和可变性_内存.md: -------------------------------------------------------------------------------- 1 | # 结构体和类 2 | 3 | ## 5.5 闭包和可变性 4 | 5 | #### 知识点:结构体的存储位置。 6 | 默认情况下结构体会存储在```堆上```,但绝大多数时候swift会对其```优化```,把结构体存储到```栈上```。 7 | 如果结构体的变量被一个函数```闭合(内部使用)```了,这个结构体就会存放在```堆上```。 8 | 结构体```太大```也会被存在```堆上``` 9 | 10 | ## 5.6 内存 (这一节主要讲循环引用相关的) 11 | ---- 12 | #### 知识点: 标准库中大部分是结构体或者枚举。因为只有一个持有者。一般情况下是不用考虑循环引用的 13 | 14 | ### weak 引用 15 | --- 16 | 这里就讲了一些循环引用的基本知识点,我相信大家都明明白白的,不赘述。 17 | #### 知识点: delegate 用 weak关键字修饰(同OC) 18 | 19 | #### 在闭包中的使用方法 20 | 21 | ///当闭包没有参数时 不要下面的 para 22 | closure = {[weak self] para in 23 | self?.func() 24 | } 25 | 26 | ### unowned 引用 27 | --- 28 | ##### 作用:```不持有```引用对象,但这个属性会一直有效(意思就是unowned 修饰的属性一定要是不为nil的) 29 | 如果确实一定有值,这个属性就不应该是可选值,这个时候可以用unowned关键字。 30 | 31 | ### tips: 32 | --- 33 | 实际项目中如果对unowned 和weak 拿捏的不太准的话```鼎力推荐使用weak !!!```,我们项目里面已经没有unowned self 这样的用法了。 34 | 我之前项目中一直的一个崩溃就是 ```用[unwoned self] 修饰self 但self可能为nil。``` 导致直接崩溃。 35 | 36 | -------------------------------------------------------------------------------- /第五章:结构体和类/5.7_8 闭包和内存.md: -------------------------------------------------------------------------------- 1 | # 结构体和类 2 | 3 | 4 | 5 | ## 5.7 闭包和内存(结构体和类的使用实践) 6 | #### 本节总结:主要是用了```类```、```结构体```、```纯函数```来表示一个银行账户的资金情况。 7 | 最后的结论: 8 | ```类```:线程不安全。 9 | ```结构体```:稳定,也不啰嗦 10 | ```函数```:线程安全,但程序会变得啰嗦 11 | 12 | 13 | ## 5.8 闭包和内存(引用循环和捕获列表) 14 | #### 知识点:闭包会引起循环引用(该知识点同OC的block的循环引用,不赘述。) 15 | ### weak 引用 16 | 这里就讲了一些循环引用的基本知识点,我相信大家OC过来的同学都明明白白的。大意是可以用[weak self]的捕获列表解决这个问题。 17 | #### 名词: 捕获列表 18 | 咋一看让人很费解,那什么是```捕获列表```: 19 | 闭包里面对```周围的常量或变量```进行的操作叫```捕获```。 20 | 我们可以通过捕获列表去```显示的控制```在闭包中的```捕获值```。 21 | 闭包中的```in前面的小框框[]``` 就是捕获列表,里面的值就是捕获列表的值。 22 | 23 | ##### 关于捕获列表下面有一个有趣的测试题。 如果不看答案你能猜出最后打印出来的 a 和 b 的值吗? 24 | 25 | 26 | var a = 0 27 | var b = 0 28 | /// a 就在捕获列表中,b没有 29 | let closure = { [a] in 30 | print(a, b) 31 | } 32 | 33 | a = 10 34 | b = 10 35 | closure() 36 | // Prints "0 10" 37 | 38 | ```原因```:当你使用了捕获列表之后,你无论怎么在闭包外面操作改变原始的值。闭包并不关心。因为这个时候它已经不是捕获的引用了,而是最初原始值的```copy副本``` 39 | 40 | 注:捕获列表中的值的```作用域只能在闭包内```,闭包外无法使用。 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /第八章:错误处理/8.1 result类型.md: -------------------------------------------------------------------------------- 1 | 2 | # 第八章:错误处理 3 | 4 | 5 | ## 8.1 Result类型 6 | 7 | #### 什么是Result 类型 8 | Result 类型~~并不是Swift标准库中~~(在Swift5中苹果已将Result置为标准库中)的类型。简单介绍就是一个简化成功和失败两种情况的枚举。 个人感觉Result更像是一种编程思想。 9 | 10 | 书中大部分篇幅是讲```try``` ```catch``` 和```throws``` 相关的东西 ```Result 类型```相关讲的不多。 为了能简单高效的理解Result类型 下面会直接拿```使用场景```来讲Result相关的知识点。 11 | 12 | 首先我们先说说swift中```可选值Optional的本质```: 13 | 其实是一个```枚举```,它包含.Some 和.None两个```枚举值```。 14 | 15 | // Optional的本质 16 | @frozen public enum Optional : ExpressibleByNilLiteral { 17 | 18 | case none 19 | 20 | case some(Wrapped) 21 | 22 | } 23 | 24 | 25 | tips:项目中需要自定义枚举时,避免不要有 ```.none``` 这个case, 因为当你的枚举为```可选值```时,就和 Optional 中的 .none 重复了。。 26 | 27 | ##### Result类型结构和可选值Optional结构```非常相似```。 28 | --- 29 | 30 | #### Result使用场景 31 | 在通常的网络请求中,我们都是通过success和failed两个闭包来传递结果 32 | 33 | AFHTTPSessionManager *session = [AFHTTPSessionManager manager]; 34 | [session GET:@"需要请求的url" parameters:nil success:^(NSURLSessionDataTask *task, id responseObject) { 35 | NSLog(@"请求成功"); 36 | } failure:^(NSURLSessionDataTask *task, NSError *error) { 37 | NSLog(@"请求失败"); 38 | }]; 39 | 40 | 这个时候使用result类型 就可以```用一个对象```去处理这两种情况 41 | 42 | #### 下面我们来手动实现一个自己的result类型。 43 | 44 | ##### 1、我们先定义成功和失败的两个协议: 45 | 46 | 成功的协议,可以扩展成功的对象需要的任何东西。 47 | ```LWSuccessedProtocol``` 48 | 49 | 失败的协议,可以扩展失败的对象需要的任何东西。 50 | ```LWFailedProtocol``` 51 | 52 | ##### 2、我们创建一个LWResult枚举 53 | Result枚举有2个泛型,T继承LWSuccessedProtocol,Error继承LWFailedProtocol。枚举有2种情况,一种是成功(success),一种是失败(failure),还有对应的初始化。 54 | 我们就完成了一个Result类型的定义了。 55 | 56 | public protocol LWSuccessedProtocol { 57 | 58 | } 59 | 60 | public protocol LWFailedProtocol { 61 | 62 | } 63 | 64 | public enum LWResult { 65 | case success(T) 66 | case failure(Error) 67 | 68 | public init(value:T) { 69 | self = .success(value) 70 | } 71 | 72 | public init(error:Error) { 73 | self = .failure(error) 74 | } 75 | } 76 | 77 | 78 | 79 | 自定义Result的具体使用:在拿到网络请求的回调之后去处理拿到的数据 80 | 注:下面Demo中 API为API接口的伪代码, completion为result类型的闭包 81 | 82 | NetworkRequest(api:API, completion: LWResult) { 83 | 84 | if 网络请求成功 { 85 | let successResult = LWSuccess() 86 | completion(TDWResult(value: successResult)) 87 | 88 | } else { 89 | 90 | let failResult = LWFailure() 91 | completion(TDWResult(error: failResult)) 92 | 93 | } 94 | 95 | } 96 | 97 | // 方法调用和网络请求的处理 98 | NetworkRequest(api:XXXapi, {result in 99 | 100 | switch result : 101 | case: .success(let data): 102 | //网络请求成功的处理 103 | case: .failed(let error) 104 | // 网络请求失败的处理 105 | } 106 | 107 | 108 | 109 | 注:使用Moya作为项目网络请求框架同学一定对Result类型不陌生。Moya天生就是对Result类型的扩展封装。 上面的Demo可以让各位同学对Result类型有一个更好的理解。 110 | 111 | 112 | -------------------------------------------------------------------------------- /第八章:错误处理/8.2 抛出和捕获.md: -------------------------------------------------------------------------------- 1 | 2 | # 第八章:错误处理 3 | 4 | 5 | ## 8.2 抛出和捕获 6 | 7 | #### 本小节主要讲了概念```do catch``` ```throws``` 相关的东西 8 | 9 | 先想想我们在上一节讲了Result类型 10 | ##### 知识点1: swift不会返回一个```Result```来表示失败,而是用throws 11 | Result作用于```类型```, throws作用于```函数``` 12 | 13 | ##### 知识点2:编译器会认为throws是一个```普通的返回```,不会像很多语言一样带来运行时的开销。相对```效率高一些```。 14 | 15 | 我们通过 do catch 去处理一个函数的throws 可以使用catch的匹配模式去捕获具体的错误 或者在catch-all中去捕获其他异常 如下 16 | 17 | do{ 18 | 19 | }catch FileError.fileNotExist { 20 | //FileError.fileNotExist是自定义的一个错误类型 21 | }catch{ 22 | //其他error 23 | } 24 | 25 | 26 | #### 自己自定义一个指定的error的用法。 27 | 我们可以通过创建自定义Error枚举去捕获对于的错误类型 28 | 29 | ///创建error的枚举 30 | enum MyCustomErrorType: Error { 31 | case ErrorReasonNoFile 32 | case ErrorReasonReadWrong 33 | case ErrorReasonShotDown 34 | } 35 | 36 | ///假设myThrowFunc函数需要做抛错处理 37 | func myThrowFunc() throws { 38 | if 触发了没有文件的错误情况 { 39 | throw MyCustomErrorType.ErrorReasonNoFile 40 | } 41 | } 42 | 43 | ///接收对应的error 44 | do{ 45 | myThrowFunc() 46 | }catch MyCustomErrorType.ErrorReasonNoFile { 47 | // 无文件的错误情况 48 | }catch{ 49 | //其他error 50 | } 51 | 52 | ##### 注:无论你有多少个自定义的catch分支 最后都要用一个```catch { }去结尾```,我们认为你无法将所有的error都考虑到。 53 | 54 | -------------------------------------------------------------------------------- /第八章:错误处理/8.3带有类型的错误.md: -------------------------------------------------------------------------------- 1 | # 错误处理 2 | 3 | ## 8.3 带有类型的错误 4 | 5 | 这一节主要讲的是将函数的error包装成```Result```类型, 6 | 不太理解Result类型的同学可以看看C8P1里对Result类型的介绍。 7 | 8 | #### 本质:在Result类型的基础上将错误的类型```指定为泛型```就可以了 9 | 10 | enum Result  { 11 | case failure(errorType) 12 | case success(A) 13 | } 14 | 15 | ///使用方法和普通的Result类型的使用一样样的 16 | func maybyReturnSomeError() -> Result<[String], someError> {...} 17 | 18 | let result = maybeReturnSomeError 19 | 20 | switch result { 21 | case ... 22 | case ... 23 | } 24 | 25 | 26 | ##### 没错,这一节的知识就是这么多,大家只用了解可以把error包装到Result中即可。 27 | -------------------------------------------------------------------------------- /第八章:错误处理/8.4 将错误桥接到Objective-C.md: -------------------------------------------------------------------------------- 1 | # 错误处理 2 | 3 | ## 8.4 将错误桥接到Objective-C 4 | 5 | 小节主要内容如标题⬆️ 6 | 7 | 8 | #### 知识点1: OC中的异常应该只用来表达```程序员👨‍💻‍的错误```,在平时的开发中我们很少用到异常。 9 | 10 | #### NSError 对象都有一个```Domin```字符串,还有一个整数的错误代码```code``` 11 | 12 | 将Swift的错误桥接到OC时运行时会默认提供这个NSError对象。 13 | 14 | #### 如果有需要我们可以通过遵守```CustomNSError```来更好的实现错误桥接。 15 | 16 | extension ParseError: CustomNSError { 17 | ///自定义错误的Domin 18 | static let errorDomain = "io.objc.parseError" 19 | ///自定义错误的Code 20 | var errorCode: Int { 21 | switch self { 22 | case .wrongEncoding: return 100 23 | case .warning(_, _): return 200 24 | } 25 | } 26 | var errorUserInfo: [String: Any] { 27 | return [:] 28 | } 29 | } 30 | 31 | 32 | #### 错误相关的一些协议 33 | ##### LocalizedError 34 | 提供一个```本地化的错误信息```,来表示```为什么发生```(failureReason),```从错误中恢复的提示```(recoverySuggestion),```额外的帮助文本```(helpAnchor) 35 | 36 | 其具体API如下: 37 | 38 | /// Describes an error that provides localized messages describing why 39 | /// an error occurred and provides more information about the error. 40 | public protocol LocalizedError : Error { 41 | 42 | /// A localized message describing what error occurred. 43 | var errorDescription: String? { get } 44 | 45 | /// A localized message describing the reason for the failure. 46 | var failureReason: String? { get } 47 | 48 | /// A localized message describing how one might recover from the failure. 49 | var recoverySuggestion: String? { get } 50 | 51 | /// A localized message providing "help" text if the user requests help. 52 | var helpAnchor: String? { get } 53 | } 54 | 55 | ##### RecoverableError 56 | 用来描述一个用户```可恢复```的错误,展示一个或者多个```recoveryOptions```,并在用户要求可恢复的时候```执行恢复```。 57 | 58 | 其具体的API如下: 59 | 60 | /// Describes an error that may be recoverable by presenting several 61 | /// potential recovery options to the user. 62 | public protocol RecoverableError : Error { 63 | 64 | /// Provides a set of possible recovery options to present to the user. 65 | var recoveryOptions: [String] { get } 66 | 67 | /// Attempt to recover from this error when the user selected the 68 | /// option at the given index. This routine must call handler and 69 | /// indicate whether recovery was successful (or not). 70 | /// 71 | /// This entry point is used for recovery of errors handled at a 72 | /// "document" granularity, that do not affect the entire 73 | /// application. 74 | func attemptRecovery(optionIndex recoveryOptionIndex: Int, resultHandler handler: @escaping (Bool) -> Void) 75 | 76 | /// Attempt to recover from this error when the user selected the 77 | /// option at the given index. Returns true to indicate 78 | /// successful recovery, and false otherwise. 79 | /// 80 | /// This entry point is used for recovery of errors handled at 81 | /// the "application" granularity, where nothing else in the 82 | /// application can proceed until the attempted error recovery 83 | /// completes. 84 | func attemptRecovery(optionIndex recoveryOptionIndex: Int) -> Bool 85 | } 86 | 87 | -------------------------------------------------------------------------------- /第八章:错误处理/8.5 错误和函数参数.md: -------------------------------------------------------------------------------- 1 | # 错误处理 Error Handling 2 | 3 | ## 8.5 错误和函数参数 Errors and Function Parameters 4 | 5 | 本小节主要对下面两个demo的统一抽象封装引出来的一些思路。 6 | 1.```查看某一系列文件的有效性``` 7 | 2.```看一个数字数组中所有数字是不是都不是质数``` 8 | 9 | 因为检查是不是质数不会报错,而检查文件可能会throws异常,从而引出```rethrows```关键字。 10 | 11 | 12 | ##### Demo1.一系列文件有效性的检查 13 | 检查一个文件的有效性checkFile方法应该有三种情况,```有效```,```无效```,```检查出错``` 14 | Demo中 用一个for循环确认每一个文件的有效性,遇到无效文件或者检查出错都会```提前退出循环```。 15 | 16 | 17 | ///检查文件有效性的方法 18 | func checkFile(filename: String) throws -> Bool 19 | 20 | 21 | func checkAllFiles(filenames: [String]) throws -> Bool { 22 | for filename in filenames { 23 | guard try checkFile(filename: filename) else { return false } } 24 | return true 25 | } 26 | 27 | ##### Demo2.检查一个数字数组中是否含有质数 和Demo1相似,但不会throws异常 28 | 29 | 30 | func checkPrimes(_ numbers: [Int]) -> Bool { 31 | for number in numbers { 32 | guard number.isPrime else { return false } 33 | } 34 | return true 35 | } 36 | checkPrimes([2,3,7,17]) // true 37 | checkPrimes([2,3,4,5]) // false 38 | 39 | 40 | #### 作为一个优秀的```高级开发工程师```(码农。。) 肯定会对这两个高度相似的方法是做一个```完美的封装``` 41 | #### 我们暂定封装的方法方法名叫```all()```, all方法的参数是一个```判断条件是否满足的函数```。 42 | 43 | extension Sequence { 44 | /// Returns `true` iff all elements satisfy the predicate 45 | func all(condition: (Iterator.Element) -> Bool) -> Bool { 46 | for element in self { 47 | guard condition(element) else { return false } 48 | } 49 | return true 50 | } 51 | } 52 | 53 | ##### 下面我们开始享受我们封装完之后的战果了。 54 | ##### 查看是否有质数 一行代码搞定。 55 | 56 | func checkPrimes2(_ numbers: [Int]) -> Bool { 57 | return numbers.all { $0.isPrime } 58 | } 59 | 60 | 61 | ### Rethrows 关键字 62 | --- 63 | #### 下面问题就来了,查看有限文件时可能会异常。```这个封装不好使啊~``` 这个时候我们可以使用rethrows关键字 64 | 65 | #### ```rethrows关键字作用```: 告诉编译器,这个函数只会在它的```参数函数抛错时抛错``` 编译器可以避免我们一定要用try调用。 66 | 67 | extension Sequence { 68 | func all(condition: (Iterator.Element) throws -> Bool) rethrows 69 | -> Bool { 70 | for element in self { 71 | guard try condition(element) else { return false } 72 | } 73 | return true 74 | } 75 | } 76 | 77 | 上面检查质数的Demo不受影响,而且检查文件有效性就可以像下面这样写了 78 | 79 | ///有try了 80 | func checkAllFiles(filenames: [String]) throws -> Bool { 81 | return try filenames.all(condition: checkFile) 82 | } 83 | 84 | 85 | 知识点:标准库中序列和集合几乎所有```可以接收函数做参数的函数```都被标记成rethrows了。 这样做就很稳了~ 86 | 87 | over~ 88 | 89 | -------------------------------------------------------------------------------- /第八章:错误处理/8.6 defer语法可以让代码更简洁 Clearing Up Using defer.md: -------------------------------------------------------------------------------- 1 | # 错误处理 Error Handling 2 | 3 | ## 8.6 defer语法可以让代码更简洁 Clearing Up Using defer 4 | 5 | 本小节内容比较简单,主要延续之前的文件的有效性查询的Demo来引出defer语法,并介绍一些相关特性 6 | 7 | #### 引出defer关键字: 一般在可能抛出异常的方法都会使用 try/finally结构。无论最后是否有抛出异常都会走finally流程。 这个时候我们就可以使用defer语法让```代码变得更灵活```。 8 | 9 | #### defer关键字的作用:实际是一个```闭包```,在当前声明的```作用域结束时执行```。 10 | defer在文件读写中的使用. 11 | 12 | func contents(ofFile ?lename: String) throws -> String { 13 | let file = open("test.txt", O_RDONLY) 14 | defer { close(file) } 15 | let contents = try process(file: file) 16 | return contents 17 | } 18 | 19 | ##### 注1:在上面的demo中,如果不用defer关键字 当```出现异常```,直接就try 提前结束了. 导致当前的文件流没有执行close方法。这个在以后的数据库操作中可算是一个```重大bug```。 20 | 21 | ##### 注2: defer要```写在reture 前面```。 虽然它的执行上在return后。 你写在return后面,代码编译 return后的代码看都不看。这个时候你写defer实际上没啥用。 22 | 23 | #### 标准库中对defer的使用 24 | 25 | 下面会写出```Enumeratedlterator```协议中```next()```方法的代码实现。 26 | ##### 使用场景:增加计数器的值而且需要返回没有增加值之前的值。 27 | 28 | struct EnumeratedIterator: IteratorProtocol, Sequence { 29 | internal var _base: Base 30 | internal var _count: Int ... 31 | func next() -> Element? { 32 | guard let b = _base.next() else { return nil } 33 | defer { _count += 1 } 34 | return (offset: _count, element: b) 35 | } 36 | } 37 | 38 | 这里引申一道关于defer很有趣的Swift面试题, 是Objc.io 里面很有名的一道题。 39 | 40 | var counter = 5 41 | 42 | func increment() -> Int { 43 | defer { counter += 1 } 44 | return counter 45 | } 46 | 47 | counter = increment() 48 | 49 | // What's the value of counter? 50 | 51 | 可能大部分童鞋都能把答案猜对,但其中具体的原因可能还真不是你想的那样哈哈~ 52 | 具体的思路见网页: https://www.objc.io/quiz/19/ 53 | 54 | 55 | ### 多defer方法多使用注意 56 | 写一个操作数据库的Demo来讲讲 57 | guard let database = openDatabase(...) else { return } 58 | defer { closeDatabase(database) } 59 | guard let connection = openConnection(database) else { return } 60 | defer { closeConnection(connection) } 61 | guard let result = runQuery(connection, ...) else { return } 62 | 63 | #### 类似于```压栈操作```,前面的后执行,后面的先执行。 64 | 上面的demo中的顺序也是在执行完数据库查询之后先```关闭链接```,再去```关闭数据库```。 65 | 66 | over~ 67 | 68 | -------------------------------------------------------------------------------- /第八章:错误处理/8.7 错误和可选值 Error and Optionals.md: -------------------------------------------------------------------------------- 1 | # 第八章:错误处理 Error Handling 2 | 3 | 4 | ## 8.7 错误和可选值 Error and Optionals 5 | 6 | 本小节围绕 ```try?``` 的用法和相关知识点展开来讲,内容不多,比较简单。 7 | 8 | #### try? 关键字的作用: 可以```忽略error```抛出的错误, 并将返回值转化为可选值, ```error时返回nil```, ```成功时正常返回```。 9 | 10 | 11 | 示意Demo如下 12 | 13 | // parse方法是一个可以throw error的方法。 14 | if let result = try? parse(text: input) 15 | { 16 | print(result) 17 | } 18 | 19 | #### 使用场景: 20 | 你对你的错误信息```并不在意```的时候可以使用try? (swift 并不建议你去忽略错误,不过有些场景去使用try?, 真香~) 21 | 22 | 23 | ##### 使用try? 意味着你能获得的错误信息少了,我们可以写一个optional的extension来补充获得想要的报错信息 24 | Demo如下,只做了解即可。 25 | 26 | 27 | extension Optional { 28 | /// Unwraps `self` if it is non-`nil`. 29 | /// Throws the given error if `self` is `nil`. 30 | func or(error: Error) throws -> Wrapped { 31 | switch self { 32 | case let x?: return x 33 | case nil: throw error 34 | } 35 | } 36 | } 37 | 38 | do { 39 | let int = try Int("42").or(error: ReadIntError.couldNotRead) 40 | } catch { 41 | print(error) 42 | } 43 | 44 | #### 书中并没有讲try! try? try 的区别,这里我补充补充: 45 | 46 | 47 | ```try!```: "我写的代码```肯定没有问题```,如果有,你```尽管崩溃``` " 这个肯定```不建议使用啊```. 48 | 我也一直觉得我写的代码不可能有问题的,可是该崩的时候还是崩。。 49 | 50 | ```try?```: 上面已讲,略略略。。 51 | 52 | ```try```: 常规的异常处理 配合 ```do catch``` 一起使用。 53 | 54 | -------------------------------------------------------------------------------- /第八章:错误处理/8.8 错误链.md: -------------------------------------------------------------------------------- 1 | # 第八章:错误处理 Error Handling 2 | 3 | 4 | ## 8.8 错误链 Chaining Errors 5 | 6 | 本小节主要讲了如何```优雅的```处理链式调用多个可能抛出错误的方法。 7 | 8 | ##### 其实swift 的内建(build-in)错误处理处理机制就已经很好的将链式调用可能抛错的方法,我们直接用一个大的do/catch块把这一段```包裹```住就行。 9 | 10 | 11 | ##### 但是作为一个优秀的开发者,我们可以用更优雅的解决方案去处理这种场景。 12 | 还记得之前学的```Result```类么? 我们可以通过Result类去包装返回值,让你的代码变得更加优雅。 13 | 14 | 具体Demo如下 15 | 16 | ///未通过Result包装error的检查文件和返回pid的方法 17 | func checkFilesAndFetchProcessID(filenames: [String]) -> Int { 18 | do { 19 | try filenames.all(checkFile) 20 | let contents = try contentsOfFile("Pidfile") 21 | return try optional(Int(contents), 22 | orError: ReadIntError.CouldNotRead) } 23 | catch { 24 | return 42 // 默认值 25 | } 26 | } 27 | 28 | ///通过Result包装error的检查文件和返回pid的方法,毫无try catch 痕迹可言。 29 | func checkFilesAndFetchProcessID(filenames: [String]) -> Result { 30 | return filenames 31 | . all (checkFile) 32 | .flatMap { _ in contentsOfFile("Pidfile") } 33 | .flatMap { contents in 34 | Int ( contents).map(Result.Success) ?? .Failure(ReadIntError.CouldNotRead) 35 | } 36 | } 37 | 38 | ##### 注: 这里实际上是通过一系列的flatMap方法去过滤掉error, 在实际开发中,我们可以通过同样可以通过flatmap做很多关于去除nil的骚操作, 这个知识点之前有听过瞄神提过。我们项目中就有许多场景使用到,小伙伴们不妨在自己的项目中也尝试一下~ 39 | 40 | 41 | -------------------------------------------------------------------------------- /第八章:错误处理/8.9 高阶函数和错误.md: -------------------------------------------------------------------------------- 1 | # 第八章:错误处理 Error Handling 2 | 3 | ## 8.9 高阶函数和错误 Higher-Order Functions and Errors 4 | 5 | 本小节主要讲了在遇到```异步耗时操作```可能```抛出错误```时候的处理。(书中也提到了并没有什么```完美```的处理,只有对应不同场景```相对合适```一些的处理方式) 6 | 7 | 对这个知识点做一个简单了解就行。 8 | 9 | ##### 先引入一个问题 10 | 假设方法```compute```是一个```耗时操作```,一般我们会使用```闭包```去异步的拿到返回值。 11 | 12 | func compute(callback: Int -> ()) 13 | 14 | 那么,如果。 15 | compute方法可能```会抛错捏```? 16 | 而且正好我们想要```拿到这个错误的详细信息```。 17 | 18 | 一般情况下我们会这样写 19 | 20 | func compute(callback: Int throws -> ()) 21 | 22 | 一眼望去老铁这样写妥妥```没毛病```啊(哈哈其实我也是这么觉得的。) 23 | but!!! 24 | 25 | 这样写现在并不是指```计算可能失败```,而是表示```回调本身可能会抛出错误```。 26 | 27 | ##### 下面我们再回顾一下很久很久之前的知识点: 28 | ```可选值```和 ```Result``` 作用于```类型```,而 ```throws``` 只对```函数```起效。将一个函数标注为 throws 意味着这个函数可能会失败. 29 | 30 | 我们真正需要的是用一个``` Result 来封装 Int 参数``` 的函数去解决我们上面的需求。 31 | 32 | func compute(callback: Result -> ()) 33 | 34 | 35 | ##### 知识点: 对于异步的错误处理来说,Result 可能会是更好的选择,但是如果你已经在你的```同步方法中使用throws了```,再去你的异步函数中使用Result 会让你的```API使用起来更加困难```。 36 | 37 | 38 | 如果你```只有一个回调```的话,使用下面这种会友好一些: 39 | 40 | func compute { (theResult: () throws -> Int) in 41 | do { 42 | let result = try theResult() 43 | print ( result ) 44 | } catch { 45 | print("An error happened: \(error)") 46 | } 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /第六章:函数/6. 函数(总体介绍).md: -------------------------------------------------------------------------------- 1 | # 第六章:函数(function) 2 | #### 注:本章算是swift的核心(个人感觉),先记住```swift中函数是"一等公民(first-class function)"``` 3 | 4 | 和OC中的函数相比,首先我们要先知道函数的三个知识点 5 | 6 | * 函数可以```赋值给变量```,也可以作为另外一个函数的```参数```和```返回值``` 7 | 8 | 9 | func loveSomeBody(name:String) { 10 | print("I love \(name)") 11 | } 12 | 13 | var loveSomeBodyFunc:((String) ->Void)? 14 | ///函数赋值给变量 15 | loveSomeBodyFunc = loveSomeBody 16 | loveSomeBodyFunc?("liaoWorking")//I love liaoWorking 17 | 18 | ///函数作为参数 19 | func doSomeThing(things: ((String)-> Void)?) { 20 | things?("NObody") 21 | } 22 | 23 | doSomeThing(things: loveSomeBodyFunc)///I love NObody 24 | 25 | ///函数作为返回值 26 | func lovingYou() -> ((String) ->Void)?{ 27 | return loveSomeBody 28 | } 29 | 30 | loveSomeBodyFunc = lovingYou() 31 | 32 | 33 | 34 | * 函数可以```捕获```其局部作用域之前的变量(同OC,不赘述)。 35 | 36 | * 除了func创建函数 ```{}```也可以创建函数,这个叫```闭包表达式``` 37 | 38 | 这里延伸一下,我们平时在写懒加载的时候用法如下, ```{}``` 就是一个方法,```()```表示执行这个方法。 和平时调用方法func() 使用是类似的。 39 | 40 | lazy var label: UILabel = { () -> UILabel in 41 | return UILabel() 42 | }() 43 | 44 | 45 | ///常见的map用法,里面就用到了{} 46 | /// $0的含义后面再讲,相信大部分同学都了它的意思。 一句话概况就是[闭包里面的默认值]。 47 | [1, 2, 3, 4].map{$0 * 2} 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /第六章:函数/6.1 函数的灵活性.md: -------------------------------------------------------------------------------- 1 | # 第六章:函数(function) 2 | ## 6.1 函数的灵活性(The flexibility of function) 3 | ##### 注:本节前部分主要通过一个排序的demo来介绍了函数的灵活性 4 | 话不多说,直接上代码 5 | 6 | var mArray = [3, 1, 2] 7 | mArray.sort() ///1 2 3 8 | mArray.sort(by: >) ///3 2 1 9 | 10 | 11 | 12 | let animal = ["fish", "dog", "elephant"] 13 | // 反向比较字符串的大小 我们可以嵌套任意的比较函数 让排序功能更强大! 14 | let okAnimal = animal.sorted { (lhs, rhs) -> Bool in 15 | let l = lhs.reversed() 16 | let r = rhs.reversed() 17 | /// ⭐️核心方法⭐️ 按顺序比较两个字符串的大小 abc > abb 返回值是一个Bool 18 | return l.lexicographicallyPrecedes(r) 19 | } 20 | print(okAnimal) //["dog", "fish", "elephant"] 21 | 22 | ##### 注: String的```lexicographicallyPrecedes```方法在项目中挺实用的,是```swift标准库自带方法```,像比较时间字符串"2018-08-08" 和 "2018-03-04" 的时间先后就可以用这个方法。 23 | 24 | ### 函数作为数据 25 | 本节主要是通过几个排序的demo讲了一些具体案例。 26 | ##### 知识点:swift中的排序算法是基于内省算法(introsort),起实质是快排和堆排的混合。 当集合很小时会转化为插入排序。避免不必要的性能消耗。 27 | 28 | ##### 参考资料: 29 | [堆排序就这么简单](https://www.cnblogs.com/Java3y/p/8639937.html) 30 | 31 | 32 | -------------------------------------------------------------------------------- /第六章:函数/6.2 局部函数和变量捕获.md: -------------------------------------------------------------------------------- 1 | # 第六章:函数(function) 2 | ## 6.2 局部函数和变量捕获 3 | 4 | 本节主要通过一些很基本的排序实现的demo去演示了一些关于```局部函数```(函数的嵌套)的使用方法。(我google了很多相关资料,发现都没有本节的知识点。大家有个印象就行。) 5 | 6 | ### 局部函数(函数中嵌套函数) 7 | --- 8 | ##### 注:本节Demo的关联性和之前几节较强,学习成本较高,这里我使用swift playground中一个比较经典的Demo来讲(感觉在实际项目中很少用到,只做大概了解。) 9 | 10 | demo主要是将一个点从-4的位置一步一步移动到0点位置。 11 | 12 | 13 | // 函数的返回值是一个参数是Int 返回值是Int函数。 14 | func chooseStepFunction(backwards: Bool) -> (Int) -> Int { 15 | 16 | func stepForward(input: Int) -> Int { return input + 1 } 17 | func stepBackward(input: Int) -> Int { return input - 1 } 18 | 19 | return backwards ? stepBackward : stepForward 20 | } 21 | 22 | var currentValue = -4 23 | // moveNearerToZero 是chooseStepFunction的返回值,实质是一个参数是Int 返回值是Int函数。 24 | let moveNearerToZero = chooseStepFunction(backwards: currentValue > 0) 25 | while currentValue != 0 { 26 | print("\(currentValue)... ") 27 | // 调用函数,获得返回值 28 | currentValue = moveNearerToZero(currentValue) 29 | } 30 | 31 | 32 | ### 变量捕获 33 | --- 34 | TODO: 35 | ~~##### 我们可以在上面demo中嵌套的```内层函数中```去操作```外层函数中```定义的```变量```。 这个就是```变量捕获``` 36 | 很多场景下可以使代码更加```简洁```。~~ 37 | -------------------------------------------------------------------------------- /第六章:函数/6.3 函数作为代理 function as delegate.md: -------------------------------------------------------------------------------- 1 | # 第六章:函数(function) 2 | 3 | ## 6.3 函数作为代理 function as delegate. 4 | 5 | ### foundation框架的代理 6 | --- 7 | ##### 知识点1:本小节只有一个知识点 swfit中的代理用```weak``` 来修饰 防止```循环引用```。 8 | 9 | weak var delegate: xxxDelegate? 10 | 11 | ### 结构体代理 12 | --- 13 | ##### 知识点2:我们可以将代理方法用```mutating```关键字来修饰,来```修改结构体本身```的内容 14 | 15 | 书中举了个例子:将结构体```设置成代理```。 发现原结构体的```内部并不能改变```。 16 | 17 | ##### 原因:delegate = structXX 时,实质是把结构体的```复制```赋值给了delegate。所以结构体内部不能发生改变 18 | 19 | ##### 总结: 在代理和协议的模式中,并```不适合```使用结构体。 20 | 21 | 22 | ### 使用函数(```闭包```),而非代理 23 | 24 | ##### 知识点3:对于结构体,尽量不要当做delegate,用```闭包```就行。 通过[weak self]的方式```避免循环引用```。 25 | 26 | ##### 知识点5:要注销一个代理用 = nil就可以。要注销闭包就不行了,```闭包实质上是函数```,函数```无法被比较```,只能通过额外的逻辑代码去移除。 27 | -------------------------------------------------------------------------------- /第六章:函数/6.4 inout参数和可变方法(inout parameter and mutating function).md: -------------------------------------------------------------------------------- 1 | # 第六章:函数(function) 2 | ## 6.4 inout参数和可变方法(inout parameter and mutating function) 3 | ##### 知识点: inout 关键字: 一般用来修饰函数的参数 就是让函数的参数变得可变(swift 中方法的参数默认都是 let 不可变的) 4 | 5 | // 给originNum参数加increment ❌ 6 | func addNumber(originNum: Int, increment: Int) { 7 | // 这里会报错:Left side of mutating operator isn't mutable: 'originNum' is a 'let' constant 8 | originNum += increment 9 | } 10 | 11 | // 使用了inout 关键字 ✅ 12 | func addNumber(originNum: inout Int, increment: Int) { 13 | originNum += increment 14 | } 15 | 16 | // 方法调用 在value参数前有一个&符号 17 | var value = 50 18 | addNumber(originNum: &value, increment: 10) 19 | print(value) // value = 60 20 | 21 | 22 | inout做的事情就是把一个值传递给函数,函数可以改变这个值,然后将原来的值替换,并传递出来。 23 | ```它并不是传递引用``` 24 | 25 | #### 什么样的表达式可以当作inout参数去传递? 26 | 先来弄懂两个c++中的概念。 ```lvalve``` (变量)和 ```rvalve``` (不是变量) 27 | 如array[2] = 4 ```lvalue``` = ```rvalue``` 28 | * lvalue估计来源于left value。 位置处于左边。就是```可以修改```的值。 29 | * rvalue估计来源于right value。处于赋值语句右边,是只读的```不可修改```的值。 30 | 资料来源:[关于lvalve 和 rvalve](https://blog.csdn.net/rogerhe/article/details/6410993) 感兴趣的同学可以看看。 31 | inout参数,只能传递lvalve给它。因为不可能对一个rvalve进行改变。 32 | 33 | ##### 注: 每个lvalve前面需要加上&符号将它传入(swift中的自动补全会帮我们加上& 😄) 34 | ![Alert text](http://pjmrfxc1n.bkt.clouddn.com/2FC22C09-9CBD-4125-B388-64A726A752B3.jpeg) 35 | inout的基本使用 36 | 37 | ##### 知识点:只读属性(只有get方法)无法作用于inout参数 38 | 39 | tips:swift文档指出 我们```不应该依赖inout```这个行为 40 | 41 | ### 嵌套函数和inout 42 | --- 43 | 44 | 可以在```嵌套函数```中使用一个inout参数 45 | ![Alert text](http://pjmrfxc1n.bkt.clouddn.com/F33DB316-8729-438B-82A9-B1755961EF96.jpeg) 46 | 47 | ##### 注: 我们不能让这里的inout参数```逃逸@escaping``` (原因:inout的值会在函数返回之前赋值回去,逃逸会增加inout参数的```生命周期```,如果延时返回回去赋值,原来的值不存在了,就妥妥的```不安全```啊~) 48 | @escaping 的用法后面会讲。 你急的话可以先自己百度。默认大家都很优秀,知道是什么用法! 49 | 50 | ### &不意味inout的情况 51 | --- 52 | 53 | &除了讲变量传递给inout, 还可以将变量转化为```不安全的指针```。 54 | ![Alert text](http://pjmrfxc1n.bkt.clouddn.com/28609584-0602-4FCD-AF05-4BC52119DF0C.jpeg) 55 | 这里的&使用就不是inout语义了,是将变量转化成了不安全的指针。 56 | -------------------------------------------------------------------------------- /第六章:函数/6.5 计算属性和下标(computed property and subscript).md: -------------------------------------------------------------------------------- 1 | # 第六章:函数(function) 2 | ## 6.5 计算属性和下标(computed property and subscript) 3 | 4 | ##### 知识点1: 什么是计算属性 5 | 我们直接看下面这个demo 6 | 7 | /// 返回我的名字 复杂度: O(1) 8 | var myName:String { 9 | return "liaoworking" 10 | } 11 | 12 | 没有指定```setter```方法,```只读```属性,myName的```值不会被缓存```,每次被调用的时候都会计算一遍。上面这个就是计算属性 13 | 注:swift API指南推荐我们对所有复杂度不是O(1)的计算属性都应该在文档中写明(如demo中所示)。因为会假设调用计算属性的耗时是常数时间。 14 | ##### 知识点2:如何声明一个外部只读,内部可读写的属性? 15 | 如下: 16 | 17 | private(set) var userName:String = "" 18 | 19 | ### 延迟存储属性(懒加载 lazy) 20 | --- 21 | 相信大家经历过多年的OC懒加载的洗礼。对lazy的理解深入骨髓! 22 | 这里就只说笔者觉得可以拿来一说的几点 23 | 24 | 常规写法1: 25 | lazy var myAge:Int = { ()-> String in 26 | return 18 27 | }() 28 | 29 | 常规写法2: 30 | ///getMyAge() 是一个返回Int类型的方法 31 | lazy var myAge:Int = getMyAge() 32 | 33 | 常规写法3 34 | ///currentYear 和 birthYear是两个变量 35 | lazy var myAge:Int = currentYear - birthYear 36 | 37 | 这里多说一句关于常规写法1和常规写法2: 38 | 在常规写法1中 39 | 40 | { ()-> String in 41 | return 18 42 | } 43 | 的本质是一个没有参数返回值是String的方法 44 | 后面的() 是对前面的方法的调用。 45 | 所以其本质和常规写法2是相同的。 46 | ### 使用不同参数重载下标 47 | --- 48 | 49 | 下标的常规使用 50 | 51 | let fibs = [0, 1, 1, 2, 3, 5] 52 | let first = fibs[0] 53 | fibs[1..<3] //[1, 1] 54 | 55 | 因为demo的使用场景有限,就是想重写一个只有```起点(从起点到最后)```的下标表示。 56 | 这里只说说```核心思想```和```步骤```。 具体代码可见最后附。 57 | 58 | 59 | 我们可以```重载下标```来定义一个可以使用```半有界区```间为参数的collection的下标方法来实现 60 | 61 | fibs[2..<] 62 | 63 | ```步骤```: 64 | 65 | * 1.定义一个简单的```操作符运算```来创建有界区间。 66 | 这个涉及到操作符合 ..< 操作符的定义的知识点。 67 | 68 | * 2.给 collection 添加一个extension 把这两个subscript的方法写入即可。 69 | 70 | ### 下标的进阶 71 | --- 72 | 下标其实也可以像函数一样```接受多个参数```。 73 | ```实现原理```: 74 | 写一个extension的```subscript```的方法即可。 75 | Demo主要是以字典的形式展示每个字母出现个个数,Demo具体使用场景太少。```了解步骤及知识点即可```。 76 | 77 | 78 | 79 | 80 | 附: 81 | 82 | ///重载下标 达到获取collection半有界区的目的 83 | struct RangeStart{let start : l} 84 | struct RangeEnd{let end : l} 85 | 86 | postfix operator ..< 87 | postfix func ..<(lhs: l) -> RangeStart { 88 | return RangeStart(start: lhs) 89 | } 90 | 91 | prefix operator ..+ 92 | prefix func ..+(rhs: l) -> RangeEnd { 93 | return RangeEnd(end: rhs) 94 | } 95 | 96 | extension Collection { 97 | subscript(r: RangeStart) -> SubSequence { 98 | return suffix(from: r.start) 99 | } 100 | subscript(r: RangeEnd) -> SubSequence { 101 | return prefix(upTo: r.end) 102 | } 103 | } 104 | fibs[2..<] 105 | fibs[..+3] 106 | 107 | 108 | ///实现多参数下标的核心方法 109 | extension Dictionary { 110 | subscript(key: Key, or defaultValue: Value) -> Value { 111 | get { 112 | return self[key] ?? defaultValue 113 | } 114 | set { 115 | self[key] = newValue 116 | } 117 | } 118 | } 119 | 120 | extension Sequence where Iterator.Element: Hashable { 121 | var frequencies: [Iterator.Element: Int] { 122 | var result: [Iterator.Element: Int] = [:] 123 | for x in self { 124 | result[x, or: 0] += 1 125 | } 126 | return result 127 | } 128 | } 129 | "hello".frequencies // ["e":1, "0":1, "l":2, "h":1] 130 | 131 | -------------------------------------------------------------------------------- /第六章:函数/6.6 自动闭包 6.7 总结.md: -------------------------------------------------------------------------------- 1 | # 第六章:函数(function) 2 | ## 6.6 自动闭包。 3 | --- 4 | 本节主要知识点: 对@autoclosure 和@escaping 标注的使用场景及用法进行了介绍。 5 | ### @autoclosure 自动闭包 6 | 解释: @autoclosure把一个很长的表达式直接自动闭合一个简单的闭包,让你的闭包看起来很简洁。自动闭包没有参数。你添加参数编译器就会报错。 7 | 8 | ##### 知识点1: 9 | ```短路求值``` :像```&&```和```||```操作符中, 只要左边不符合条件或者符合条件就直接忽略(短路掉)右边的操作数。 10 | 11 | ///这里我们依赖了短路求值, 第一个操作数不符合要求时候直接不走对应的逻辑代码 12 | let evens = [2, 4, 6] 13 | if !evens.isEmpty && evens[0] > 10 { 14 | //do something 15 | } 16 | 17 | 下面通过一个Demo来讲讲使用```@autoClosure```关键字的好处: 18 | 19 | 如果我们想要为字典添加一个Extension,使某个key为空的时候有默认值,可以这样写: 20 | 21 | extension Dictionary { 22 | mutating func value(for key: Key, orAdd valueClosure: () -> Value) -> Value { 23 | if let value = self[key] { 24 | return value 25 | } 26 | 27 | let value = valueClosure() 28 | self[key] = value 29 | return value 30 | } 31 | } 32 | 33 | // 使用: 34 | var dict = ["name":"liaoWorking","job":"iOS","gender":"m"] 35 | 36 | dict.value(for: "salary", orAdd: {"99999"}) 37 | //"99999" 38 | 39 | 这样的Api的确看起来有些不友好, 40 | 如果使用自动闭包@autoclosure 去修饰你的闭包,会有这样的效果: 41 | 42 | extension Dictionary { 43 | mutating func value(for key: Key, orAdd valueClosure: @autoclosure () -> Value) -> Value { 44 | if let value = self[key] { 45 | return value 46 | } 47 | 48 | let value = valueClosure() 49 | self[key] = value 50 | return value 51 | } 52 | } 53 | 54 | dict.value(for: "salary", orAdd: "99999") 55 | // "99999" 56 | 57 | 58 | 让你的闭包参数变的更简洁。 59 | 60 | 61 | 62 | ### @escaping(逃逸闭包的标注) 标注 63 | --- 64 | 65 | ##### 知识点1:闭包作为参数默认都是```非逃逸闭包```。 66 | ##### 注:一句话介绍@escaping的作用就是```延长```闭包作为参数时候的```生命周期```。 67 | ##### 注2: 一般在项目中网络请求的回调用作为闭包时 系统就会```强制添加```闭包参数加@escping标注。 68 | 69 | 相信swift开发者在项目中肯定用(被系统强制用)过@escaping标注。 这里就不赘述了。 70 | 71 | ## 6.7 总结 72 | --- 73 | 这一章很重。慢慢消化~ 74 | 75 | 76 | -------------------------------------------------------------------------------- /第十一章:互用性/11.1 实践封装 CommonMark.md: -------------------------------------------------------------------------------- 1 | # 第十一章:互用性 Interoperability 2 | 3 | ## 前言: 4 | 5 | swift 的最大优点就是与C 或者 OC 混编的时候稳的一匹。 6 | 本章节主要讲了swift和C之间的一些知识点。 7 | 8 | 9 | ## 11.1 实践:封装 CommonMark Hands-On: Wrapping CommonMark 10 | 这一小节更像是一个```教程```。教你如何封装C语言中的```CommonMark```库(和markdown语法规范相关的一个库),提供更符合swift风格的API. 11 | 12 | 书中写的很好,个人感觉如果```没有很强的项目需求```。这一章的内容可以作为一个查阅的资料去看。 13 | 这里个人推荐[SwiftPM 之桥接 C 库](https://xiaozhuanlan.com/topic/6410729835)一文. 14 | 可以在有对应需求的时候回过来再看看😉。 15 | 16 | 文章中的Demo官方github地址如下,感兴趣的同学可以去瞅瞅。这种偏实践性的文章个人感觉有需求的话直接看源码好一些。 17 | [https://github.com/objcio/commonmark-swift](https://github.com/objcio/commonmark-swift) 18 | 19 | over~ 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /第十一章:互用性/11.2 低层级类型概览 AnOverviewofLow-LevelTypes.md: -------------------------------------------------------------------------------- 1 | # 第十一章:互用性 Interoperability 2 | 3 | ## 11.2 低层级类型概览 AnOverviewofLow-LevelTypes 4 | 5 | 一开始看到```低层级类型```的时候一脸懵逼。。这个是啥意思,我们这里可以理解成```偏底层的类型```(个人观点,如有出入谢谢指正。thanks.) 6 | 7 | ##### 在swift标准库中有不少类型提供了```低层方式```来访问内存。 8 | ##### 和底层相关的api或者命名中我们可以通过其命名方式就可以知道大概作用。 9 | 10 | · ```含有managed的类型```: 11 | 内存是自动管理的。编译器为你申请,初始化并且释放内存。 12 | 13 | · ```含有unsafe的类型```: 14 | 不提供自动的内存管理(这个managed正好相反)。你需要明确地进行内存申请,初始化,销毁和回收。 15 | 16 | · ```含有buffer类型```: 17 | 表示作用于一连串的多个元素,而非一个单独的元素上,它也提供了 Collection 的接口。 18 | 19 | · ```含有raw类型```: 20 | 包含无类型的原始数据,它和C的void*是等价的。在类型名字中不包含raw 的类型的数据是具有类型的。 21 | 22 | · ```含有mutable类型```: 23 | 允许它指向的内存发生改变。 24 | 25 | 26 | ### 指针 27 | 这小段的内容偏C语言,C语言大佬可以微微一笑跳过本小段hhh😄 28 | ##### 除了OpaquePointerType之外,swift 中还有另外八种指针类型,都用来映射 C 中不同的指针。 29 | 30 | 这里推荐瞄神关于swift中指针的文章[《Swift 中的指针使用》](https://onevcat.com/2015/01/swift-pointer/) 31 | 32 | 33 | ##### 知识点1: 34 | C 中 35 | 36 | const int* (一个指向不可变数据的可变指针) 等效于swift中的UnsafePointer 37 | 38 | int* const (一个不可变指针,或者 说,你不能改变这个指针指向的位置) 39 | 两者是不一样的 40 | 41 | 42 | ##### 知识点2: 43 | 在任意类型正确的可变变量前面加上 ```&``` 符号,可以将它们转变 ```in-out``` 表达式: 44 | 45 | var x = 5 46 | func fetch(p: UnsafePointer) -> Int { 47 | return p.pointee 48 | } 49 | 50 | fetch(p: &x) // 5 51 | 52 | 有印象的同学是不是想到函数章讲到的in-out表达式[6.4 inout参数和可变方法](https://github.com/Liaoworking/Advanced-Swift/blob/master/%E7%AC%AC%E5%85%AD%E7%AB%A0%EF%BC%9A%E5%87%BD%E6%95%B0/6.4%20inout%E5%8F%82%E6%95%B0%E5%92%8C%E5%8F%AF%E5%8F%98%E6%96%B9%E6%B3%95%EF%BC%88inout%20parameter%20and%20mutating%20function%EF%BC%89.md) 忘却的同学赶紧回去再补补🐶 53 | 54 | #### 知识点3: 55 | Swift 中申请内存的方式,其实和C中是很像的。 56 | 1.先申请内存。 57 | 2.初始化。 58 | 3.使用。 59 | 4.不用就释放。 60 | 61 | 直接上demo~ 62 | 63 | // 申请两个 Int 的内存,并初始化它们 64 | let z = UnsafeMutablePointer.allocate(capacity: 2) z.initialize(to: 42, count: 2) 65 | z.pointee // 42 66 | //指针计算: 67 | (z+1).pointee = 43 68 | //下标: 69 | z[1] // 43 70 | //销毁内存 71 | // 如果被指的是⼀一个⾮非平凡值 (⽐比如类实例例),那么你需要 // 在调⽤用 deallocate 之前先 deinitialize z.deallocate(capacity: 2) 72 | // 不不要在 deallocate 之后在访问被指向的值 73 | 74 | 在申请内存后,你必须对其进行初始化,之后 才能使用它。一旦你不再需要这个指针,你需要释放内存。 75 | 76 | over~ 77 | 78 | -------------------------------------------------------------------------------- /第十一章:互用性/11.3 函数指针 FunctionPointers .md: -------------------------------------------------------------------------------- 1 | # 第十一章:互用性 Interoperability 2 | 3 | ## 11.3 函数指针 4 | 本小节主要用过调用c中的```qsort```函数来做字符串排序的Demo, 并且讲了每一个参数的含义。 5 | 具体demo如下。 6 | 7 | public func qsort( 8 | _ __base: UnsafeMutableRawPointer!, 9 | _ __nel: Int, 10 | _ __width: Int, 11 | _ __compar: @escaping @convention(c) (UnsafeRawPointer?, 12 | UnsafeRawPointer?) 13 | -> Int32) 14 | 15 | 16 | func qsortStrings(array: inout [String]) { 17 | qsort(&array, array.count, MemoryLayout.stride) { a, b in 18 | let l = a!.assumingMemoryBound(to: String.self).pointee 19 | let r = b!.assumingMemoryBound(to: String.self).pointee 20 | if r > l { 21 | return -1 22 | }else if r == l { 23 | return 0 24 | } else { 25 | return 1 26 | } 27 | } 28 | } 29 | 30 | · 第一个参数 31 | ```指向数组首个元素的指针```。这和使用 Swift 函数的 inout 参数的规则是一样的。 32 | 33 | · 第二个参数 34 | ```元素的个数```。数组的count属性就可以了。 35 | 36 | · 第三个参数 37 | ```元素的宽度```,使用了```MemoryLayout.stride```获取,而不是用```MemoryLayout.size```(由于空隙宽度的原因可能会不准确)。 38 | 39 | · 最后一个参数 40 | 一个指向C函数的指针,这个C函数用来比较数组中的两个元素。 41 | 42 | ##### 知识点1: 43 | 书中还提到了[block 并不是 C 标准的一部分](https://en.wikipedia.org/wiki/Blocks_%28C_language_extension%29) (来自wiki)感兴趣的同学可以瞅瞅。 44 | 45 | 全书over🌜🌛~ 46 | 47 | -------------------------------------------------------------------------------- /第十章:协议/10.1 面向协议编程 Overload Resolution for Free Functions .md: -------------------------------------------------------------------------------- 1 | #### 2 | # 第十章:协议 Protocol Protocol-Oriented Programming 3 | 4 | ## 前言: 5 | 6 | swift 中的协议还是```很酷的```😎 7 | 8 | 1.可以当做```代理```来使用。 9 | 10 | 2.可以让```结构体```,```枚举```来满足协议。 11 | 12 | 3.还可以通过协议的```extension```为协议添加新方法。 13 | 14 | 4.协议允许我们```动态派发```。 15 | 16 | 5.OC中共享代码通常使用的继承,swift中可以通过使用的是协议来```共享代码```。 17 | 18 | 6.你可以为你的类添加协议去达到```功能点整合```。 19 | 20 | 21 | 22 | ## 10.1 面向协议编程 Overload Resolution for Free Functions 23 | 这一小节举了个例子来介绍面相协议编程的几个```使用场景```。 24 | 25 | 26 | #### 图形渲染的Demo 27 | 将 Core Graphics 的 CGContext 渲染到屏幕上,或者创建一个 SVG 格式的图形文件。我们可以从定义绘图 API 的最 小功能集的协议开始进行实现 28 | 29 | 1.先写协议方法 30 | 31 | protocol Drawing { 32 | mutating func addEllipse(rect: CGRect, fill: UIColor) mutating 33 | func addRectangle(rect: CGRect, fill: UIColor) 34 | } 35 | 36 | 2.为CGContext添加扩展来满足协议 37 | 38 | extension CGContext: Drawing { 39 | func addEllipse(rect: CGRect, fill: UIColor) { 40 | // do something here 41 | } 42 | func addRectangle(rect: CGRect, fill fillColor: UIColor) { 43 | // do something here 44 | } 45 | } 46 | 47 | 3. 对自定义的SVG类添加扩展来满足协议 48 | 49 | struct SVG { 50 | 51 | } 52 | 53 | extension SVG: Drawing { 54 | mutating func addEllipse(rect: CGRect, fill: UIColor) { 55 | // 针对于svg的具体实现 56 | } 57 | mutating func addRectangle(rect: CGRect, fill: UIColor) { 58 | // 针对于svg的具体实现 59 | } 60 | } 61 | 62 | 4.正式使用 63 | 64 | var context: Drawing = SVG() 65 | let rect1 = CGRect(x: 0, y: 0, width: 100, height: 100) 66 | let rect2 = CGRect(x: 0, y: 0, width: 50, height: 50) 67 | context.addRectangle(rect: rect1, fill: .yellow) 68 | context.addEllipse(rect: rect2, fill: .blue) 69 | 70 | #### 书中举的是svg的的实现,其实我见过一个关于面向协议编程的best practice: 假设tabbar的每一个VC都有显示小红点的逻辑,可以写一个协议 叫 ShowTabRedDotable, 每个VC都遵守这个协议。 71 | #### 到时候可以把每个VC都在tabVC的代码中看成ShowTabRedDotable,直接调用对应方法就行。 72 | 73 | 74 | ### 协议扩展 75 | 经常看swift标准库API的同学肯定都用见过 苹果官方对协议扩展的使用。 76 | 这里讲讲优点: 77 | 1.不需要被强制使用某个父类 78 | 79 | 2.可以让已经存在的类型满足协议(比如我们让CGContext满足了Drawing)。子类就没那么灵活了,如果 CGContext 是一个类的话,我们无法以追溯的方式去变更它的父类。 80 | 81 | 3.协议既可以用于类,也可以用于结构体,而父类就无法和结构体一起使用了 82 | 83 | 4.当处理协议时,我们无需担心方法重写或者在正确的时间调用super这样的问题 84 | 85 | ### 在协议扩展中重写方法 86 | 87 | 沿着上面的Demo我们再看一个使用场景。 88 | 89 | extension SVG { 90 | mutating func addCircle(center: CGPoint, radius: CGFloat, fill: UIColor) { 91 | var attributes: [String:String] = [ "cx": "\(center.x)", 92 | "cy": "\(center.y)", 93 | "r": "\(radius)", 94 | ] 95 | attributes["fill"] = String(hexColor: fill) 96 | append(node: XMLNode(tag: "circle", attributes: attributes)) 97 | } 98 | } 99 | 100 | 我们去调用: 101 | 102 | var sample = SVG() 103 | sample.addCircle(center: .zero, radius: 20, fill: .red) print(sample) 104 | /* 105 | 106 | 107 | */ 108 | 109 | 发现正如我们所预料的, 110 | 111 | 如果我们把sample强转为Drawing 112 | 113 | var otherSample: Drawing = SVG() otherSample.addCircle(center: .zero, radius: 20, fill: .red) 114 | print(otherSample) 115 | /* 116 | 117 | 118 | */ 119 | 120 | 它返回的是 ellipse 元素,而不是我们所期望的 circle。 121 | 当我们将 otherSample 定义为 Drawing 类型的变量时,编译器会自动将 SVG 值封装到一个代表协议的类型中,这个封装被称作```存在容器``` (existential container) 122 | 123 | 当我们对存在容器调用 addCircle 时,方法是```静态派发```的 124 | 125 | 想要将 addCircle 变为动态派发,我们可以将它```添加到协议定义里```: 126 | 127 | protocol Drawing { 128 | mutating func addEllipse(rect: CGRect, fill: UIColor) 129 | mutating func addRectangle(rect: CGRect, fill: UIColor) 130 | mutating func addCircle(center: CGPoint, radius: CGFloat, fill: UIColor) 131 | } 132 | 133 | 这个时候addCircle 方法变为了协议的一个```自定义入口``` 134 | 135 | 136 | -------------------------------------------------------------------------------- /第十章:协议/10.2 协议的两种类型 TwoTypesofProtocols.md: -------------------------------------------------------------------------------- 1 | #### 2 | # 第十章:协议 Protocol Protocol-Oriented Programming 3 | 4 | ## 10.2 协议的两种类型 TwoTypesofProtocols 5 | 这一节主要介绍了协议的两种类型```带有关联类型的协议```和```普通的协议```,还引入了一个新的概念```类型抹消``` 6 | 7 | 8 | 9 | ##### 什么是带有关联类型的协议: 10 | 11 | class 的泛型类型参数写法 12 | 13 | class Person { ... } 14 | 15 | 16 | ```protocol``` 和 class、struct 以及 enum 不同,它```不支持泛型类型参数```。取而代之的是支持抽象类型成员;称作```关联类型```。 17 | 18 | 这样单纯的讲的确有些抽象,其实你在playGround中把下面的demo写一遍就知道很简单的😄 19 | 20 | ``` 关联类型```就是可以使你在协议方法中的参数类型保存一致。 21 | 22 | ##### 带有关联类型的协议的使用场景 23 | 有时候会遇到这样的需求,继承自某一协议的类中需要保证代理方法处理的```参数类型保持一致``` 24 | 我们可以联想UITableView的代理来记忆关联类型的使用场景 25 | 26 | 协议声明: 27 | 28 | protocol ATypeDelegate { 29 | /// 关联类型的协议需要你去手动写associatedtype 创建系统提供的关联类型 T 30 | associatedtype T 31 | /// 自定义函数的参数是 32 | func printContent( ct : T) 33 | 34 | func colum( ct : T) 35 | 36 | } 37 | 38 | 协议使用: 39 | 40 | class ATypeClass: ATypeDelegate { 41 | ///只要遵守了ATypeDelegate协议 系统会自动补全 typealias T = "你指定的类型" 42 | typealias T = Int 43 | 44 | func printContent(ct: Int) { 45 | print("Int",#function) 46 | 47 | } 48 | 49 | 50 | 51 | func colum(ct: Int) { 52 | 53 | print("Int",#function) 54 | 55 | } 56 | } 57 | ATypeClass 遵守了ATypeDelegate协议,必须在ATypeClass类中定义 T 的具体类型才能使用 58 | 59 | let a = ATypeClass() 60 | a.printContent(ct: 888) 61 | a.colum(ct: 999) 62 | //Int printContent(ct:) 63 | //Int colum(ct:) 64 | 65 | 66 | ### 类型抹消: 67 | 68 | ##### 定义和使用场景 69 | 所谓类型抹消就是```不将某实例的真实类型暴露出去```,对外只暴露一个必要的```类型```。 70 | 71 | 举例 72 | 当我们编写一个class或者struct并实现了一个协议,当我们对外提供该实例时,只想让外界知道这个东西实现了该协议,可是又```不想让外界知道实现了这个协议的class或者struct是哪一个类型的```,这时我们就需要用到```类型抹消``` 73 | 74 | 使用方法: 75 | 这里推荐swiftgg翻译组的[Swift 类型擦除](https://swift.gg/2018/10/11/friday-qa-2017-12-08-type-erasure-in-swift/) 一文,这里对类型抹消的理解和使用有一个很全面的介绍。 76 | 77 | ##### 小知识点: 78 | 在上一节中我们可以写出 79 | 80 | var context: Drawing = SVG() 81 | 82 | 这里将 Drawing协议 作为一个类型来使用。 83 | 84 | 当你的协议存在关联类型的协议就不能够像Drawing这样去使用,编译器会报错。 85 | 86 | 87 | -------------------------------------------------------------------------------- /第十章:协议/10.3 带有 Self 的协议 Protocols with Self Requirements.md: -------------------------------------------------------------------------------- 1 | #### 2 | # 第十章:协议 Protocol Protocol-Oriented Programming 3 | 4 | ## 10.3 带有 Self 的协议 Protocols with Self Requirements 5 | 本小节的知识点比较单一,主要是围绕带有Self的协议来讲的 6 | 7 | #### 什么是带有 Self 要求的协议? 8 | 当我们的协议中需要引入自身相关的参数或者返回自身相关的返回值的时候用self 9 | (书中并没有明确的解锁,外国大佬写的书直接上了就干demo了。上面对self的解释是我查了很多资料的结果,看起来的确是废话,哈哈但让解释也只能这样解释😄) 10 | 11 | 带有 Self 要求的协议在行为上和那些```带有关联类型的协议```很相似 12 | 13 | 最简单的是 Equatable 14 | 15 | protocol Equatable { 16 | static func ==(lhs: Self, rhs: Self) -> Bool 17 | } 18 | 19 | ---- 20 | 我们来写一个最简单的带有self协议的Demo。 一眼一看就会了😼 21 | 22 | protocol GHEqual { 23 | ///这里引入了self 24 | static func == (lhs: Self, rhs: Self) -> Bool 25 | } 26 | 27 | class Person: NSObject, GHEqual { 28 | 29 | var gender: String = "" 30 | ///这里实现的时候系统会自动将self替换成了具体的类型。 31 | static func == (lhs: Person, rhs: Person) -> Bool { 32 | if lhs.gender == rhs.gender { 33 | return true 34 | } else { 35 | return false 36 | } 37 | } 38 | } 39 | 40 | let personA = Person() 41 | personA.gender = "male" 42 | 43 | let personB = Person() 44 | personB.gender = "male" 45 | 46 | let isSame = (personA == personB) ///true 47 | 48 | 49 | ##### 小疑问❓: 50 | 在我们的认知中,上面用到的 == 应该是对象方法。 为嘛在协议中声明的时候会用 static func? 51 | 我目前的结论是在协议中写操作符号时 对象方法也是用static func? 52 | 各位大佬可以把你们的想法分享出来。大家一起学习一下😄。 53 | ##### 疑问后续❗️: 54 | 后来我查阅资料,在StackOverflow中有人提到了这个问题 55 | [Why must a protocol operator be implemented as a global function? 56 | ](https://stackoverflow.com/questions/35246003/why-must-a-protocol-operator-be-implemented-as-a-global-function) 57 | 其大概解释就是 58 | ```由于swift语法的原因,操作符的实现必须是一个全局函数。``` 59 | 感兴趣的同学可以看看问题中大牛们的回答。 60 | 61 | 62 | #### 我们不能简单地用 Equatable 来作为类型进行变量声明: 63 | 64 | 和上一节的关联类型协议一样,我们不能把带有self的协议作为类来变量声明 65 | 66 | let x: Equatable = MonetaryAmount(currency: "EUR", amountInCents: 100) 67 | // 会编译错误:因为 'Equatable' 协议中有 Self 或者关联类型的要求, 68 | // 所以它只能被⽤用作泛型约束 69 | 70 | -------------------------------------------------------------------------------- /第十章:协议/10.4 协议内幕 Protocol Internals.md: -------------------------------------------------------------------------------- 1 | # 第十章:协议 Protocol Protocol-Oriented Programming 2 | 3 | ## 10.4 协议内幕 Protocol Internals 4 | 本小节通过性能消耗的Demo来从底层讲了协议底层```容器```相关的知识点 5 | 6 | 先看demo 7 | 8 | ///使用泛型参数的方法--性能高 9 | func f(_ x: C) -> Int { 10 | return MemoryLayout.size(ofValue: x) 11 | } 12 | ///使用协议做参数的方法--性能低 13 | func g(_ x: CustomStringConvertible) -> Int { 14 | return MemoryLayout.size(ofValue: x) 15 | } 16 | f(5) // 8--正常一个Int类型在64位中Int的尺寸 17 | g(5) // 40 18 | 19 | 明明是同一个Int为嘛后面的方法size就大很多。 20 | 21 | 我们先看看g(5) 值为 40 是怎么组成的,这里面存在一个```不透明的容器```的概念(下图中标注的有)。 22 | 23 | 40的长度是由三部分组成 24 | 25 | 1.存储值的缓冲区(3个指针长度) 3 * 8 = 24 26 | 27 | 2.元数据 8 28 | 29 | 3.目击表(vtable 可以有0个或者多个 这里有1个) 8 30 | 31 | 来来,看看我大笔一挥画的灵魂示意图: 32 | ![image](https://github.com/Liaoworking/Advanced-Swift/blob/master/pic/ProtocolInternals01.jpeg) 33 | 34 | #### 下面我们来说说什么是目击表?(只做了解即可) 35 | 目击表是让```动态派发```成为可能的关键,为一个特定的类型将```协议的实现```进行编码,表中会包含一个指向特定类型中的实现的```入口```。 36 | 37 | ##### 知识点:如果我们合并多个协议,每多加一个协议,就会多 8 字节的数据块 38 | 39 | OC中的协议不需要封装在存在容器中,so~ 40 | 41 | MemoryLayout.size // 8 42 | 43 | ### 性能调优 44 | 45 | 不推荐❎ 46 | 47 | //隐式打包 48 | func printProtocol(array: [CustomStringConvertible]) { 49 | print(array) 50 | } 51 | 52 | 推荐✅ ```swift标准库```中大多数使用场景。 53 | 54 | //没有打包 55 | func printGeneric(array: [A]) { 56 | print(array) 57 | } 58 | 59 | -------------------------------------------------------------------------------- /第四章:可选值/4.1_3 序列_魔法数问题_可选值概览.md: -------------------------------------------------------------------------------- 1 | # 可选值 2 | 3 | ## 4.1哨岗值 Sentinel Value 4 | 定义:有的函数可能返回nil 当函数返回nil 我们将返回值 用作-1 等类似的值代替,这样的值就被称为["哨岗值"](https://en.wikipedia.org/wiki/Sentinel_value) 也叫魔法数(这里只做概念了解)。 5 | 6 | 7 | 8 | ## 4.2通过枚举来解决魔法数的问题 9 | swift中Optional的本质是一个枚举(目前可以先不具体思考,等以后的项目中慢慢就领悟到了)。 10 | 11 | enum Optional { 12 | case none //为空 13 | case some(wrapped) // 有值 14 | } 15 | 16 | 17 | 知识点:当返回值为可选值 ? 时,实质上返回的是一个Optional的枚举 18 | 19 | ///返回值为Optional的实质 20 | let strArray = ["one","two","three"] 21 | switch strArray.index(of: "four") { 22 | case .none: 23 | print("返回值为空") //返回值为空 24 | case .some(let idx): 25 | print(idx) 26 | print("有值") 27 | } 28 | 29 | ## 4.3可选值概览:Optional 30 | 31 | ### if let 32 | 相信大家在swift 入门的时候就已经能很好的使用这个语法了。 不赘述。 33 | 34 | #### 技巧1: 可与Bool 联合判断 35 | if let obj = Optional , Bool { 36 | /// 前面有值且后面Bool为true执行 37 | } 38 | 39 | #### 技巧2: 多let 并列。 这个在开发中也经常用到 40 | 41 | if let obj1 = Optional1, let obj2 = Optional2, ....{ 42 | ///当上面的let判断都有值的时候执行 43 | } 44 | 45 | 46 | ### while let 47 | 类似于if let 遇到```nil时终止循环``` 48 | 也有和上面👆技巧1(```一旦为false 就循环停止,并不是filter那样去工作```)相同的使用方法。不赘述。 49 | 50 | while let line = printLine() { 51 | ///当printLine为空时候终止循环 52 | print(line) 53 | } 54 | 55 | 56 | ### 双重可选值: 57 | 一个可选值可能会被另外一个可选值包装起来,形成了可选值的嵌套。你也可以理解成obj?? 。平时在复杂逻辑操作时注意一下就可以了。 58 | 59 | ### if var 和 while var 60 | 和 let 的使用类似。 但你var 出来的obj实际上是一个```本地副本``` 你对obj做任何更改都```不会影响```原来的值。 61 | 62 | ///if var 的改变不影响原值 63 | let dict:[String:String]? 64 | dict = ["key1":"value1"] 65 | if var varDict = dict { 66 | varDict["key1"] = "value2" 67 | } 68 | print(dict) //Optional(["key1": "value1"]) 69 | 70 | 71 | ### 解包后可选值的作用域 72 | if let obj = Optional { 73 | ///obj的作用域 74 | } 75 | 76 | #### 注:有时候我们需要```提前退出```来避免烦人的if嵌套,这个时候 ```guard let``` 的优点马上就体现出来了. 77 | 78 | ///为空提前退出 79 | guard let obj = Optional else{return} 80 | 81 | 82 | 83 | ### 可选链 84 | OC中对nil发送消息nothing had happened。 在swift中可以通过可选链(Optional chaining)达到一样的效果 85 | 86 | 那么问题来了,什么是可选链? 87 | ```可选链``` 中的可选是```?```,可以把```链```这个字理解成swift中的点语法来链式调用方法,可以在喵神的另外一本书《swift链式编程》中对这个有更深的讲解。 88 | 89 | delegate?.callback() //就是一个可选链。 90 | 91 | //当上面的delegate为可选时编译器会发出警告。 确实有值方法会被调用。没有值时候方法不会被调用。 92 | 93 | 94 | ps. 😂写了这么多年swift的delegate和闭包 直到自己写笔记的时候才发现自己写复杂了(一点swift的优点都没体现出来。。。) 95 | 96 | var voidCallback:(()-> Void)? 97 | 98 | ///对于闭包调用的两种写法 99 | //❌不推荐 100 | if voidCallback != nil { 101 | voidCallback!() 102 | } 103 | 104 | //✅ 推荐 105 | voidCallvack?() 106 | 107 | 108 | #### 多个可选链的调用。下面这个例子简单易懂,不赘述。 109 | 110 | extension Int { 111 | 112 | var half: Int? { 113 | guard self > 1 else {return nil} 114 | return self/2 115 | } 116 | 117 | } 118 | ///多可选链调用 119 | 20.half?.half?.half //Optional(2) 120 | 121 | 122 | ### nil合并运算符 ?? (项目中经常会用到!!可以让你的代码写的很优美~) 123 | 一开始看到标题```nil合并运算符```的时候我是😳的。这都是啥啥啥啊。 当我看到 ```??``` 这个符号时候豁然开朗。两个是一个意思 项目里面已经用过很多次了,百试不爽。 124 | 125 | ```使用场景```: 当去解包```!```一个可选值,且当这个可选值为nil时,我们要去赋一个```默认值```防止崩溃 这个时候就用到了 ```??``` 126 | 127 | var number: Int? 128 | number = nil 129 | String(number ?? 5) //5 130 | 131 | 感觉上是和OC 中的 三目运算符 Bool ? A: B 类似 132 | ~~需要强调的是可选值```不是指针```~~ 133 | 134 | ### 可选值map 135 | ### 可选值flatMap(Demo值得借鉴) 136 | flatMap 是一个很有趣的函数 137 | flat的意思是“扁平的”,有一个主要作用就是降维。可以把```多维数组降低维数```。 下面的demo主要介绍flatMap的```转换功能``` 138 | 139 | ///将一个网络图片加载出来的奇淫方法!!! 140 | let urlString = "http://www.objc.io/logo.png" 141 | let view = URL(string: urlString) 142 | .flatMap { (url) -> Data? in 143 | try? Data(contentsOf: url) 144 | } 145 | .flatMap { (data) -> UIImage? in 146 | UIImage(data: data) 147 | } 148 | .map { (image) -> UIImageView in 149 | UIImageView(image: image) 150 | } 151 | 152 | if let view = view { 153 | UIView().addSubview(view) 154 | } 155 | 156 | 利用swift中的```$0```语法简化上面的代码 157 | 158 | 159 | let view2 = URL(string: urlString) 160 | .flatMap { 161 | try? Data(contentsOf: $0) 162 | } 163 | .flatMap { 164 | UIImage(data: $0) 165 | } 166 | .map { 167 | UIImageView(image: $0) 168 | } 169 | 170 | if let view2 = view2 { 171 | UIView().addSubview(view2) 172 | } 173 | 174 | 这里的```flatMap```和```if let``` 非常相似。 175 | 这样我们就将一个网络图片加载出来了。 176 | 想想自己之前写了那么多的垃圾代码,不禁长叹一句:```swift🐂🍺!!!``` 177 | 178 | ### 使用flatMap过滤nil 179 | 同样这里我们用一个demo去说明用法 180 | 181 | ```目的```:我们想要求一个字符串数组中的数字和。 182 | 183 | let numbers = ["1","2","3","4","liaoworking"] 184 | ///普通青年 185 | var sum = 0 186 | for case let i? in numbers.map({ 187 | Int($0) 188 | }) { 189 | sum += i// Int($0)为nil就不走这里了 190 | } 191 | // sum的值为10 192 | 193 | ///优质青年 194 | ///当我们用?? 把nil替换成0 195 | numbers.map { Int($0) }.reduce(0) { $0 + ($1 ?? 0)} //10 196 | 197 | ///文艺青年 198 | ///在标准库中flatMap的作用可能正是你想要 199 | numbers.flatMap { Int($0) }.reduce(0, +) // 10 200 | /// swift4.1后的筛选作用的flatMap的命名 201 | numbers.compactMap { Int($0) }.reduce(0, +) // 10 202 | 203 | 注:这里的flatMap的作用:把一个映射为可选值的序列进行展平。 204 | 205 | 这个例子的确好吊,```不自己写一遍不知道还有很多高级用法自己不会。``` 学习了学习了~ 206 | 如果让我去面试swift开发。一定会选这个demo做一个面试题! 207 | 208 | 209 | ##### 注: 因为flat又有降维作用也有筛选的作用 210 | ##### 为了避免歧义,swift4.1中加入的compactMap来细分出flatMap的筛选作用[SE-0187](https://github.com/apple/swift-evolution/blob/master/proposals/0187-introduce-filtermap.md) 211 | 212 | ### switch case 匹配可选值 213 | 214 | /// switch case 215 | let numbers = ["1","2","3","4","liaoworking"] 216 | for i in numbers { 217 | switch Int(i) { 218 | case 0: 219 | print("0") 220 | case nil: 221 | print("can't convert to int") 222 | default: 223 | print("not in case") 224 | } 225 | } 226 | 227 | swift中case匹配是通过~=运算符进行的。 我们可以实现这个方法增加对```范围```(0..=, <, >操作符 后来在[SE-0121](https://github.com/apple/swift-evolution/blob/master/proposals/0121-remove-optional-comparison-operators.md)因为```安全原因```被移除了。 231 | 232 | 233 | ///在一个温度数组中 234 | let temp = ["-23", "0", "66.66", "warm"] 235 | 236 | Double("warm") 的值为nil 237 | 238 | Double("warm") < 0 是成立的 239 | 240 | 但你不能说warm是小于0度的是很冷的。 241 | 242 | 243 | 这显然不合情理。 244 | 245 | -------------------------------------------------------------------------------- /第四章:可选值/4.4 强制解包的时机.md: -------------------------------------------------------------------------------- 1 | # 可选值 ? 2 | 3 | ## 4.4强制解包(使用!)的时机 4 | 对于使用!有以下几种观点 5 | 6 | 1. 绝不使用 7 | 8 | 2. 使代码逻辑更清晰的时候使用 9 | 10 | 3. 不可避免的时候使用 11 | 12 | 13 | 我们通过特定的方法让可选值变成必选值,从而```巧妙的避开```强制解包。 14 | 可以先用```filter```去对序列进行排空处理,再通过```map```进行映射和排序。 15 | 如下面这个例子: 16 | 用来查找所有大于50岁的人 17 | 18 | 19 | let ages = ["liaoWorking":17,"wangzhuxian":16] 20 | /// 有强制解包 这里的强制解包绝对安全 21 | ages.keys.filter { name in ages[name]! < 50 }.sorted() 22 | ///巧妙的避开了强制解包 写的时候心里也踏实哈哈 23 | ages.filter { (_, age) in age < 50 } 24 | .map { (name, _) in name } 25 | .sorted() 26 | 27 | ##### 没怎么用过filter 和map的同学 先看一遍,再敲一遍,大概就知道其用途了。敲一遍还是```很有用```的🦆。 28 | 29 | 30 | ### 在调试版本中进行断言 31 | --- 32 | 我们只会在开发版本中进行断言(```assert```),发布版本就算了。 33 | 书中主要讲了利用断言进行调试 34 | 35 | assert(Bool, "error here") ///其中Bool为false时执行断言“error here” 36 | 37 | 注:断言assert 是仅在Debug 版本起作用 38 | 39 | 40 | -------------------------------------------------------------------------------- /第四章:可选值/4.5 多灾多难的隐式可选值.md: -------------------------------------------------------------------------------- 1 | # 可选值 2 | 3 | ## 4.5多灾多难的隐式可选值 4 | 5 | #### 定义:无论什么时候使用都会自动强制解包的可选值。(就是看起来像肯定有值, 但你后面写```?```也没啥问题,不太理解这句话直接看下面的场景和坑就行。) 6 | 7 | ###### 出现场景: 8 | swift调用```OC里面有返回值的方法```,其OC方法返回值就是隐式可选值。 9 | 假设OC里Person类有一个属性name是NSString类型的. 这里的name在OC里面可能是nil 10 | 11 | NSString *string = [Person alloc]init].name; 12 | 13 | /// 在swift中去调用时 我们这样写,编译器不会报错 但是当saySomething为nil的时候就会崩溃 14 | 15 | Person().name.count ❌ 16 | 17 | /// 我们需要自己在name属性后主动添加?防止因为隐式可选值引起的崩溃。 18 | 19 | Person().name?.count ✅ 20 | 21 | ###### 填坑: 22 | 前一段时间在上线项目中有一个崩溃。 23 | 就是因为调用OC方法返回了一个nil,而代码层面返回值是一个```没有说明是必选还是可选(看起来是必选 你写?也没有问题。)``` 24 | 没太注意,我就当成了必选。 25 | 结果上线后发现OC方法可能会返回nil。 26 | 项目一阵崩溃。 27 | 扎心了。。 希望各位同学看到后能踩在我的尸体上前行😂。。 28 | 29 | -------------------------------------------------------------------------------- /第四章:可选值/4.6 隐式解包可选值.md: -------------------------------------------------------------------------------- 1 | # 可选值 ? 2 | 3 | ## 4.6 隐式解包可选值 4 | 5 | 我个人对```隐式解包可选值```这个词的理解是 我们对一个可选值解包了,就变成一个隐式可选值了。可能由于翻译的原因绕了一点。 6 | 7 | 我们直接从下面的Demo来了解到底什么是```隐式解包可选值```。 8 | 9 | ### 隐式可选值行为 10 | --- 11 | 12 | Demo中是s就为```隐式解包可选值```。 这里会有点坑。给人的第一印象s是非可选值,但在调用的过程中s为nil就Crash。 13 | 14 | var s: String! = "liaoworking" 15 | // 当你第一眼看到s后面没有加?的时候 你的第一直觉是s不是可选 16 | s.count 17 | // 但这里的s是隐式可选的 ,因为你再初始化的时候解包了 print结果: Optional("liaoworking") 18 | print(s) 19 | s = nil 20 | print(s) // nil 21 | // 下面会造成Crash 22 | s.count // crash 23 | 24 | 25 | 书中解释了两点原因: 26 | 1. OC和C桥接到Swift中的属性在早期版本没有```nullable```关键字修饰的时候都是以这种形式存在。 在Swift4(印象中是这个版本)以后OC的属性新加了nullable关键字。才避免了```隐式解包可选值``` 27 | 28 | 2. 一个```隐式解包可选值```只是```短暂地```为nil,准备好之后就不是nil了。 书中举了XIB生成的属性的例子,大家可以自己思考一下这个例子。 29 | 30 | --------------------------------------------------------------------------------