Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Liaoworking/Advanced-Swift

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

306 Commits

Repository files navigation

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这门语言的一些基础概念特点。在以后的章节里会对应一一讲解。略略略。。。

第二章 :内建集合类型

第三章 : 集合类型协议

第四章 : 可选值

第五章:结构体和类

第六章:函数

第七章:字符串

第八章:错误处理

第九章:泛型

第十章:协议

第十一章:互用性

全书终🌛🌛


补充:

关于swift的一些心得和建议:

写在最开始

代码规范性

可能有很多同学在一开始写swift代码时都不知道一些相关的代码规范,常量变量如何定义等等。

这里我推荐关于代码规范的三份指导文章, 对于一些同学的代码规范性会有很大的提升。


代码格式检查工具

我们项目中用的是Realm 团队的swiftLint 安装比较简单 大部分的警告Error(不影响运行)可以给你一些代码规范的指导

唯一缺点:会稍微增加一些编译时间

公司项目中不改动任何代码的二次编译时间需要3.82s

添加swiftLint后时间为4.279s,有的时候会更长一些。

如果你只是对代码格式化有要求
推荐使用nicklockwood大神写的SwiftFormat

以XCode插件的形式添加到XCode中,一键格式化当前Swift文件。非常方便。


All Tips

⭐️tip1:

swift项目引用OC对象的坑

swift项目引用OC对象时必须要考虑该OC象是否可能为nil, swift默认引用的OC对象为必选 当oc对象为nil就会引起崩溃。
最好在引用OC对象时手动添加一个?,将OC对象标记为可选。

在开发过程中有遇到几次崩溃都是没有考虑到这种情况。😿


⭐️tip2:

多使用let

let会让我们在很多时候放心大胆的去使用定义好的值,而不用去考虑后面再哪里改变了这个值和安全性的问题。

⭐️tip3:

通过计算型属性实现模型的转换(Objective-C 到 Swift的一个思维转换)

假设App中有一个全局播放器,我们需要把后台发给我们的不同模块音乐模型(ChildrenSongModel, PodcastModel)转换成统一的音乐模型(GenernalMusicModel)。

刚刚从Objective-C过渡到Swift时候的我的写法:

/// 统一音乐模型转换类
class MusicConvertManager {
 
 /// 将儿歌的音乐模型转换成统一音乐模型
 /// - Parameter childernSongModel: 儿歌模型
 /// - Returns: 统一的音乐模型
 static func convertChildrenSong(of childernSongModel: ChildernSongModel) -> GenernalMusicModel {
 let genernalMusic = GenernalMusicModel()
 genernalMusic.id = childernSongModel._id
 genernalMusic.url = childernSongModel.musicURL
 genernalMusic.name = childernSongModel.title
 return genernalMusic
 }
 
 /// 将播客的音乐模型转换成统一音乐模型
 /// - Parameter childernSongModel: 播客音乐模型
 /// - Returns: 统一的音乐模型
 static func convertChildrenSong(of podcastModel: PodcastModel) -> GenernalMusicModel {
 let genernalMusic = GenernalMusicModel()
 genernalMusic.id = PodcastModel.pid
 genernalMusic.url = childernSongModel.url
 genernalMusic.name = childernSongModel.name
 return genernalMusic
 }
}
/// 具体使用 不建议这样,每次写到这里都需要先想到MusicConvertManager类,再思考用哪个具体的方法。❎
MusicManager.shared.currentModel = MusicConvertManager.convertChildrenSong(of: jsonModel.childrenModel)

建议写法: 通过给具体的模型创建extension, 在extension中创建generalMusicModel的计算型属性方便阅读和使用。

