Advanced-Swift
The only way to learn a new programming language is by writing programs in it. 学习一种新的编程语言的唯一方法,就是用它编写程序。
--- Dennis Ritchie
Notes of Advanced Swift. 《swift进阶》学习笔记, 持续更新中。。。 swift 5.0 to swift 5.3ing.
第一章 介绍
博大精深
的东西,讲了很多swift这门语言的一些基础概念
和特点
。在以后的章节里会对应一一讲解。略略略。。。
一本书的第一章都是一些第二章 :内建集合类型
2.1数组
2.2字典
2.3 set 2.4 Range
第三章 : 集合类型协议
3.1 序列
3.2集合类型
3.3索引
3.4切片
3.5专门的集合类型
第四章 : 可选值
4.1---4.3 序列-魔法数问题-可选值概览
4.4 强制解包的时机
4.5 多灾多难的隐式可选值
第五章:结构体和类
5.1 值类型---5.2 可变性
5.3 结构体
5.4 写时复制
5.5_6 闭包和可变性_内存
5.7_8 闭包和内存
第六章:函数
6 函数(总体介绍)
6.1 函数的灵活性
6.2 局部函数和变量捕获
6.3 函数作为代理
6.4 inout参数和可变方法
6.5 计算属性和下标
6.6 自动闭包 6.7 总结
第七章:字符串
7.1 不再固定宽度
7.2 字符串和集合
7.3 简单的正则表达式匹配器。 7.4 ExpressibleByStringLiteral
7.5 String的内部结构
7.6 编码单元的表示方式
7.7 CustomStringConvertible 和 CustomDebugStringConvertible
7.8 文本输出流
7.9 字符串的性能
第八章:错误处理
8.1 result类型
8.2 错误和函数参数
8.3 带有类型的错误
8.4 将错误桥接到Objective-C
8.5 错误和函数参数
8.6 defer语法可以让代码更简洁
8.7 错误和可选值
8.8 错误链
8.9 高阶函数和错误
第九章:泛型
9.1 重载
9.2 对集合采用泛型操作
9.3 使用泛型进行代码设计
9.4 泛型的工作方式
第十章:协议
10.1 面向协议编程
10.2 协议的两种类型
10.3 带有 Self 的协议
10.4 协议内幕
第十一章:互用性
11.1 实践:封装 CommonMark
11.2 低层级类型概览
11.3 函数指针
🌛 🌛
全书终补充:
关于swift的一些心得和建议:
写在最开始
代码规范性
可能有很多同学在一开始写swift代码时都不知道一些相关的代码规范,常量变量如何定义等等。
这里我推荐关于代码规范的三份指导文章, 对于一些同学的代码规范性
会有很大的提升。
raywenderlich/swift-style-guide ⭐️ 10k
github/swift-style-guide ⭐️ 4.5k(有中文翻译)
airbnb/swift ⭐️ 1k
代码格式检查工具
我们项目中用的是Realm 团队的swiftLint
安装比较简单 大部分的警告
和Error(不影响运行)
可以给你一些代码规范的指导
稍微增加一些编译时间
唯一缺点:会公司项目中不改动任何代码的二次编译时间需要3.82s
添加swiftLint后时间为4.279s
,有的时候会更长一些。
如果你只是对代码格式化有要求
SwiftFormat
推荐使用nicklockwood大神写的以XCode插件的形式添加到XCode中,一键格式化当前Swift文件。非常方便。
All Tips
⭐️ tip1:
引用OC对象
的坑
swift项目必须要考虑
该OC象是否可能为nil, swift默认引用的OC对象为必选
当oc对象为nil就会引起崩溃。
swift项目引用OC对象时?
,将OC对象标记为可选。
最好在引用OC对象时手动添加一个在开发过程中有遇到几次崩溃都是没有考虑到这种情况。
⭐️ tip2:
let
多使用放心大胆
的去使用定义好的值,而不用去考虑后面再哪里改变了这个值和安全性的问题。
let会让我们在很多时候⭐️ tip3:
array.isEmpty 效率比 arrya.count 更高
数组是否为空
的时候 大多都会写if array.count > 0 {}
当我们去判断一个startIndex == endIndex``就可以。而count的底层是
遍历整个array求集合长度。当数组长度过大时
性能低```一些。
isEmpty 方法只有检查array更安全
不仅isEmpty效率高,而且会有时候我们判断一个array? 是否为空会写出下面这样代码
var array:[String]?
/// 一番array 操作后
if array?.count != 0 {
///当数组长度不为0时
doSomething()
}
nil
时 也会走doSomething() 的逻辑 这个时候可能就会出现逻辑上的bug.
其实当array为用 isEmpty 就不会忽略这样的问题。
⭐️ tip4:
时常需要的常量
封装成你需要的属性
将你OC中的宏是我们在之前开发中经常用到的一些常用属性的封装。
extension
中创建一些类属性,让你的常量更优雅
在swift中我们可以通过在SwiftUI标准库中大部分常量都是以这种方式封装。
extension UIFont {
/// APP中大标题的字体
static let appLargeTitle = UIFont.systemFont(ofSize: 24)
}
extension UIColor {
/// APP主题色
static let appMain = UIColor.yellow
}
let titleLabel = UILabel()
titleLabel.font = .appLargeTitle
titleLabel.backgroundColor = .appMain
⭐️ tip5:
成功
或者失败
两种情况,而且成功或者失败的情况有很多种
的话。推荐你使用Swift5以后推出的Result
类型。
当你需要的返回值有之前写过的一篇文章
具体用法可看它会让你的代码变的更简洁清晰。
⭐️ tip6:
toggle()
, 它的主要作用是让Bool值取反。
同样在Swift5.0中添加了bool值的新方法btn.isSelected = !btn.isSelected
有了toggle方法后 直接可以 btn.toggle()
达到同样的效果。
像我们在btn的按钮的状态改变的时候之前一般都会用 ⭐️ tip7:
用通俗的语言和使用场景向大家介绍@autoclosure 注解的使用 不了解的同学可以先google一下相关用法。
TODO-⭐️ tip8:
default
分支
switch 语句中尽量少的使用当我们添加新的case时候 有些没有cover到的地方没有编译报错就会产生一些逻辑错误。
@unknown 关键字修饰default 分支 让新添加的case以编译警告的形式出现。
如果觉得编译报错太烦可以使用swift 5 出来的⭐️ tip9:
打印 枚举的case名,输出并不是枚举的value值而是case的字面名字。
enum Animal: String {
case human = "H"
case dog = "D"
case cat = "C"
}
enum TimeUtile: Int {
case second = 1
case minute = 60
case hour = 3600
}
var animal: Animal = .human
var time: TimeUtile = .second
print(animal) // human
print(animal.rawValue) // H
print(time) // second
print(time.rawValue) // 1
⭐️ tip10:
guard let
少用 if let
多用 // 使用 if let 嵌套太多 不利于维护 ❌
if let realOptionalA = optionalA {
print("had A")
if let realOptionalB = optionalB {
print("had A and B")
if let realOptionalC = optionalC {
print("had A、B and C")
}
}
}
// 使用 guard let 调理清楚 便于阅读 ✅
guard let realOptionalA = optionalA else { return }
print("had A")
guard let realOptionalB = optionalB else { return }
print("had A and B")
guard let realOptionalC = optionalC else { return }
print("had A、B and C")
⭐️ tip11:
快速为Class生成带有属性的初始化方法
在struct中, 编译器会自动生成带有属性的初始化方法。
struct User {
let name: String?
var age: Int?
}
// 可直接调用
User(name: String?, age: Int?)
但对于class就没有对于的初始化方法。我们可以使用XCode提供的辅助功能来生成对应的初始化方法。
class Book {
let name: String?
let pageCount: Int?
}
//使用后:
class Book {
// 编译器自动补全的方法
internal init(name: String?, pageCount: Int?) {
self.name = name
self.pageCount = pageCount
}
let name: String?
let pageCount: Int?
}
⭐️ tip12:
自定义enum中尽量不要使用 case none的枚举项。
enum MyEnum {
case ok
case error
case none ❌
}
// 这个时候myEnum实际上是一个Optional的枚举值 而Optional 也有一个 none的枚举选项。
var myEnum : MyEnum? = .none
//可以通过指定类型解决 但不建议这样
var myEnum : MyEnum? = Optional.none
var myEnum : MyEnum? = MyEnum.none
这个时候编译器会报警告 而且你的switch中会多一个case .some(.none):的选项。
⭐️ tip13:
用枚举去定义一些静态的tableView数据源会让代码变的更简洁。
假设某电商app首页的tableView有4个section
// 电商首页的tableView 分组
//CaseIterable 用来获取枚举项个数
enum HomeSectionType: Int, CaseIterable {
// banner位
case banner = 1
// 合辑
case menu = 2
// 推荐
case recommend = 3
// 商品
case goods = 4
// 枚举内部封装组头高度的计算方法
var headerHeight: CGFloat {
switch self :
case banner:
return 88.88
.....
}
}
// tableView 代理
func numberOfSections(in tableView: UITableView) -> Int {
return HomeSectionType.allCases.count
}
// 获取组头高度
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
guard let sectionType = HomeSectionType(rawValue: section) else { return 0.0 }
return sectionType.headerHeight
}
这样就可以让tableView的代理看起来简洁明了。
CaseIterable
协议可以让你的枚举具备Array相关的属性,如count
还有一个好处就是当产品某个版本想要调换section的顺序
的时候 可以直接 修改枚举项的Int值
即可。
Swift中的枚举还有很多很强大的用法,小伙伴们可以在开发过程中自己多尝试一下下~
Contributors List:
maxiaoqing - https://github.com/maxiaoqing
gitKun - https://github.com/gitKun