Skip to content

Monkey-X-Byte/Advanced-Swift

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

302 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

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Swift 100.0%