Notes of Advanced Swift. 《swift进阶》学习笔记 swift 5.3
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
,有的时候会更长一些。
以XCode插件的形式添加到XCode中,一键格式化当前Swift文件。非常方便。
引用OC对象
的坑必须要考虑
该OC象是否可能为nil, swift默认引用的OC对象为必选
当oc对象为nil就会引起崩溃。?
,将OC对象标记为可选。在开发过程中有遇到几次崩溃都是没有考虑到这种情况。😿
let
放心大胆
的去使用定义好的值,而不用去考虑后面再哪里改变了这个值和安全性的问题。计算型属性
实现模型的转换
(Objective-C 到 Swift的一个思维转换)不同模块音乐模型
(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
able
结尾: Codable
表示当前协议可以添加一个新的功能
。Type
结尾:CollectionType
表示当前协议可以表示一种类型
。Convertable
结尾:CustomStringConvertible
表示当前协议可以做类型转换
。以后有自定义协议的时候,命名可以参照这三种情况去规范命名。
数组是否为空
的时候 大多都会写if array.count > 0 {}startIndex == endIndex``就可以。而count的底层是
遍历整个array求集合长度。当数组长度过大时
性能低```一些。更安全
有时候我们判断一个array? 是否为空会写出下面这样代码
var array:[String]?
/// 一番array 操作后
if array?.count != 0 {
///当数组长度不为0时
doSomething()
}
nil
时 也会走doSomething() 的逻辑 这个时候可能就会出现逻辑上的bug.map
、filter
、reduce
等函数,有时候可以使用标准库的其他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 ❌
allSatisfy(_:)
的用法,用于判断是否所有元素满足某一条件。// 判断是不是所有的元素都是大于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 ❌
// 其原因同上。
时常需要的常量
封装成你需要的属性extension
中创建一些类属性,让你的常量更优雅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
成功
或者失败
两种情况,而且成功或者失败的情况有很多种
的话。推荐你使用Swift5以后推出的Result
类型。toggle()
, 它的主要作用是让Bool值取反。btn.isSelected = !btn.isSelected
有了toggle方法后 直接可以 btn.toggle()
达到同样的效果。@autoclosure
关键字,让你的没有参数的闭包做函数的参数时,代码阅读性更强(只做了解,个人感觉在项目中使用的场景不多,使用的意义不大)。@autoclosure
算是使用机会比较少的一个关键字了,唯一的作用是使代码变的美观一些。使闭包的描述不再使用{}
, 而是更参数化用()
。 不太能理解@autoclosure
的同学可以看一下Swift中文文档闭包章节的最后一个知识点。 这个tip只做了解就好。default
分支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
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")
大幅度的减小一些耗时函数的编译时间
,具体可以参考Swift编译加速Tips这篇文章。在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?
}
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):的选项。
假设某电商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中的枚举还有很多很强大的用法,小伙伴们可以在开发过程中自己多尝试一下下~
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
maxiaoqing - https://github.com/maxiaoqing
gitKun - https://github.com/gitKun