1、Swift
String
- 自定义的数据结构可实现CustomStringConvertable协议来提供自定义的转字符串逻辑
- 代码中书写json字符串时,用多行字符串很方便
Collection
- swift中的集合是不可变的。
- mutating方法实质上是创建了一个新的副本
Function & Closure
- 函数永远不会返回,可标为 ->Never
- 数组的map方法
Class
- final关键字
- 子类的指定构造器要调用父类的指定构造器
Struct
- 值类型
- 不能继承
- 内存分配在栈上,创建销毁成本极低
Enum
- 原始值(rawValue),
enum SomeEnum: Int { }
中的Int表示原始值类型为Int- Int类型的原始值,默认从0开始,递增1
- String类型的原始值,默认为枚举的名字
- 也可手动指定原始值
- 也可创建不关联原始值的枚举
- 关联值。
case loaded(Data)
- 模式匹配。
- 通过
switch
或者if case
取出关联值
- 通过
- Optional是一个有关联值的枚举
- 要用
if let
取出封装的值
- 要用
Protocol
- { get }
- { get set }
- 通过extension为协议提供默认的实现,标准库中大量使用了这样的设计
ARC
weak定义的属性必须是可选类型
在闭包中捕获变量时一定要用
1
2
3
4{ [weak self] in
guard let strongSelf = self else { }
// code
}
2、View & Life-Cycle
loadView -> viewDidLoad -> viewWillAppear -> viewWillLayoutSubview -> viewDidLayoutSubview -> viewDidAppear
UITabBarController
UINavigationController
[controller1].push(controller2)的生命周期:
2.loadView -> 2.viewDidLoad -> 1.viewWillDisappear -> 2.viewWillAppear -> 2.viewWillLayoutSubViews -> 2.viewDidLayoutSubviews -> 1.viewDidDisappear -> 2.viewDidAppear
[controller1, controller2],pop()的生命周期:
2.viewWillDisappear -> 1.viewWillAppear -> 2.viewDidDisappear -> 1.viewDidAppear
ChildViewController
viewController之间的通信
Vc1 -> vc2 :
- 初始化
- 属性
Vc2 -> vc1:
Notification:
1
2
3
4
5NotificationCenter.default.post(
name: AViewController.notificationName,
object: nil, //通知的发出者
userInfo:["data": 42, "isImportant": true]
)1
2
3
4
5
6NotificationCenter.default.addObserver(
self, //观察的对象
selector: #selector(onNotification(notification:)), //通知响应函数
name: AViewController.notificationName, //通知名称
object: nil //可以指定从特定的对象接收通知
)
delegate
A实现代理
B调用A的代理(weak),并将数据传给参数
closure
A给B的闭包赋值,实现闭包的处理逻辑
B定义闭包属性,将数据传给闭包参数
3、UIView & Layout
- frame:视图在父view坐标系中的大小和位置
- bounds:视图在本身坐标系中的大小和位置
- center:视图的中心点在父view中的位置
- CALayer:每个视图都有一个layer属性,只呈现内容,不处理事件
- transform:矩阵的平移、缩放、缩放、旋转等变换
- 旋转之后,view的frame会变化,bounds和center不变
- 由于transform是基于视图中心点为原点的坐标,因此在变换时,首先将坐标系变为以左上角为原点的坐标系;然后将新坐标和frame相乘,得到转换后的frame;再将新的frame变为以中心点为原点的坐标,即得到最终结果
ScrollView
contentOffSet:滑动距离
contentOffset = scrollView.bounds.origin
在将子视图添加到scrollView中时,若立即获取其大小,则为0,因为还没有被渲染到屏幕上;此时可调用
layoutIfNeeded
方法来强制绘制,即可获得正确大小。添加subview到时候,会讲当前view到layout标记为无效,下次需要重新layoutSubviews
layoutSubviews
- 计算每个view的frame并更新,不能主动调用
- 从superview到subviews(自上到下)顺序发生,会对每个view调用layoutSubviews方法
- setNeedsLayout,将当前layout设置为无效
- layoutIfNeeded,如果layout被标记为无效,会立即调用layoutSubviews
- 重写layoutSubViews方法注意事项
- 调用super.layoutSubviews()
- 不要调用setNeedsLayout或者setNeedsUpdateConstraints方法,否则会导致无限循环调用
- 不要修改外部view的约束
UILabel
- numberOfLines
- lineBreakMode
- contentMode
UIControl
Event:
touchUpInside:在控件之内触摸并抬起事件
touchUpOutside:在控件之外触摸抬起事件
touchDown:单点触摸按下事件,点触屏幕
editingDidBegin: textField 编辑开始
editingChanged:textField 内容变化
editingDidEnd:textField 编辑结束
State:
normal //普通状态
highlighted //触摸状态
disabled //禁用状态
selected //选中状态
addTarget
removeTarget
UIButton
UITextField
- addTarget
- UITextFieldDelegate
UITextView
- 多行输入
- selectable
- editable
UISlider
- 滑动条、进度条
- 可设置进度圆点、当前进度和剩余进度的颜色、图片
Layout
- 原理:Cassowary算法
SnapKit
有父视图才能添加约束
创建约束和更新约束的对应关系不能变
在有可能产生压缩行为时,要指定约束的优先级
约束优先级范围1~1000, 默认值为最高1000
抗压缩, 默认优先级750, 优先级越高越不容易被压缩
抗拉伸, 默认优先级250, 优先级越高越不容易被拉伸建议:
尽量与同级view或者superview建立约束
如果view需要删除或者隐藏掉,尽量使用隐藏,而不使用removeFromSuperview
UIStackView
alignment,对齐方式
fill,拉伸填满
center,居中
firstBaseline、lastBaseline,在axis为horizontal的时候有效,代表按照第一行、最后一行baseline对齐
.top、bottom 当 axis 是 horizontal 的时候,按 上、下 方向对齐
.leading、trailing 当 axis 是 vertical 的时候,按 头、尾 方向对齐
spacing,间距
distribution,排列方式
fill,默认,按照抗压缩、拉伸优先级填满
equalSpacing,等间距布局,如果放不下,按照抗压缩优先级压缩
fillEqually,等宽、高布局
equalCentering,等中线间距布局,间距不小于spacing,如果放不下,按照抗压缩优先级压缩
fillProportionally,根据intrinsic content size按比例布局
4、Advanced View
Touches
事件的传递:IOKit.framework –(IOHIDEvent)–> SpringBoard –(mach port)–> App进程 –(RunLoop注册Source1,接收消息后调用 _UIApplicationHandleEventQueue() 将 IOHIDEvent 封装成 UIEvent)–> 分发
收到事件后,会按照UIApplication -> UIWindow -> subviews.reversed(),从顶至底确定第一响应者
触摸事件:
touchesBegan
touchesMoved
touchesEnded
touchesCanceled
手势:
tap
longPress
pan
pinch
rotate
添加多个手势,可用
require(toFail:)
定义他们的依赖关系
Rendering
- CPU 计算好显示内容提交到 GPU,GPU 渲染完成后将渲染结果放入帧缓冲区,随后视频控制器会按照 VSync 信号逐行读取帧缓冲区的数据,经过可能的数模转换传递给显示器显示。
- 如果两次屏幕刷新之间执行了一个长任务,那其中就会有一帧被跳过去,造成界面卡顿的感觉。
CALayer
每个UIView都有一个CALayer
UIView的父类UIResponder处理用户的交互
CALayer处理屏幕的显示
UIView 的 bounds、frame、transform、backgroundColor 其实都是基于 CALayer 的封装。
drawRect()
- 把 Context 看成画布,利用 Core Graphics 进行绘制
- 调用该函数时,CALayer会分配一个backing store,通过core graphics使用cpu将内容写入;然后backing store会反复在屏幕上渲染,直到
setNeedsDisplay
被调用,表示backing store需要更新
Creating Image
init(named:)
:图片放在bundle中,会自动缓存,适合多次使用的图片init(contentsOfFile:)
:图片放在磁盘中,适合较大的数据- 图片格式:
- JPEG:有损压缩,压缩率高,解压耗时;适用于从网络下载
- PNG:无损压缩,解码简单,支持Alpha通道;适用于包内的图片
resizableImages(withCapInsets:resizingMode:)
:- 用于背景,可根据内容自动缩小或者扩大
orientation
- 当方向改变时,UIKit会调用window的根视图控制器的
viewWillTransitionToSize:withTransitionCoordinator:
和willTransitionToTraitCollection:withTransitionCoordinator:
函数,根视图控制器又会向下传播消息,通知子视图控制器。 - 设置app支持的方向:
func application(UIApplication, supportedInterfaceOrientationsFor: UIWindow?)
- 设置视图控制器支持的方向:
var supportedInterfaceOrientations
5、List View & Storage
UITableView
DataSource
管理数据、提供cell
IndexPath
cell
section header/footer
index
UITableViewDiffableDataSource:自动判断数据的不同,效率更高
Delegate
- header and footer views
- heights for rows, headers, and footer
- height estimates
- row selections、swipes and other actions
- editing the table’s content
自定义Cell
Reuse
register(_ cellClass: AnyClass?, forCellReuseIdentifier identifier: String)
tableview.dequeueReusableCell(withIdentifier identifier: String, for indexPath: IndexPath)
重用时会自动调用
cell.prepareForReuse
当cell消失或者tableView滑动时,会把cell添加到reuse queue
Update
reloadData/reloadSection/reloadRows
beginUpdate/endUpdate
insertRows/insertSections
deleteRows/deleteSections
moveRows/moveSections
performBatchUpdates
datasource and operation must consistent
UICollectionView
DataSource
Delegate
UICollectionViewDiffableDataSource
Flow layout
Configure
item大小
滚动方向
section间距
最小行间距
最小item间距
UICollectionViewDelegateFlowLayout
Codec
Json -> model
decoder.decode(type, from: jsonData)
decoder.keyDecodingStrategy:设置命名规则转换
decoder.dateDecodingStrategy:日期转换格式
Model -> Json
encoder.encode(structure)
CodingKey
json的key和数据结构中的key不同,可在结构中
enum CodingKeys: string, CodingKey { }
手动Decode
init(from decoder: Decoder) throws { }
手动Encode
func encode(to encoder: Encoder) throws { }
Storage
File
- Bundle
- Document
- Cache
Database
- SQLite
- CoreData
UserDefaults
- 单例:
UserDefaults.standard
- set
- get
- 单例:
6、多线程和网络
Pthread, NSThread, GCD, NSOperation
GCD的使用步骤:
- 选择调度队列
- 选择执行方式
- 添加要执行的任务
- 任务被执行
网络请求
HTTP请求
apple原生
- URLConnection
- URLSession
- URLSession
- URLSessionConfiguration
- URLSessionTask
- CFNetwork
第三方框架
AFNetworking
AFURLSessionManager
创建并管理一个URLSession对象,
遵循 URLSessionTaskDelegate,URLSessionDataDelegate,URLSessionDownloadDelegate,URLSessionDelegate 等协议
AFHTTPSessionManger
AFURLSessionManager的子类
YTKNetworking
参考
7、设计与架构
设计原则
ETC:Easier To Change
DRY:Don’t Repeat Yourself
正交性:相互独立
解耦
避免继承,
用接口表达多态:protocol
用委托提供服务:HAS-A胜过IS-A
用extension进行功能扩展
SPR:Single Responsibility Principle
任何一个模块都有且仅有一个被修改的原因
KISS: Keep It Simple Stupid
一次做一件事(正交性)
OCP:Open Close Principle
对扩展开放,对修改关闭
LSP:里氏替换原则
使用父类的地方都可以使用子类替换
ISP:接口隔离
不应该强迫客户依赖于它们不用的方法
DIP:依赖反转
高层次不应依赖于低层次的模块,他们都应依赖于抽象
具体实现依赖于抽象
App架构
MVC
![image-20200813172008749](/Users/ltp/Library/Application Support/typora-user-images/image-20200813172008749.png)
构建
View Controller 负责创建 Model 和 View
更新Model
View Controller 通过 target/action 或者 delegate 接受 View 事件,改变自身内部状态、更改 Model
改变View
View Controller 通过订阅 Model 通知,在通知到达时改变 View 层级
ViewState
与 Model 无关的状态放在 View 或者 View Controller 中,只影响 View 或 View Controller 状态的 action 则不需要通过 Model 进行传递
测试
由于边界缺失,View Controller 难以测试,尤其是单元测试和接口测试
MVVM
![image-20200813172104969](/Users/ltp/Library/Application Support/typora-user-images/image-20200813172104969.png)
构建
Model 属于 View Model,View Controller 不需要为 View 准备和获取数据,而是将 View 绑定到 View Model 相应的属性上去
更新Model
当 View Controller 接受 View 事件时,将调用 View Model 的方法,由 View Model 改变自身内部状态或更改 Model
改变View
View Model 将负责观察 Model,并将 Model 的通知转变为 View Controller 可以理解的形式,View Controller 在通知到达时改变 View 层级
ViewState
View Controller 中将没有任何 View State,由 View 或 View Model 维护
测试
View Model 和 View/View Controller 是解耦合的, 接受 Action更新 Model,并最终更新 View 使用的值,都是状态,因此能独立测试
![image-20200813173230419](/Users/ltp/Library/Application Support/typora-user-images/image-20200813173230419.png)
8、单元测试
敏捷开发和自动化测试
- 迭代
单元测试
- 单测是一些操作的集合,有一个可观察到结果
- 有参数和返回结果才能测
- Given -> 函数名
- When -> 参数
- Then -> 断言结果
Test Double
工具:Cuckoo和Nimble
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class Clazz {
func getValue() -> Int {
return 1024
}
func callWithNoResult(value: Int) {
print("called with \(value)")
}
}
func testClazz() {
let mock = MockClazz()
//插桩
stub(mock) { stub in
when(stub).getValue().thenReturn(1)
}
//断言
XCTAssert(mock.getValue() == 1)
stub(mock) { stub in
when(stub).callWithNoResult(value: any()).thenDoNothing()
}
mock.callWithNoResult(value: 1024)
verify(mock, times(1)).callWithNoResult(value: 1024)
}Stub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19//协议
protocol RandomIntGenerator {
func random(fromExclude: Int, toExclude: Int) -> Int
}
//实现协议
class InnerRandomIntGenerator: RandomIntGenerator {
func random(fromExclude: Int, toExclude: Int) -> Int {
return Int.random(in: (fromExclude + 1)..<toExclude)
}
}
//mock一个数据
let mockRandomIntGenerator = MockRandomIntGenerator()
stub(mockRandomIntGenerator) { stub in
when(stub).random(fromExclude: 0, toExclude: 15).thenReturn(3)
when(stub).random(fromExclude: 3, toExclude: 15).thenReturn(5)
}
//使用mock的数据
let (m, n) = generateRandom2NumberNotEqualBetween(
x, y, randomGenerator: mockRandomIntGenerator)!Mock
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
27
28//协议
protocol StringElementsPrinter {
func showElements(printElements: [String])
}
//实现协议
class InnerStringElementsPrinter: StringElementsPrinter {
func showElements(printElements: [String]) {
printElements.forEach {
print($0, terminator: "")
}
print("")
}
}
//定义使用协议的函数
func showLines(printLines: [[String]], printer: StringElementsPrinter) {
printLines.forEach {
printer.showElements(printElements: $0)
}
}
//mock数据
let mockStringElementsPrinter = MockStringElementsPrinter()
stub(mockStringElementsPrinter) { stub in
when(stub).showElements(printElements: any()).thenDoNothing()
}
//使用mock
showLines(printLines: printLines, printer: mockStringElementsPrinter)
verify(mockStringElementsPrinter, times(3))
.showElements(printElements: any())