iOS新牛笔记

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
    5
    NotificationCenter.default.post(
    name: AViewController.notificationName,
    object: nil, //通知的发出者
    userInfo:["data": 42, "isImportant": true]
    )
    1
    2
    3
    4
    5
    6
    NotificationCenter.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

  1. 计算每个view的frame并更新,不能主动调用
  2. 从superview到subviews(自上到下)顺序发生,会对每个view调用layoutSubviews方法
  3. setNeedsLayout,将当前layout设置为无效
  4. layoutIfNeeded,如果layout被标记为无效,会立即调用layoutSubviews
  5. 重写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

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)

  1. 构建

    View Controller 负责创建 Model 和 View

  2. 更新Model

    View Controller 通过 target/action 或者 delegate 接受 View 事件,改变自身内部状态、更改 Model

  3. 改变View

    View Controller 通过订阅 Model 通知,在通知到达时改变 View 层级

  4. ViewState

    与 Model 无关的状态放在 View 或者 View Controller 中,只影响 View 或 View Controller 状态的 action 则不需要通过 Model 进行传递

  5. 测试

    由于边界缺失,View Controller 难以测试,尤其是单元测试和接口测试

MVVM

![image-20200813172104969](/Users/ltp/Library/Application Support/typora-user-images/image-20200813172104969.png)

  1. 构建

    Model 属于 View Model,View Controller 不需要为 View 准备和获取数据,而是将 View 绑定到 View Model 相应的属性上去

  2. 更新Model

    当 View Controller 接受 View 事件时,将调用 View Model 的方法,由 View Model 改变自身内部状态或更改 Model

  3. 改变View

    View Model 将负责观察 Model,并将 Model 的通知转变为 View Controller 可以理解的形式,View Controller 在通知到达时改变 View 层级

  4. ViewState

    View Controller 中将没有任何 View State,由 View 或 View Model 维护

  5. 测试

    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
    23
    class 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())
0%