一定要看,完整版iOS开发点滴

iOS 图片加载方式对比

2022-10-11  本文已影响0人  果哥爸

一. 引言

我们做启动优化和卡顿优化的时候,发现图片通过不同的加载方式,加载时长相差巨大,尤其在低端机型(iPhone6iPhone7iPhone8系列)上,不同方式的图片加载,加载时间可以相差10几倍-几十倍不等,相差至少一个数量级。

试想一下,如果你做某个需求,添加了一些图片的加载,而这个图片加载耗时比较大,直接影响了启动时长和卡顿率,导致稳定性数据有大的下降,到时领导对你的印象和后期绩效评估,估计都会有影响。

因此充分了解图片加载的不同方式之间优缺点,加载的过程是非常有必要,有利于我们针对不同场景采用不同加载方式来提升项目稳定性。

我们都知道图片加载主要有三种方式:

这里我们对三种图片的加载速度进行一个对比,并分析下原因:

二. 实验

这里分别选了两组图片:

然后分别将两组图片,放在Assets.xcassetsmain bundle里面。

image.png

放在main bundle的所有图片,加上前缀local

image.png

然后在FJFImageLocalLoadVC里面创建三个按钮分别是:

每次启动,点击一个按钮,循环去加载对应的图片,并输出每张图片的加载耗时:

1. 大图实验数据

A. 用Assets.xcassets管理通过 imageNamed加载

2022-09-03 16:18:11.412898+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:7.09605 ms
2022-09-03 16:18:11.414388+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:0.95403 ms
2022-09-03 16:18:11.415612+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:0.86498 ms
2022-09-03 16:18:11.416765+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:0.79906 ms
2022-09-03 16:18:11.417784+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:0.71502 ms
2022-09-03 16:18:11.418706+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:0.64099 ms
2022-09-03 16:18:11.419683+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:0.72193 ms
2022-09-03 16:18:11.420537+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:0.59795 ms
2022-09-03 16:18:11.421399+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:0.60701 ms
2022-09-03 16:18:11.422386+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:0.73600 ms
2022-09-03 16:18:11.422987+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:0.30601 ms
2022-09-03 16:18:11.424036+0800 FJFBlogProjectDemo[15202:794425] --------Asset catalog加载图片 函数耗时:0.81706 ms

B. 用bundle管理通过 imageNamed加载

2022-09-03 16:19:55.879618+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:141.64495 ms
2022-09-03 16:19:55.886850+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:6.81698 ms
2022-09-03 16:19:55.892923+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:5.73993 ms
2022-09-03 16:19:55.897756+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:4.54497 ms
2022-09-03 16:19:55.900638+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:2.71297 ms
2022-09-03 16:19:55.902903+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:2.18391 ms
2022-09-03 16:19:55.905131+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:2.14791 ms
2022-09-03 16:19:55.907324+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:2.11704 ms
2022-09-03 16:19:55.909492+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:2.09796 ms
2022-09-03 16:19:55.911705+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:2.13397 ms
2022-09-03 16:19:55.913859+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:2.08092 ms
2022-09-03 16:19:55.916220+0800 FJFBlogProjectDemo[15217:795488] --------文件 imageName加载图片 函数耗时:2.28393 ms

C. 用bundle管理通过 imageWithContentsOfFile加载

2022-09-03 21:39:53.313360+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:122.10906 ms
2022-09-03 21:39:53.317499+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:3.84998 ms
2022-09-03 21:39:53.321495+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:3.74699 ms
2022-09-03 21:39:53.325367+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:3.62003 ms
2022-09-03 21:39:53.329069+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:3.44801 ms
2022-09-03 21:39:53.332854+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:3.51596 ms
2022-09-03 21:39:53.336646+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:3.54803 ms
2022-09-03 21:39:53.340615+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:3.70693 ms
2022-09-03 21:39:53.344022+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:3.15106 ms
2022-09-03 21:39:53.346476+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:2.30098 ms
2022-09-03 21:39:53.348315+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:1.73199 ms
2022-09-03 21:39:53.349933+0800 FJFBlogProjectDemo[18445:937441] --------文件 imageWithContentsOfFile加载图片 函数耗时:1.54901 ms

2. 小图实验数据

A. 用Assets.xcassets管理通过 imageNamed加载

2022-09-03 16:25:07.374076+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:4.29296 ms
2022-09-03 16:25:07.375350+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:0.88501 ms
2022-09-03 16:25:07.375836+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:0.23699 ms
2022-09-03 16:25:07.376785+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:0.75495 ms
2022-09-03 16:25:07.377780+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:0.76401 ms
2022-09-03 16:25:07.378743+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:0.72801 ms
2022-09-03 16:25:07.379187+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:0.20504 ms
2022-09-03 16:25:07.379620+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:0.25606 ms
2022-09-03 16:25:07.380062+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:0.27001 ms
2022-09-03 16:25:07.380984+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:0.73600 ms
2022-09-03 16:25:07.383440+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:2.22194 ms
2022-09-03 16:25:07.384820+0800 FJFBlogProjectDemo[15271:798689] --------Asset catalog加载图片 函数耗时:1.15597 ms