/// 通过genernalMusicModel计算型属性转换统一的音乐模型。 PodcastModel转换同理。
extension ChildernSongModel {
 /// 统一的音乐模型 (如果是耗时操作建议缓存转换后的结果)
 var genernalMusicModel: GenernalMusicModel {
 let genernalMusic = GenernalMusicModel()
 genernalMusic.id = _id
 genernalMusic.url = musicURL
 genernalMusic.name = title
 return genernalMusic
 }
}
/// 具体使用 这样写便于阅读及使用方便。 ✅
MusicManager.shared.currentModel = jsonModel.childrenModel.genernalMusicModel

⭐️tip4:

自定义协议如何规范命名?

参考了55个系统API的协议命名规范我们可以把协议命名分三类:
1. 以able结尾: Codable 表示当前协议可以添加一个新的功能
2. 以Type结尾:CollectionType 表示当前协议可以表示一种类型
3. 以Convertable结尾:CustomStringConvertible 表示当前协议可以做类型转换

以后有自定义协议的时候,命名可以参照这三种情况去规范命名。


⭐️tip5:

array.isEmpty 效率比 arrya.count 更高

当我们去判断一个数组是否为空的时候 大多都会写if array.count > 0 {}
isEmpty 方法只有检查arraystartIndex == endIndex``就可以。而count的底层是遍历整个array求集合长度。当数组长度过大时性能低```一些。
不仅isEmpty效率高,而且会更安全

有时候我们判断一个array? 是否为空会写出下面这样代码

var array:[String]?
/// 一番array 操作后
if array?.count != 0 {
 ///当数组长度不为0时
 doSomething()
} 
其实当array为nil时 也会走doSomething() 的逻辑 这个时候可能就会出现逻辑上的bug.
用 isEmpty 就不会忽略这样的问题。

⭐️tip6:

集合上使用的一些函数式编程的性能提升建议。

上面提到了isEmpty的性能会好于count, 下面会引申一些类似的提升性能的用法。
操作集合我们经常会用到mapfilterreduce等函数,有时候可以使用标准库的其他API使性能提升。
// 取一个集合中第一个大于0的数
let numberArray = [-4,1,-1,2,3,9]
let firstPositiveNumber = numberArray.first(where: { 0ドル > 0 }) ✅
let firstPositiveNumber = numberArray.filter { 0ドル > 0 }.first ❌
// 第一个方法遍历到符合条件的元素后即停止, 第二个方法在所有元素都遍历完一遍后再去找第一个。
// 同上面还有 取出集合中的最大最小元素
let minNumber = numberArray.min() ✅
let maxNumber = numberArray.max() ✅
let minNumber = numberArray.sorted().first ❌
let maxNumber = numberArray.sorted().last ❌
在Swift4.2的时候推出了allSatisfy(_:) 的用法,用于判断是否所有元素满足某一条件。
某些时候可以替换filter。且对于长集合性能提升很大 具体使用场景如下:
// 判断是不是所有的元素都是大于0 isAllPositive为Bool
let isAllPositive = numberArray.allSatisfy { 0ドル > 0 } ✅✅✅
let isAllPositive = numberArray { 0ドル > 0 }.isEmpty ❌❌❌
// 第一个方法在遇到第一个元素不不符合条件就遍历结束 直接返回false
// 第二个方法需要把所有的元素都遍历一遍后再去看是否是isEmpty 长集合会性能低下。
判断是否包含一个元素: contains的性能要优于使用filter(_:)first(where:)的用法
// 判断是否包含 -1 这个元素
let isContiansNagtiveOne = numberArray.contains(-1) ✅
let isContiansNagtiveOne = numberArray.filter { 0ドル == -1 }.isEmpty == false ❌
let isContiansNagtiveOne = numberArray.first(where: { 0ドル == -1 }) != nil ❌
// 其原因同上。

⭐️tip7:

将你时常需要的常量封装成你需要的属性

OC中的宏是我们在之前开发中经常用到的一些常用属性的封装。
在swift中我们可以通过在extension中创建一些类属性,让你的常量更优雅
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

⭐️tip8:

当你需要的返回值有成功或者失败两种情况,而且成功或者失败的情况有很多种的话。推荐你使用Swift5以后推出的Result类型。

具体用法可看之前写过的一篇文章
它会让你的代码变的更简洁清晰。

⭐️tip9:

同样在Swift5.0中添加了bool值的新方法toggle(), 它的主要作用是让Bool值取反。

像我们在btn的按钮的状态改变的时候之前一般都会用 btn.isSelected = !btn.isSelected 有了toggle方法后 直接可以 btn.toggle() 达到同样的效果。

⭐️tip10:

使用@autoclosure 关键字,让你的没有参数的闭包做函数的参数时,代码阅读性更强(只做了解,个人感觉在项目中使用的场景不多,使用的意义不大)。

@autoclosure算是使用机会比较少的一个关键字了,唯一的作用是使代码变的美观一些。使闭包的描述不再使用{}, 而是更参数化用()。 不太能理解@autoclosure的同学可以看一下Swift中文文档闭包章节的最后一个知识点。 这个tip只做了解就好。

⭐️tip11:

switch 语句中尽量少的使用default 分支

当我们添加新的case时候 有些没有cover到的地方没有编译报错就会产生一些逻辑错误。
如果觉得编译报错太烦可以使用swift 5 出来的@unknown 关键字修饰default 分支 让新添加的case以编译警告的形式出现。

⭐️tip12:

打印 枚举的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

⭐️tip13:

多用 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")

多用guard let 去解包可以在很多情况下大幅度的减小一些耗时函数的编译时间,具体可以参考Swift编译加速Tips这篇文章。


⭐️tip14:

快速为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?
}

image

//使用后:
class Book {
 // 编译器自动补全的方法
 internal init(name: String?, pageCount: Int?) {
 self.name = name
 self.pageCount = pageCount
 }
 
 let name: String?
 let pageCount: Int?
}

⭐️tip15:

自定义enum中尽量不要使用 case none的枚举项。

原因Swift 自带 Optional 也有一个 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):的选项。

⭐️tip16:

用枚举去定义一些静态的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中的枚举还有很多很强大的用法,小伙伴们可以在开发过程中自己多尝试一下下~

⭐️tip17:

利用Swift的泛型优雅封装圆角带阴影的视图

在iOS的开发中,圆角带阴影都是一件比较头疼的事情。

但是利用Swift泛型Core Animation的一些知识,可以写出很优雅简洁的圆角阴影代码。

具体如下:

/// 阴影圆角的视图
class CornerShadowView<T: UIView>: UIView {
 
 var childView: T = T()
 
 override init(frame: CGRect) {
 super.init(frame: frame)
 configBaseUI()
 }
 
 private func configBaseUI() {
 childView = T()
 addSubview(childView)
 childView.frame = bounds
 }
 
 required init?(coder: NSCoder) {
 fatalError("init(coder:) has not been implemented")
 }
}

使用:

// 设置泛型的具体类为 UIButton
let cornerShadowView = CornerShadowView<UIButton>(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
// UIButton的基本属性设置
cornerShadowView.childView.setTitle("Hi", for: .normal)
// UIButton的圆角属性设置 可以进行二次封装,略。
cornerShadowView.childView.backgroundColor = .red
cornerShadowView.childView.layer.cornerRadius = 50
cornerShadowView.childView.layer.masksToBounds = true
// 阴影设置 可以进行二次封装,略。
cornerShadowView.layer.shadowColor = UIColor.black.cgColor
cornerShadowView.layer.shadowOffset = .zero
cornerShadowView.layer.shadowRadius = 20
cornerShadowView.layer.shadowOpacity = 0.8

更多Tips...

Contributors List:

maxiaoqing - https://github.com/maxiaoqing

gitKun - https://github.com/gitKun

About

Notes of Advanced Swift. 《swift进阶》学习笔记 swift 5.3

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

Languages

AltStyle によって変換されたページ (->オリジナル) /