0%

Instruments工具包含了非常丰富的数据,可以自由选择查看各机型、各指标类目的具体性能数据,同样也正因为其展示的数据太过庞杂,可能让开发者不清楚该从如何入手去进行优化。

Xcode 13中Instruments中新增了Regressions模块,它会突出需要优先处理的性能问题,来简化工作流。

(温馨提示:本片内容来源自WWDC21:Diagnose Power and Performance regressions in your app

想了解更多WWDC2021内容的小伙伴,可以阅读我以下文章,欢迎多多交流和指正

一文带你读完WWDC21核心(新)技术点

APP终极性能生存指南

检测和诊断内存问题

iOS中的Hang是什么?该如何治理?

Regression

当一个APP相对与近期的版本,在性能或电量方面发生劣化,就称为回归(Regression)。

比如在上线一个新版本后,APP启动时间增加。

3:47

最新版本的启动时间,会与近期几个版本的启动时间的平均值进行比较,如果最新版本的启动时间更长,就会标记为回归。

4:33

Regression左侧栏汇总了哪些指标被回归,以及相较上几个版本劣化了多少。

  • Disk Writes

    磁盘和内存还有CPU一样都是受限制的资源,不检查磁盘写入会损耗和伤害底层设备,同时还可能造成Hang(用户操作超过250ms未响应记作一个Hang)和UI卡顿,甚至缩短电池寿命。

    • Insights

    Organzier新增了一个叫做Insights区域的区域,来提供一些性能优化建议

    10:29

  • File activities

    Instruments中的File activities也可以用来debug储存相关的问题。

    11:40

相关资料

Why is my app getting killed?

Triage TestFlight crashes with Xcode Organizer

Identify trends with the Power and Performace API

性能是客户端永恒的主题,我们希望用户在使用APP时,能够获得极致的性能体验,关注内存占用也正是因为这一点。2:18

(温馨提示:本片内容来源自WWDC21:Detect and diagnose memory issues

想了解更多WWDC2021内容的小伙伴,可以阅读我以下文章,欢迎多多交流和指正

一文带你读完WWDC21核心(新)技术点

APP终极性能生存指南

iOS中的Hang是什么?该如何治理?

诊断APP的电源和性能回归

  • Faster application activations

    减少APP的内存占用,可以提高APP在后台存活的机会,从而让APP更快的被激活。

    设备的内存空间是有限的,监控APP的内存使用情况,能够防止系统为了回收内存而主动终止APP。

    这样即使APP在后台运行,也能够保存当前用户的使用状态,从而避免再次从内存加载导致的耗时。

  • Responsive experience

    更好的响应速度

    策略性的将APP的内容加载到内存里,能够避免在用户使用APP时,因系统回收内存增加的等待耗时。

  • Complex features

    让用户体验更丰富的功能

    像加载视频、展示动画,这些复杂的功能往往需要占用更多的内存。因此有策略的使用内存,可以避免在用户使用APP的复杂功能时,被系统因内存占用问题而终止。

  • Wider device compatibility

    更好的设备兼容性

    让内存空间不太充足的老设备也能更好的使用APP的功能

内存占用结构

2:26

  • Clean Memory

  • Dirty Memory

    Clean Memory被分配,并写入内容后成为”脏内存“,脏内存包括:

    • 所有的堆分配(malloc)
    • 解码图像缓冲区
    • Frameworks
  • Compressed Memory

    压缩内存特指脏页(Dirty Pages)中暂未被访问的部分(Unaccessed pages),会在访问后解压,成为脏内存。

    (下文会详细介绍脏页(Dirty Pages)的概念)

    注:iOS中没有内存交换(Memory Swap)的概念,你可能在使用Instrument工具时,见到过Swapped字段,实际上所指的是Compressed Memory

内存占用 = Dirty Memory + Compressed Memory

内存画像工具

3:52

  • XCTest

    通过单元测试和U测试中的XCTest来检测开发环境的性能指标

    • XCTest可以测量以下性能指标:

      • 内存使用情况
      • CPU使用
      • 磁盘写入情况
      • 卡顿率
      • 执行时间(完成某一任务所花的全部时间)
      • APP启动时间
    • 通过XCTest检测内存使用情况

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      func testSaveMeal() {
      let app = XCUIApplication()
      let options = XCTMeasuireOptions()
      options.invocationOptions = [.manullyStart]
      measure(metrics: [XCTMemoryMetric(application: app)],
      options:options) {
      app.launch()
      startMeasureing()
      app.cells.firstMatch.buttons["Save meal"].firstMatch.tap()

      let savedButton = app.cells.firstMatch.buttons["Saved"].firstMatch
      XCTAssertTure(savedButton.waitForExistence(timeout: 30))
      }
      }
    • XCTest在Xcode 13中新增的两项收集性能情况的诊断产物

      开启:enablePerformanceTestsDiagnostics YES

      1
      2
      3
      4
      5
      xcodebuild test
      -project MealPannerApp.xcodeproj
      -scheme PerformanceTests
      -destination platform=iOS,name="iPhone"
      -enablePerformanceTestsDiagnostics YES
      • Ktrace files

        Ktrace file能够打开,并展示以下分析报告:

        • 当卡顿发生时的渲染通道的状态
        • 因主线程阻塞,导致的Hang(APP无法响应用户的输入或者行为超过250ms,即记作一个hang)
      • Memory graphs

        Memory graph展示APP进程的地址空间的快照

        我们可以生成任务开始(pre_XXX.memgraph)和结束(post_XXX.memgraph)两个时机的内存快照,从而查看这一阶段内存占用的变化。

        7:19

  • MetricKit & Xcode Organizer

    检测线上环境的内存指标

内存问题

  • 内存泄漏(Leaks)

    最常见内存泄漏的原因的是循环引用

    11:18

    • 通过命令行查看memgraph文件

      12:28

  • 堆大小问题(Heap size issues)

    堆是进程地址空间中储存动态分配的对象的段(section),有策略性的加载和释放内存,可以避免内存峰值过高引发OOM。

    • 堆分配回归(Heap allocation regressions)
    • 碎片化(Fragmentation)

堆分配回归

  • 使用vmmap -summary对memgraph文件进行分析

15:31

  • 我们主要关心Dirty SizeSwapped Size两列数据

    16:09

  • 对任务开始(pre_XXX.memgraph)和结束(post_XXX.memgraph)两个时机的内存快照进行对比

    16:42

  • 可以在下方看到按类聚合的,各类对象占用内存的大小

    17:15

    从图中可以看到,non-object类型的数据,占用内存约13M。在Swift中,non-object通常代表原始分配字节(raw malloced bytes)

  • 打印memgraph文件中,类型为non-object且占用内存超过500k的对象

    17:36

  • 打印对象的引用树

    18:00

  • 可以通过malloc_history -fullStacks打印对象的分配调用栈

    19:42

  • 当不确定是哪个对象有问题时,可以通过leaks --referenceTree,自顶向下打印进程的所有内存中最可疑的对象

    18:40

    • 可以通过--groupByType参数,按type聚合简化打印结果。

    19:08

碎片化

先介绍两个概念:页和”脏页“

  • 页(Pages):是最小的独立的内存单元
  • 一旦页中写入任何数据,都会使整页变成”脏页“

21:37

当内存准备写入新的数据时,系统会优先尝试使用脏页中的空闲内存,而当即将分配的内存过大时,即使空闲内存的总大小足够,但其并不是一段连续的内存空间,仍会开辟一个新的脏页去写入这些数据。

这些无法被使用的空闲内存就是”碎片化内存“

再例如以下这种情况:我们分配的对象总共使用了4个页,但每个页的占用空间只占50%。

23:19

当我们释放这些对象后,这些4个脏页的内存空间仍有50%的碎片化内存。

最理想化的状态应该将所有的分配的对象放在两页,如下图:

23:42

这样一旦释放这些对象时,仅会留有两个脏页,并且不存在碎片化内存。

23:52

  • 我们的目标:

    • 让分配的对象尽可能连续且拥有同样的生命周期
    • 碎片化率保持在25%以下
    • 使用自动释放池
    • 特别注意长时间运行的进程
  • vmman -summary xx.memgraph查看碎片化内存情况

    25:00

    • DefaultMallocZone

      平时开发者只需要关注这部分的内存空间,因为这是我们进行堆分配默认结束的地方

    • MallocStackLoggingLiteZone

      这个空间是所有堆分配结束的地方

  • 使用Instruments工具中的Allocations track查看内存情况

    26:41

    其中的Destroyed和Persisted分别对应上面所描述的内存释放后的空闲内存(free memory)和仍未释放的内存(remaining objects)

    26:42

回顾下我们的总体排查流程:

27:51

更多WWDC有关性能的Session

iOS memory deep dive WWDC18

Getting started with Instruments WWDC19

Understand and eliminate hangs from your apps WWDC21

性能是客户端永远绕不开的话题,一起来康康今年的WWDC提供了哪些有关性能提升的建议吧~

(温馨提示:本片内容来源自WWDC21:Ultimate application performance survival guide

想了解更多WWDC2021内容的小伙伴,可以阅读我以下文章,欢迎多多交流和指正

检测和诊断内存问题

一文带你读完WWDC21核心(新)技术点

iOS中的Hang是什么?该如何治理?

诊断APP的电源和性能回归

1:38

  • 使用的五种工具

    • Xcode Organizer
    • MetricKit
    • Instruments
    • XCTest
    • App Store Connect API
  • 参考的八个性能指标

    1:58

    • 电量

    • 启动时间

    • 超时响应率(Hang rate)

      APP无法响应用户的输入或者行为超过250ms,即记作一个hang

    • 内存

    • 磁盘写入

    • 滚动卡顿

    • APP终止

    • MXSignposts

  • 电量(battery usage)

    • 优化电池使用需要关注的几点:

      • CPU
      • 网络
      • 定位
      • 音频
      • 蓝牙
      • GPU

      其中CPU、网络、定位是电量消耗大户

    • 使用Xcode工具查看Debug期间的电量损耗情况

      4:10

      • 查看CPU超过20%的阶段(CPU High Utilization)
      • 查看CPU从闲置被唤醒的阶段(CPU Wake Overhead)

      4:41

    • 使用Instrument中的Time Profile工具查看该阶段更详细的信息

      (比如使用Location Energy Model确保应用如预期中正确地使用定位功能)

      这张是我本地截屏

    • 使用MetricKit收集用户的性能数据

      6:41

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      // 创建遵守`MXMetricManagerSubscriber`的协议的类AppMetrics
      class AppMetrics: MXMetricManagerSubscriber {
      init() {
      // 初始化时将AppMetrics加入到`MXMetricManager`单例中
      let shared = MXMetricManager.shared
      shared.add(self)
      }
      deinit {
      // 销毁时进行移除
      let shared = MXMetricManager.shared
      shared.remove(self)
      }

      // 处理每日的metrics
      func didReceive(_ payloads: [MXMetricPayload]) {

      }

      // 处理diagnostics
      func didReceive(_ payloads: [MXDiagnositcPayload]) {

      }
      }
  • 使用Xcode Organizer查看线上性能数据

    Xcode Organizer对MetrixKit收集到的数据进行了聚合,忽略了单一用户的详细数据,展现的是整体的趋势;

    而使用MetricKit可以更具针对性的对某一用户在一段时间内的性能表现进行分析。

    6:57

    • 查看用户的电量使用情况

    7:03

    • Regressions

      Regressions是Xcode 13新增的模块,它将各个版本的性能指标中发生劣化的情况单独拎出来展示,让开发者更清晰的看到哪些性能指标亟待优化。

      7:22

    • 查看问题发生时对应的代码

      7:58

  • 超时响应率和滚动卡顿(Hang rate & Scrolling)

    • 超时响应

      长时间无法响应是导致用户强退应用的重要原因

    • 滚动卡顿

      当在下一次屏幕刷新时,新的内容还未ready就会出现卡顿

    Hang rate & Scrollings是表明APP没有及时响应的两个指标,一旦发生会严重影响用户体验,甚至让用户在使用应用时产生挫败感,从而降低用户的使用APP的意愿。

    • 使用Xcode Organizer查看线上的卡顿情况

      • Hang rate

      9:40

      • Scrolling

      9:42

    • 使用Instrument进行分析

      10:05

      • Thread State Trace

        可以查看线程被阻塞的详细情况

      • System Call Trace

        可以查看系统函数调用的时机和时长

    • 使用XCTest进行性能测试

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      func testScrollingAnimationPerformance() throws {
      app.launch()
      app.staticTexts["Meal Planner"].tap()
      let foodCollection = app.collectionViews.firstMatch
      // `measure`函数默认重复5次,可以通过option设置手动停止
      let measureOptions = XCTMeasureOptions()
      measureOptions.invocationOptions = [.manuallyStop]
      // 开始measure
      measure(metrics: [XCTOSSignpostMetric.scrollDecelerationMetric],
      option: measureOptions) {
      // 滑动
      foodCollection.swipeUP(velocity: .fast)
      // 停止measure
      stopMeasuring()
      // 重置状态
      foodCollection.swipeDown(velocity: .fast)
      }
      }
    • 使用MetricKit收集线上数据

      在iOS 14中,MetricKit会收集用户使用过程中发生的问题,然后每24小时集中上报一次。

      在iOS 15和MacOS 12中,MetricKit仍是每天上报一次,但会在addSubscriber后,并且立刻进行回调

  • iOS 15中MetricKit新增动画性能检测的API,能够记录动画期间详细的性能数据和卡顿情况

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    func startAnimating() {
    // 标记动画开始执行
    mxSignpostAnimaitionIntervalBegin(
    log: MXMetricManager.makeLogHandle(category: "animation"_telemetry),
    name: "custom_animation")
    )
    }

    func animationDidComplete() {
    // 标记动画结束
    mxSignpost(OSSignpostType.end, log: MXMetricManager.makeLogHandle(category: "animation_telemetry"), name: "custom_animation")
    }
    1
    2

    > 注:MXSignpost是MetricKit中封装的API,可以用来监控关键代码的运行情况

    11:20

  • 磁盘写入

    • 使用Instrument中的File Activity查看磁盘写入情况

      13:22

      • 优化建议:

        • 对于频繁写入的case,推荐使用Core Data

        • 避免快速创建和删除文件

    • 使用XCTest进行性能测试

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      func testSaveMeal() {
      let app = XCUIApplication()
      let options = XCTMeasureOptions()
      options.invocationOptions = [.manullyStart]
      // 检测下面代码运行时的磁盘写入情况
      measure(metrics: [XCTStorageMetric(application: app)], options: options) {
      app.launch()
      startMeasuring()

      let firstCell = app.cells.firstMatch
      firstCell.buttons["Save meal"].firstMatch.tap()

      let savedButton = firstCell.buttons["Saved"].firshMatch
      XCTAssertTure(savedButton.waitForExistence(timeout: 2))
      }
      }
    • 使用Xcode Organizer查看线上磁盘写入情况

      14:35

      • 查看在Disk Writes报告中可以查看24小时内写入量超过1GB的case

      15:01

      • 在Xcode 13中,还可以得到一些优化建议

      15:22

    • 使用MetricKit收集用户的磁盘写入情况

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      // 标记开始磁盘写入
      func syncAllContentsToDB() {
      mxSignpost(OSSignpostType.begin,
      log:MXMetricManager.makeLogHandle(categroy: "diskWrite_telemetry"),
      name: "custom_diskWrites")

      // sync contents to database

      // 标记磁盘写入结束
      mxSignpost(OSSignpostType.end,
      log:MXMetricManager.makeLogHandle(category: "diskWrite_telemetry"),
      name: "custom_diskWrites")
      }
  • 启动时间

    • 使用Xcode Organizer查看线上启动数据

      18:15

      • 查看因启动超时导致的程序终止

      18:47

    • 使用Instrument中的App Launch工具进一步分析

      19:13

  • Memory

    • 使用Xcode Organizer查看线上内存使用情况

      20:21

    • 使用Instrument中的Leak、Allocations和VM Tracker三个模板

      • Leak检测内存泄漏
      • Allocations分析内存的生命周期
      • VM Tracker展示虚拟内存空间的使用情况
    • 使用MetricKit收集线上数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      func saveAppAssets() {
      mxSignpost(OSSignpostType.begin,
      log: MXMetricManager.makeLogHandle(category: "memory_telemetry"),
      name: "custom_memory")

      // save app metadata

      mxSignpost(OSSignpostType.end,
      log: MXMetricManger.makeLogHandle(category: "memory_telemetry"),
      name: "custom_memory")
      }

更多有关电量优化的Session:

Improving battery life and performance WWDC19

Analyze HTTP traffic in Instruments WWDC 21

更多Hang优化的Session:

Understand and Eliminate Hangs from you app WWDC21

更多滚动优化的Session:

Explore UI animation hitches and the render loop WWDC20

Eliminate animation hitches with XCTest WWDC20

更多磁盘写入优化的Session:

Diagnose power and performance regressions in your app WWDC21

更多启动优化的Session:

Why is my app getting killed WWDC20

更多内存优化的Session:

Detect and diagnose memory issues

更多关于性能工具的Session:

Diagnose performance issues with Xcode Organizer WWDC20

What’s new in MetricKit WWDC20

Identify trends with Power and Performance API WWDC20

Getting started with Instruments WWDC19

一年一度的WWDC又来了,一起来看看今年又有哪些值得关注的技术点吧~

(温馨提示:本片内容来源自WWDC21:Platforms State of the Union

想了解更多WWDC2021内容的小伙伴,可以阅读我以下文章,欢迎多多交流和指正

检测和诊断内存问题

APP终极性能生存指南

iOS中的Hang是什么?该如何治理?

诊断APP的电源和性能回归

Xcode Cloud

试想一个一站式的iOS开发体验,在Xcode中提交代码后,自动在云端编译并且测试,接下来开发者只需根据Xcode上显示的测试结果改进代码,当一切准备就绪,就会自动将应用分发到各平台的TestFlight中…,为了实现这样流畅的开发体验,Apple将开发流程中所涉及到的所有环节,都集成到了Xcode中。

温馨提示:Xcode Clould目前限时免费,大家可以在官网申请Beta版权限

截屏2021-06-08 下午9.53.50

  • 原有的开发流程

    • 各个环节

      Coding -> 测试 -> 整合 -> 分发 -> 改进

      这里的整合指的是功能分支合到主干分支的过程。

      截屏2021-06-09 上午7.25.41

    • 痛点

      每个环节依赖不同的工具,而环境切换的过程往往会中断我们编写代码的思路

  • Xcode Cloud

    一个负责持续集成和分发服务的工具

    • 优势:

      • 内置在Xcode,无需切换工作上下文;
      • 提交Commit后,自动在云端编译;
      • 灵活且可扩展,支持集成其他CI系统;
      • 数据安全,保证源码、密钥安全。
    • 工作流:

      • 选中项目

        截屏2021-06-09 上午7.39.33

        截屏2021-06-09 上午7.39.01

      • 确认工作流

        截屏2021-06-09 上午7.40.22

      • 授权访问源代码

        截屏2021-06-09 上午7.42.25

      • 连接App Store Connect

        截屏2021-06-09 上午7.41.50

      • 查看结果

        截屏2021-06-09 上午7.43.28

        • 左侧导航器会根据项目的分支名进行分组

        • 右侧详情页会显示所有动作的状态

          截屏2021-06-09 上午7.44.35

      • 增加新的工作流

        截屏2021-06-09 上午7.45.54

        • 设置触发编译的条件

          • 方案一:分支上每次代码变更时触发

          • 方案二:Pull Request分支上每次代码变更时触发

          • 方案三:对标签(Tag)进行修改时触发

            截屏2021-06-09 上午7.52.36

        • 选择Xcode的版本

          截屏2021-06-09 上午7.52.36

        • 添加Action

          截屏2021-06-09 上午7.54.36

          • 添加UI Test

            截屏2021-06-09 上午7.55.14

        • 查看Action运行结果

          截屏2021-06-09 上午7.57.03

          • 可以使用Xcode Cloud通过UI Test过程中的截屏查看运行过程

            截屏2021-06-09 上午7.57.37

  • 分支整合

    • 在Xcode中管理Pull Request

      截屏2021-06-09 上午7.58.36

    • 在当前编译的条件下,Xcode Cloud会自动构建和测试所有Pull Merge分支上的新Commit

      截屏2021-06-09 上午8.00.04

    • 还可以快速定位至评论中所涉及到的代码位置

      点击左侧栏中带有信息标识的类名,点击可以直接定位到对应的位置,并查看评论

      截屏2021-06-09 上午8.01.48

      截屏2021-06-09 上午8.03.02

  • 分发

    • 云端自动签名

      开发者无需关心描述文件和密钥等事项

    • 支持多平台 TestFlight自动分发(包括Mac版)

  • 改进

    • 在测试过程中,Xcode Organizer可以在几分钟内收到TestFlight用户的反馈信息,开发者可以根据用户描述的信息对代码进行改进

      截屏2021-06-09 上午8.04.24

Swift

异步

  • 理解asymc/await

    await表示当前调用者正在等待异步函数的结果

    比如:完成一次舞蹈演出需要三个 异步步骤:演员热身、工作人员从仓库找出道具、搭建舞台,完成这些演员才可以登台演出。

    • 原有嵌套式写法(伪代码):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      func prepareForShow {
      // 演员热身
      danceCompany.warmUp {
      // 工作人员从仓库找出道具
      self.crew.fetchStageScenery {
      // 搭建舞台
      self.setStage {
      // 登场演出
      dancers.moveToPosition
      }
      }
      }
      }
  • 使用async/await的直线排列式写法:

    1
    2
    3
    4
    5
    6
    func prepareForShow() async throws -> Scene {
    let dancers = try await danceCompany.warmUP(duration: .minutes(45))
    let scenery = await crew.fetchStageScenery()
    let openingScene = setStage(with: scenery)
    return try await dancers.moveToPosition(in: openingScence)
    }
  • 结构化并发(Structured concurrency)

    再比如:完成一次舞蹈演出需要三个 异步步骤:演员热身、工作人员从仓库找出道具、搭建舞台,完成这些演员才可以登台演出。其中演员热身工作人员从仓库找出道具、搭建舞台可以同时进行

    • 创建并发子任务

      1
      2
      3
      4
      5
      6
      func prepareForShow() async throws -> Scene {
      async let dancers = try await danceCompany.warmUP(duration: .minutes(45))
      async let scenery = await crew.fetchStageScenery()
      let openingScene = setStage(with: await scenery)
      return try await dancers.moveToPosition(in: openingScence)
      }
  • actors

    • actor

      1
      2
      3
      4
      5
      6
      actor StageManager {
      var stage: Stage

      func setStage(with scenery: Scenery) -> Scene {
      }
      }

      以前通过串行dispatch queue进行属性互斥访问的方式,由于需要管理很多冗余代码,很容易引入额外的竞争机制,从而出现问题, 而将actor作为一级构造嵌入到Swift里,可以底层设计层面避免了这种情况的发生。

  • MainActor

    函数会同步到主线程执行

    1
    2
    @MainActor
    func display(scene: Scene)

小结:async/await让异步代码写的更自然,结构化并发让代码更容易理解,actors用来构建安全的共享状态

Swift Playgrounds

现在在iPad上也能用Swift Playgrounds开发app了

截屏2021-06-09 上午8.10.55

  • 在iPad上可以直接发布应用到TestFlight

    截屏2021-06-09 上午8.12.18

Focus

  • 通知

    原有的通知无论优先级如何,都会以相同的方式堆积在我们的屏幕上,完全看不出哪一条通知更重要

    • 现在通知可以归类成四类干扰等级

      • Passive

        无声且不会唤醒设备,下一次拿起手机时显示,用于时效性不强的通知

      • Active(同之前默认的通知类型)

        带有音效和触感反馈,同以往的通知一样

      • Time Sensitive

        这一等级的通知会马上推送给用户,用户不点击会停留在锁屏页上一段时间,用于马上需要读取的通知

      • Critical

        即使设备静音,也会播放声效,只有关系到健康和安全的重大通知,才能设置成这一等级,且设置时需要用户授权

      除此之外,还有一种特别的通知:

      • 消息类型通知(信息或者通话)

        如果你开发的是通信类APP,可以告诉系统,这是消息或者通话类型的通知,系统会自动调整通知的样式和运行方式,以便人们更好的查看消息

    • 通知摘要

      为了避免人们错过一些通知,同时帮助用户能在自己想要的时间查看通知,系统提供了一种新的通知展示样式 — 通知摘要,用户可以使用通知摘要,在选择的时间来集中查看推送

      截屏2021-06-09 上午8.26.35

      • 消息类型:只展示Passive或Active类型的通知

      • 通知摘要的顶部有两个轮播位置

        • 轮播位通知选取规则:
          • 带有大缩略图的通知会比没有大缩略图的通知,优先入选
          • 默认情况:推送最多的APP会优先得到推荐,当然用户可以在此基础上个性化定制摘要
    • 等级调整

      系统根据用户和APP通知的交互行为,推荐用户将其调整为合适的干扰等级

      • 如果用户经常在专注模式使用某APP

        系统会建议将该APP调整为专注模式时允许接收通知

        截屏2021-06-09 上午8.27.35

      • 如果用户经常与某APP的Time Sensitive通知进行交互

        系统会建议将改APP的通知等级调回为Active

        截屏2021-06-09 上午8.30.33

      • 如果某APP不断发送通知,用户却没有反应

        系统会建议将这个APP发送的通知静音

        截屏2021-06-09 上午8.33.44

  • 小结

    • 通过设置通知相关性分数和合适的缩略图,使通知占据摘要顶部的轮播位置
    • 思考APP的通知最适合哪一干扰级别
      • 例如:如果是聊天软件,应该使用最新的User Notifications API来告诉系统来自APP的消息和来电通知
    • 使用最新的Focus Status API,在APP里显示用户的专注模式状态

Widget

通过Widget的新特性让你的APP更好用、且更容易被用户看到

  • 使用iPad上的超大尺寸小组件

    截屏2021-06-09 上午8.34.23

  • 通过Intents框架让Widget自动加入到智能堆叠小组件中

    截屏2021-06-09 上午8.35.14

SharePlay

人们聚会时,除了聊天以外,分享体验也很重要,SharePlay Kit的出现正是为了帮助人们在难以相见的时候,依旧保持紧密联系。

截屏2021-06-09 上午8.36.35

  • 入口

    • FaceTime
    • iMessage

    让人们在视频聊天的同时,共同进行一项活动,比如:共享视频播放、共享画布。

  • 共享视频播放

    截屏2021-06-09 上午8.37.18

在视频APP里采用Group Activities和AVPlayer同步,从而可以快速集成这一功能。

  • 共享画布

    基于Group Activities,将笔画发给FaceTime群组中的每一个人,从而实现协同绘画

    截屏2021-06-09 上午8.38.07

小结:只需要让APP实现Group Activities,就可以实现基于FaceTime的共享体验

3.4 mov 、add、sub指令

  • 已学mov指令的几种形式
    • mov 寄存器,数据 mov ax, 6
    • mov 寄存器,寄存器 mov bx, ax
    • mov 寄存器,内存单元 mov ax, [0]
    • mov 内存单元,寄存器 mov [0], ax
    • mov 段寄存器,寄存器 mov ds, ax

截屏2021-06-07 下午9.29.16

3.5 数据段

我们可以将一组长度为N(N <= 64)、地址连续、起始地址为16的倍数的内存单元当作专门存放数据的内存空间,从而定义了一个数据段。

比如我们用123B0H~123B9H这段空间来存放数据:

  • 段地址:123BH
  • 长度:10字节

问题3.5

写几条指令,累加数据段中的前3个字型数据

1
2
3
4
5
mov ax, 123BH
mov ds, ax
mov ax, [0]
add ax, [2] ;将数据段第二个字(偏移量为2)
add ax [4] ;将数据段第二个字(偏移量为4)

结构体

  • 定义一个结构体

    1
    2
    3
    4
    5
    struct Sales_data { 
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
    };
  • 定义结构对象

    1
    2
    3
    4
    struct Sales_data { /* ... */ } accum, trans, *salesptr; 
    // equivalent, but better way to define these objects
    struct Sales_data { /* ... */ };
    Sales_data accum, trans, *salesptr;

    定义结构体以分号结束

#include

预处理器是一个在编译器之前运行的程序,并且会改变我们程序的源码。

当预处理看到一个#include,会将指定头文件的内容替换掉#include

1
2
3
4
5
6
7
8
9
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif

使用#ifndef来防止头文件被多次引用

using declaration

通过使用using形式的定义,来简化写法,比如:using namespace::name

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
// using declaration; when we use the name cin, we get the one from the namespace std
return 0;
}

using std::cin;
int main()
{
int i;
cin >> i; // ok: cin is a synonym for std::cin
cout << i; // error: no using declaration; we must use the full name
std::cout << i; // ok: explicitly use cout from namepsace std

由于定义了using std::cin,我们可以在调用cin函数时,不添加std::namespace前缀。

String

  • 在使用string之前需要引入string头文件

    1
    2
    #include <string>
    using std::string

定义和初始化

1
2
3
4
string s1; // default initialization; s1 is the empty string 
string s2 = s1; // s2 isacopyof s1
string s3 = "hiya"; // s3 is a copy of the string literal
string s4(10, 'c'); // s4 is cccccccccc

复制初始化(copy initialize)和直接初始化(direct initialization)

当使用=时,编译器是复制初始化,而当忽略=时,我们使用的是直接初始化,比如:

1
2
3
4
5
6
7
8
string s5 = "hiya"; // copy initialization
string s6("hiya"); // direct initialization
string s7(10, 'c'); // direct initialization; s7 is cccccccccc

string s8 = string(10, 'c'); // copy initialization; s8 is cccccccccc

string temp(10, 'c'); // temp is cccccccccc
string s8 = temp; // copy temp into s8

读写字符串

1
2
3
4
5
6
7
// Note: #include and using declarations must be added to compile this code int main()
{
string s; // empty string
cin >> s; // read a whitespace-separated string into s
cout << s << endl; // write s to the output
return 0;
}

获取字符串的长度

1
auto len = line.size(); // len has type string::size_type

拼接字符串

1
2
3
string s1 = "hello, ", s2 = "world\n";
string s3 = s1 + s2; // s3 is hello, world\n
s1+=s2; // equivalentto s1=s1+s2
1
2
string s1 = "hello", s2 = "world"; // no punctuation in s1 or s2
string s3 = s1 + ", " + s2 + '\n';

注意+符号两边至少要有一个string类型,不能是字符字面量相加

1
2
3
4
string s4 = s1 + ", "; // ok: adding a string and a literal string 
s5 = "hello" + ", "; // error: no string operand string
s6 = s1 + ", " + "world"; // ok: each + has a string operand string
s7 = "hello" + ", " + s2; // error: can't add string literals
1
string s6 = (s1 + ", ") + "world";

相当于:

1
2
string tmp = s1 + ", "; // ok: + has a string operand
s6 = tmp + "world"; // ok: + has a string operand
1
string s7 = ("hello" + ", ") + s2; // error: can't add string literals

(“hello” + “,”) 符号两边都是常量

Exercise 3.3: Explain how whitespace characters are handled in the string input operator and in the getline function.

对于string类的输入函数,它会自动忽略开头的空白(空格、制表符、换行等等),从第一个真正的字符开始直到下一个空白。

对于getline()函数,它会保存字符串中的空白符,它读入数据,直到遇到换行符位置。

1
2
3
4
5
6
7
8
int main()
{
string line;
// read input a line at a time until end-of-file
while (getline(cin, line))
cout << line << endl;
return 0;
}

答案来自☞C++Primer第五版 第三章习题答案(1~10)

decltype

有时我们想将某一个表达式的结果的类型作为变量的类型

1
decltype(f()) sum = x; // sum has whatever type f returns.
1
2
3
4
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x has type const int
decltype(cj) y = x; // y has type const int& and is bound to x
decltype(cj) z; // error: z is a reference and must be initialized
1
2
3
4
// decltype of an expression can be a reference type
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // ok: addition yields an int; b is an (uninitialized) int
decltype(*p) c; // error: c is int& and must be initialized

decltype(*p)代表引用类型

  • decltype(()):仅代表引用类型

    1
    2
    int i;
    decltype((i)) d; // error: d is int& and must be initialized

Exercises Section 2.5.3
Exercise 2.36:
In the following code, determine the type of each variable and the value each variable has when the code finishes:

int a = 3, b = 4;

decltype(a) c = a;

decltype((b)) d = a;

++c;

++d;

答:c : int, d int &

Exercise 2.37: Assignment is an example of an expression that yields a reference type. The type is a reference to the type of the left-hand operand. That is, if i is an int, then the type of the expression i = x is int&. Using that knowledge, determine the type and value of each variable in this code:

int a = 3, b = 4;

decltype(a) c = a;

decltype(a = b) d = a;

答:c : int ; d : int &

Exercise 2.38: Describe the differences in type deduction between decltype and auto. Give an example of an expression where auto and decltype will deduce the same type and an example where they will deduce differing types.

答:针对引用类型,如下: a是int类型,b是int &

1
2
3
4
5
int i = 0, &r = i;

auto a = r;

decltype(r) b = i;

实验一

环境准备

DEBUG常用命令

截屏2021-06-04 上午12.58.45

  • 修改寄存器的值

    1
    r ax

    截屏2021-06-04 上午7.38.28

  • 查看当前寄存器的值

    1
    r

    截屏2021-06-04 上午7.39.48

  • 执行一条指令

    • 修改代码段寄存器

      1
      r cs
    • 修改指令指针寄存器

      1
      r ip
    • 执行

      1
      t

输入以下指令:

截屏2021-06-04 上午7.55.53

  • 查看内存中的内容

    1
    d
    • 指定内存位置

      1
      d 073F:0100

      截屏2021-06-04 上午7.58.37

  • 将内存中的机器指令翻译成汇编指令

    1
    u 073F:0100

    截屏2021-06-04 上午8.00.15

    依次执行这些指令观察寄存器的变化:

  • 修改代码段寄存器和指令指针寄存器的位置

    截屏2021-06-04 上午8.06.55

  • 依次执行…

    截屏2021-06-04 上午8.07.44

第三章 寄存器(内存访问)

3.1 内存中字的存储

在0地址处开始存放20000(4E20H):

截屏2021-06-04 上午12.10.10

小端模式:高字节存放高地址位,低字节存放低地址位(0号单元是低地址单元,1号单元是高地址单元)

问:1地址单元存放的字型数据是多少?

答:124EH

结论:任何两个地址连续的内存单元,N号单元和N+1号单元,可以将它们看成两个内存单元,也可以看成一个地址为N的字单元中的高位字节单元和低位字节单元。

3.2 DS和[address]

  • CPU要读取一个内存单元的时候,必须先给出这个内存单元的地址
  • 在8086PC中,内存地址由段地址和偏移地址组成
  • 8086CPU中有一个DS寄存器,通常用来存放要访问的数据的段地址

例如:我们要读取10000H单元的内容可以用如下程序段进行:

mov bx, 1000H

mov ds, bx

mov al, [0]

上面三条指令将10000H(1000:0)中的数据读到al中

mov指令的功能

  1. 将数据直接送入寄存器

    mov ax,2

  2. 将一个寄存器中的内容送入另一个寄存器

    mov bx,ax

  3. 将一个内存单元中的内容送入另一个寄存器

    mov al, [0]

    • 格式:mov 寄存器名, 内存单元地址
    • […]表示一个内存单元,[…]中的0表示内存单元的偏移地址。
    • 执行指令时,8086CPU自动取DS中的数据为内存单元的段。

注意:8086CPU不支持将数据直接送入段寄存器 (由于硬件设计问题)

mov ds, 1000H是非法的 ❌

正确做法:数据->通用寄存器->段寄存器 ✅

怎样将数据从寄存器al送入内存单元10000H?(反向操作~)

mov bx, 1000H

mov ds, bx

mov [0] al

3.3 字的传送

因为8086CPU是16位结构,有16根数据线,所以,可以一次性传送16位的数据,也就是一次性传送一个字。

比如:

mov bx 1000H

mov ds, bx

mov ax, [0] ;1000:0处的字型数据送入ax

mov [0], cx ; cx中的16位数据送到1000:0处

问题3.3

TODO: P14

问题3.4

别名(Type Aliases)

  • 写法一
1
2
3
4
5
typedef double wages; // wages is a synonym for double
typedef wages base, *p; // base is a synonym for double, p for double*

wages i = 1.0;
p val = &i;
  • 写法二
1
using SI = int; // SI is a synonym for int

C++11支持

1
2
3
typedef char *pstring;
const pstring cstr = 0; // cstr is a constant pointer to char
const pstring *ps; // ps is a pointer to a constant pointer to char

auto类型

编译器会自动通过初试值来判断auto对象的类型

1
2
autoi=0,*p=&i; //ok: i is int and p isapointerto 
int auto sz = 0, pi = 3.14; // error: inconsistent types for sz and pi
  • auto会自动忽略top-level const,保留low-level const

    top-level const即自身为常量

    low-level const即自身可以修改,但是所指向(引用)的地址储存值为常量

    1
    2
    3
    4
    const int ci = i, &cr = ci;
    auto b = ci; // b is an int (top-level const in ci is dropped)
    auto c = cr; // c is an int (cr is an alias for ci whose const is top-level) autod=&i; // d isan int*(& ofan int objectis int*)
    auto e = &ci; // e is const int*(& of a const object is low-level const)
  • 设置为top-level const

    1
    const auto f = ci; // deduced type of ci is int; f has type const int