B. 用bundle管理通过 imageNamed加载

2022-09-03 16:27:19.256526+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:189.96501 ms
2022-09-03 16:27:19.266366+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:9.21297 ms
2022-09-03 16:27:19.273456+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:6.66499 ms
2022-09-03 16:27:19.278714+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:4.87900 ms
2022-09-03 16:27:19.283533+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:4.44400 ms
2022-09-03 16:27:19.287708+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:3.83902 ms
2022-09-03 16:27:19.290927+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:2.94399 ms
2022-09-03 16:27:19.292683+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:1.63591 ms
2022-09-03 16:27:19.294186+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:1.41799 ms
2022-09-03 16:27:19.295716+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:1.43397 ms
2022-09-03 16:27:19.296240+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:0.44703 ms
2022-09-03 16:27:19.296694+0800 FJFBlogProjectDemo[15289:800441] --------文件 imageName加载图片 函数耗时:0.39005 ms

C. 用bundle管理通过 imageWithContentsOfFile加载

2022-09-03 16:27:42.501943+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:160.00700 ms
2022-09-03 16:27:42.507223+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:4.72999 ms
2022-09-03 16:27:42.511637+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:3.99196 ms
2022-09-03 16:27:42.515078+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:3.05498 ms
2022-09-03 16:27:42.517940+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:2.53499 ms
2022-09-03 16:27:42.520895+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:2.61807 ms
2022-09-03 16:27:42.524116+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:2.92397 ms
2022-09-03 16:27:42.525691+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:1.39809 ms
2022-09-03 16:27:42.526938+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:1.10292 ms
2022-09-03 16:27:42.528083+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:1.03700 ms
2022-09-03 16:27:42.528171+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:0.01800 ms
2022-09-03 16:27:42.528236+0800 FJFBlogProjectDemo[15295:801035] --------文件 imageWithContentsOfFile加载图片 函数耗时:0.01597 ms

从以上的实验数据,我们取十次数据的平均值来看,我们可以得出以下几个结果:

因此从这个结果,可以得出如下几个问题:

三. 分析

带着上面的两个疑问,我们通过抓捕堆栈和阅读源码等来分析,为什么三种加载方式会产生差异的原因。

3. 用Assets.xcassets管理通过 imageNamed加载图片

以下是通过InstrumentsTime Profiler获取到的用Assets.xcassets管理通过 imageNamed加载图片堆栈。

Assets.xcassets管理通过 imageNamed首次加载图片:

image.png

Assets.xcassets管理通过 imageNamed非首次加载图片:

image.png

从堆栈我们可以看出imageNamed加载方法实际上调用的是一个叫做UIAssetManager的类,每个Bundle会有一个UIAssetManager,它有一个strong-strongNSMapTable的属性,用来做缓存,这个可以参考SDImageAssetManager

因此这里可以大概推断下用Assets.xcassets管理通过 imageNamed加载图片过程:

流程图.jpg

备注:
rendition是 CoreUI.framework 对某一图像资源的不同样式的统称,如@1x,@2x,每一个rendition有一个renditionKey与之对应,renditionKey包含了不同的attribute,用于记录图片资源的参数.
CUIMutalbeStructuredThemeStore与CUIStructuredThemeStore,可以理解为可变和不可变的图像集,包含了不同的图像资源。

从上面的分析我们也可以知道,用Assets.xcassets管理通过 imageNamed加载图片,首次加载图片之所以耗时比较多的原因:

那为什么通过Assets.car加载图片资源,会比直接加载从bundle上加载耗时少至少一个量级呢?

这就需要了解下.car文件:

.xcassets里面的所有资源在编译过程结束后会编译为.car文件,而.car文件实际上是一种特殊的bom文件,而bom(Bill of Materials)是从NeXTSTEP继承下来的一种文件格式:

iOSmacOS操作系统,会通过私有库CoreUI.framework来解析car文件,中间会调用Bom.framework的接口来解析BOM.

我们可以通过在命令行输入: assetutil -I Assets.car, 查看Assets.car文件内部结构

