Core Data by tutorials 笔记(七)

本章主要介绍了一些影响 Core Data 的性能问题,以及优化的方法。如果你对CoreData的其他方面感兴趣请查看我之前的笔记或直接购买《Core Data by Tutorials》

Chapter 9:Measuring and Boosting Performance

一、Getting started

性能其实是一个需要在内存用量与速度之间的平衡问题,访问内存中的数据比磁盘中的数据要快很多,但是往内存中存入大量数据又会引起触发 low memory warnings,你的程序又很快会被系统干掉。所以这都要靠开发者自己去平衡。

本章提供了一个关于“雇员名录”的 Start Project,基于 tab-bar。第一次启动时间会非常的长,这也是作者故意这么做的,方便我们接下来优化。

二、Measure,change,verify

关于性能优化,作者主要列举了三个步,通过对这三步的反复执行形成一个个闭环,从而达到性能最优的目标。 ①. 使用 Gauges,Instruments 和 XCTest framework 等工具衡量性能。
②. 针对第1步结果,改写 code 提高性能。
③. 验证新加 code 对性能的提升。

反复执行这三步,达到性能最优的目的。

  1. Measuring the problem 首先运行这个 Start APP,切换到 Memory Report 查看具体的内存用量。

    上图2稳定下来的内存用量基本上就是整个程序最终的内存占用。可以看到整个内存占用还是相当可观的。接下来我们结合代码来分析一下。 通过对 seed.json 以及载入 seed 的方法的 review,发现引起内存占用的主要是载入了大量的图片,一个解决办法就是将图片从整个数据结构中剥离出来,真正需要时再载入内存。

  2. Making changes to improve performance 书中的做法是在数据 model 中新添加了一个 EmployeePicture 实体,用来专门存放图片相关属性(这里为其添加了一个 picture 的属性)。这里的 picture 属性设置为 Binary Data 并勾选了 Allows External Storage,意味着由 Core Data 自己决定将这些二进制数据单独保存到磁盘中还是保留在 sqlite 数据库中。
    将原 Employee 实体的 picture 属性重名为 pictureThumbnail,表明在 Employee 中只保存缩略图,从而缩减内存用量。 在 model 中创建 Employee 与 EmployeePicture 之间的 relationship。接下来就是对代码进行一些修改。如果是从网络 API 获取的图片,要看是否提供了缩略图的版本,因为这里将图片转换成缩略图的 绘图方法 也是会消耗一些性能的。

  3. Verify the changes 再次使用 Memory Report 来验证内存用量,发现已经从当初的 392MB 缩减为 41MB。

三、Fetching and performance

对 Fetch 进行优化同样是要平衡内存的占用率,为了提高启动速率,我们来削减不必要的 fetch。Core Data 提供了一个 fetchBatchSize 属性来避免 Fetch 多余数据,该属性的默认值为 0(没有启用)。

接着我们使用 Instruments 工具来分析一下(要选择Instruments Core Data template),默认的 Core Data template 包括下面三个工具:

  • Core Data Fetches Instrument

    Captures fetch count and duration of fetch operations. This will help you balance the number of fetch requests versus the size of each request.

  • Core Data Cache Misses Instrument

    Captures information about fault events that result in cache misses. This can help diagnose performance in low-memory situations.

  • Core Data Saves Instrument

    Captures information on managed object context save events. Writing data out to disk can be a performance and battery hit, so this instrument can help you determine whether you should batch things into one big save rather than many small ones.

我们点击 Record 按钮,等待 20 秒后停止,选择 Core Data Fetches 工具,下面的窗口会展示详细的信息,包括 fetch entity,fetch count,fetch duration

通过上图可以看出一次 imports 了 50 个 employees,而且完成整个 fetch 花了 2000 微秒,并不十分高效。 接着就来修改代码,书里将 fetchRequest.fetchBatchSize = 10,至于 fetchBatchSize 这个具体的数字应为屏幕显示条目数的 2 倍,iPhone 一次大概能显示 5 行,因此这里设为 10 比较合适。

最后来验证我们的修改效果,同样是运行 Core Data Fetches tool 20 秒,这次可以看到多个 fetches,排第一 fetch 因为只 fetch count 而不是所有的 objects,所以速度是最快的,随后,我们滚动 scrollView,fetch counts 每次为 10,表明每 fetch 一次 最多取回 10 个 objcet,也符合之前 fetchBatchSize 的设置。

更进一步的做法是使用 predicates 条件来限制返回的 data,仅仅 fetch 需要的数据即可。如果存在多个 predicate,那么将更具限制性的条件放在前面性能会更好一些。更多细节见官方Guide

"(active YES) AND (name CONTAINS[cd] %@)"就比"(name CONTAINS[cd] %@) AND (active YES)"更加高效。

最后我们换一种工具来衡量性能,这次使用的是 XCTest framework,这也是 Xcode 6 新增加的特性,可以用来进行性能方面的测试,关于单元测试可以看我Core Data by tutorials 笔记(六)

打开 DepartmentListViewControllerTests.swift,添加一个测试方法:

func testTotalEmployeesPerDepartment(){  
    measureMetrics([XCTPerformanceMetric_WallClockTime],
        automaticallyStartMeasuring: false) {
        let departmentList = DepartmentListViewController()
        departmentList.coreDataStack = CoreDataStack() 
        //标记开始measuring
        self.startMeasuring()
        let items = departmentList.totalEmployeesPerDepartment()
        self.stopMeasuring()
    } 
} 

这个方法使用 measureMetrics 来衡量具体 code 的执行时间。CMD+U 执行测试方法,会显示具体消耗的时间(不同设备需要的时间也不相同)。

我们来修改代码只获取 employee 的数目 count 而不是实际的对象,书中这里用到了NSExpressionDescription,修改完成后继续验证,果然又快了很多。

接着我们来获取 sales 的数目,同样是先验证,然后再修改代码,再次验证。这次我们在设置完 predicate 后,直接使用 countForFetchRequest 来获取 costs 数目。

获取 sales 还有没有更快的方法呢,答案是肯定的,因为 employee 有一个属性就是 sales,所以我们可以简单地使用 relationship 来获取:

public func salesCountForEmployeeSimple(employee:Employee) -> String {  
    return "\(employee.sales.count)"
}

-EOF-

如果感觉此文对你有帮助,请随意打赏支持作者 😘

chengway

认清生活真相之后依然热爱它!

Subscribe to Talk is cheap, Show me the world!

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!