assetutil -I Assets.car
[
  {
    "Appearances" : {
      "UIAppearanceAny" : 0
    },
    "AssetStorageVersion" : "Xcode 13.4 (13F17a) via AssetCatalogSimulatorAgent",
    "Authoring Tool" : "@(#)PROGRAM:CoreThemeDefinition  PROJECT:CoreThemeDefinition-520\n",
    "CoreUIVersion" : 738,
    "DumpToolVersion" : 738.1,
    "Key Format" : [
      "kCRThemeAppearanceName",
      "kCRThemeLocalizationName",
      "kCRThemeScaleName",
      "kCRThemeIdiomName",
      "kCRThemeSubtypeName",
      "kCRThemeGlyphWeightName",
      "kCRThemeGlyphSizeName",
      "kCRThemeDimension2Name",
      "kCRThemeDimension1Name",
      "kCRThemeDeploymentTargetName",
      "kCRThemeDisplayGamutName",
      "kCRThemeDirectionName",
      "kCRThemeSizeClassHorizontalName",
      "kCRThemeSizeClassVerticalName",
      "kCRThemeGraphicsClassName",
      "kCRThemeMemoryClassName",
      "kCRThemeIdentifierName",
      "kCRThemeElementName",
      "kCRThemePartName",
      "kCRThemeStateName",
      "kCRThemeValueName"
    ],
    "MainVersion" : "@(#)PROGRAM:CoreUI  PROJECT:CoreUI-738.1\n",
    "Platform" : "ios",
    "PlatformVersion" : "11.0",
    "SchemaVersion" : 2,
    "StorageVersion" : 17,
    "ThinningParameters" : "optimized <idiom 1> <subtype 1792> <scale 2> <gamut 1> <graphics 6> <graphicsfallback (5,4,3,2,1,0)> <memory 4> <deployment 7> <hostedIdioms (4)>",
    "Timestamp" : 1662280005
  },
  {
    "AssetType" : "Image",
    "BitsPerComponent" : 8,
    "ColorModel" : "RGB",
    "Colorspace" : "srgb",
    "Compression" : "deepmap2",
    "DeploymentTarget" : "2019",
    "Encoding" : "ARGB",
    "Idiom" : "universal",
    "Name" : "little_icon_1",
    "NameIdentifier" : 1592,
    "Opaque" : false,
    "PixelHeight" : 60,
    "PixelWidth" : 60,
    "RenditionName" : "little_icon_1.png",
    "Scale" : 1,
    "SHA1Digest" : "C161BEAD4ABCF455C8DFA2EF7901CF40EF40F2A5",
    "SizeOnDisk" : 334,
    "State" : "Normal",
    "Template Mode" : "automatic",
    "Value" : "Off"
  },

从解析出来的json我们可以看出,这个json里面存储图片的所有基本信息,比如名称,宽高、大小,倍数,编码类型,压缩类型,颜色空间等。

我们知道加载图片,不仅要加载图片的二进制数据,还要加载图片相关的基本信息,这里通过Asset.car来加载,可以在将Asset.car解析之后,直接获取到图片的基本信息和加载图片的二进制。

A. 用Assets.xcassets管理通过 imageNamed首次加载为什么耗时相对较大原因

B. 用Assets.xcassets管理通过 imageNamed加载为什么耗时相对最小

4. 用bundle管理通过 imageNamed加载

以下是通过InstrumentsTime Profiler获取到的用bundle管理通过 imageNamed加载图片的堆栈。

bundle管理通过 imageNamed首次加载图片的堆栈:

image.png image.png

bundle管理通过 imageNamed非首次加载图片的堆栈:

image.png

通过这个调用堆栈,我们可以看出用bundle管理通过 imageNamed加载图片的过程如下:

bundle管理通过 imageNamed加载图片.jpg

从这个加载过程,我们可以分析出如下结论:

A. 用bundle管理通过 imageNamed加载首次加载为什么耗时这么大

B. 为什么bundle管理通过 imageNamed加载图片耗时比用Assets.xcassets管理通过 imageNamed加载图片大(非首次)

5. 用bundle管理通过 imageWithContentsOfFile加载

以下是通过InstrumentsTime Profiler获取到的用bundle管理通过 imageNamed加载图片堆栈。

bundle管理通过 imageWithContentsOfFile首次加载图片的堆栈:

image.png

bundle管理通过 imageWithContentsOfFile非首次加载图片的堆栈:

image.png

通过这个调用堆栈,我们可以看出用bundle管理通过 imageWithContentsOfFile加载图片的过程如下:

bundle管理通过 imageWithContentsOfFile加载图片.jpg

从这个加载过程,我们可以分析出如下结论:

A. 用bundle管理通过 imageWithContentsOfFile加载首次加载为什么耗时这么大

B. 为什么bundle管理通过 imageWithContentsOfFile加载图片耗时比用bundle管理通过 imageWithContentsOfFile加载图片小(非首次)

四. 总结

1. 为什么Assets.xcassets管理通过 imageNamed加载耗时最少,用bundle管理通过 imageWithContentsOfFile加载耗时第二,用bundle管理通过 imageNamed加载,耗时最多。

2. 为什么放在bundle无论是通过imageNamed还是imageWithContentsOfFile来加载,为什么首次加载耗时都如此大。

五. 引用

Humble Assets Catalog
iOS拾遗—— Assets Catalogs 与 I/O 优化

上一篇下一篇

猜你喜欢

热点阅读