在StackOverflow上经常提到SpriteKit自己做 内部缓存和重用。
如果我不重复使用纹理或地图集,那么缓存是什么 我可以从SpriteKit中获得重用行为吗?
答案 0 :(得分:18)
“Long story short: rely on Sprite Kit to do the right thing for you.” - @ LearnCocos2D
这是长篇故事,从iOS 9开始。
纹理笨重的图像数据不会直接保存在SKTexture
上
宾语。 (参见SKTexture
class
reference。)
SKTexture
延迟加载数据直到必要。创建一个
纹理,即使是从大型图像文件,也很快,消耗很少
存储器中。
纹理的数据通常是在相应的时候从磁盘加载的
精灵节点已创建。 (或者真的,只要需要数据,
例如,当调用size
方法时。)
纹理数据准备在(第一个)期间渲染 渲染过程。
SpriteKit为纹理笨重的图像提供了一些内置缓存 数据。两个特点:
纹理笨重的图像数据由SpriteKit缓存,直到 SpriteKit就像摆脱它一样。
根据SKTexture
类引用:“一旦SKTexture
对象已准备好渲染,它一直保持准备直到所有强大
将删除对纹理对象的引用。“
在目前的iOS中,它往往会延长时间,甚至可能更长 整个场景消失后。 A StackOverflow comment引用了Apple的话 技术支持:“iOS发布缓存的内存 + textureWithImageNamed:或+ imageNamed:当它认为合适时,为 例如,当它检测到内存不足的情况时。“
在模拟器中运行测试项目,我能够看到
纹理记忆在removeFromParent
后立即回收。
然而,在物理设备上运行,记忆似乎如此
萦绕;重复渲染和释放纹理
没有额外的磁盘访问。
我想知道:渲染内存是否可以在某些版本的早期发布 记忆关键的情况(保留纹理但不保留 目前显示)?
SpriteKit巧妙地重用缓存的庞大图像数据。
在我的实验中,很难不重复使用它。
假设您在精灵节点中显示了一个纹理,而是
与重用SKTexture
对象相比,您可以为[SKTexture
textureWithImageNamed:]
调用相同的图像名称。纹理
不会指针与原始纹理相同,但它
将分享庞大的图像数据。
无论图像文件是图集文件还是图片文件的一部分,都是如此 不
无论是否使用加载原始纹理,上述情况都是正确的
[SKTexture textureWithImageNamed:]
或使用[SKTextureAtlas
textureNamed:]
。
另一个例子:假设你创建了一个纹理图集对象
使用[SKTextureAtlas atlasNamed:]
。你拿一个
纹理使用textureNamed:
,你不保留地图集。
您在精灵节点中显示纹理(因此纹理是
保留在你的应用程序中),但你不打扰跟踪
缓存中的特定SKTexture
。然后你做了所有这些
重新:新纹理图集,新纹理,新节点。所有的
这些物品将被新分配,但它们相对而言
轻巧。同时,重要的是:庞大的图像数据
最初加载的将在实例之间透明地共享。
试试这个:按名称加载怪物图集,然后拍摄 其中一个orc纹理并在orc sprite节点中呈现它。 然后,玩家返回主屏幕。您编码orc节点 在应用程序状态保存期间,然后解码它 申请状态恢复。 (当它编码时,它没有 编码其二进制数据;它改为编码它的名字。)在 恢复应用程序,你创建另一个兽人(与新的地图集,纹理, 和节点)。这个新的兽人将与其分享其庞大的兽人数据 解码的orc?是。是的,orcking会。
几乎是让纹理不重用的唯一方法
纹理图像数据是使用[SKTexture
textureWithImage:]
初始化它。当然,也许UIImage
会自己做
内部缓存图像文件,但无论如何,SKTexture
负责数据,不会重复使用渲染数据
别处。
简而言之:如果你有两个相同的精灵出现在你的 在同一场比赛中,他们使用记忆是一个公平的赌注 有效。
将这两点放在一起:SpriteKit有一个内置缓存 坚持重要的庞大图像数据,并巧妙地重用它。
换句话说,它只是有效。
没有承诺。在运行测试应用程序的模拟器中,我可以很容易地证明 我之前SpriteKit正在从缓存中删除我的纹理数据 真的用它完成了。
在原型制作过程中,您可能会惊讶地发现合理 即使您从不重复使用单个地图集,也可以从您的应用中获得良好的行为 质地。
SpriteKit具有专门用于纹理图集的缓存机制。 它的工作原理如下:
您调用[SKTextureAtlas atlasNamed:]
来加载纹理图集。
(如前所述,这还没有加载庞大的图像数据。)
您会在应用中的某个位置强烈保留地图集。
稍后,如果您使用相同的号码致电[SKTextureAtlas atlasNamed:]
atlas名称,返回的对象将与指针相同
保留地图集。使用地图集中提取的纹理
那么textureNamed:
也将是指针相同的。 (更新:
在iOS10下,纹理不一定是指针相同的。)
应该提到纹理对象,不要保留它们的地图集。
所以我看到你正在构建自己的缓存和重用机制 无论如何。你为什么这样做?
最终,您可以获得有关何时保留或清除的更好信息 某些纹理。
您可能需要完全控制加载时间。对于
例如,如果您希望纹理在第一次出现时立即出现
渲染后,您将使用preload
中的SKTexture
方法
SKTextureAtlas
。在这种情况下,您应该保留引用
对于预加载的纹理或地图集,对吧?或者,将SpriteKit
无论如何为你缓存?不清楚。自定义地图集或纹理
缓存是保持完全控制的好方法。
在某个优化点(天堂过早地禁止!! ),
有意义的是停止创建新的SKTexture
和/或
SKTextureAtlas
一遍又一遍地对象,无论多么轻巧。
可能你会首先建立地图集重用机制,因为地图集
重量较轻(他们有一个纹理字典,之后
所有)。稍后您可以构建单独的纹理缓存机制
用于重用非地图集SKTexture
对象。或者也许你永远不会
绕过第二个。毕竟,你很忙,而且
该死的厨房并没有自我清理。
所有这一切,你的缓存和重用行为可能会最终结束 与SpriteKit类似。
SpriteKit的纹理缓存如何影响您自己的纹理缓存 设计?以下是要记住的事情(来自上面):
您无法直接控制释放庞大图像的时间 使用命名纹理时的数据。你发布你的推荐,和 SpriteKit会在需要时释放内存。
您可以控制加载庞大图像数据的时间,
使用preload
方法。
如果您依赖SpriteKit的内部缓存,那么您的atlas缓存
只需保留对SKTextureAtlas
个对象的引用,
不归还他们。 atlas对象将自动重用
整个应用程序。
同样,您的纹理缓存只需要保留引用
SKTexture
个对象,不归还它们。庞大的图像数据
将在整个应用中自动重复使用。 (这个很奇怪
不过,我有点儿了;验证良好行为是一种痛苦。)
鉴于最后两点,请考虑a的设计替代方案
单例缓存对象。相反,您可以保留使用中的地图集
在你的精灵对象或他们的控制器上。一生的
然后,控制器会将您应用中的所有来电atlasNamed:
重用指针相同的图集。
两个指针相同的SKTexture
个对象共享相同的内存,
是的,但由于SpriteKit缓存,反过来不一定
真正。如果您正在调试内存问题并找到两个SKTexture
你希望它们是指针相同的对象,但不是
仍然可能正在分享他们庞大的图像数据。
我是工具新手,所以我只测量了应用程序的整体内存使用情况 使用Allocations工具发布版本。
我发现“All Heap&匿名VM“将在两个之间交替 顺序运行时的稳定值。我跑了几次测试 使用最低内存值作为结果。
对于我的测试,我有两个不同的地图集,每个地图有两个图像; 调用地图集A和B以及图像1和2.源图像 是大的(一个760 KiB,一个950 KiB)。
使用[SKTextureAtlas atlasNamed:]
加载地图集。纹理是
使用[SKTexture textureWithImageNamed:]
加载。在表中
在下面, load 实际上意味着“放入精灵节点并渲染。”
All Heap
& Anon VM
(MiB) Test
--------- ------------------------------------------------------
106.67 baseline
106.67 preload atlases but no nodes
110.81 load A1
110.81 load A1 and reuse in different two sprite nodes
110.81 load A1 with retained atlas
110.81 load A1,A1
110.81 load A1,A1 with retained atlas
110.81 load A1,A2
110.81 load A1,A2 with retained atlas
110.81 load A1 two different ways*
110.81 load A1 two different ways* with retained atlas
110.81 load A1 or A2 randomly on each tap
110.81 load A1 or A2 randomly on each tap with retained atlas
114.87 load A1,B1
114.87 load A1,A2,B1,B2
114.87 load A1,A2,B1,B2 with preload atlases
* Load A1 two different ways: Once using [SKTexture
textureWithImageNamed:] and once using [SKTextureAtlas
textureNamed:].
在调查时我发现了一些关于内部的真实事实 SpriteKit中纹理和atlas对象的结构。
有趣?这取决于你感兴趣的东西!
SKTextureAtlas
当[SKTextureAtlas atlasNamed:]
加载地图集时,请检查
它在运行时的纹理显示了一些重用。
在Xcode构建期间,脚本编译来自个人的地图集
将图像文件分成若干大的精灵图像(受限于
大小,按@ 1x @ 2x @ 3x分辨率分组)。每一个纹理
atlas通过捆绑路径引用其精灵表图像,存储在
_imgName
(_isPath
设为true)。
地图集中的每个纹理都由其单独标识
_subTextureName
,其精灵内嵌textureRect
片材。
图集中共享相同精灵图像的所有纹理
将有相同的非零伊莎[{1}}和
_originalTexture
。
共享_textureCache
,本身就是_originalTexture
个对象,
可能代表整个精灵表图像。它没有
它自己为SKTexture
,其_subTextureName
为textureRect
。
如果地图集已从内存中释放然后重新加载,则为新副本
将有不同的(0, 0, 1,
1)
个对象,不同的SKTexture
对象和不同的_originalTexture
个对象。从我能做到的
看,只有_textureCache
(即实际的图像文件)连接了
旧地图集的新地图集。
_imgName
使用SKTextureAtlas
加载纹理时,
它可能来自地图集,但它似乎并非来自
[SKTexture textureWithImageNamed:]
。
以这种方式加载的纹理与上述不同:
它有一个短SKTextureAtlas
,如“giraffe.png”,并设置了_imgName
假
它有一个未设置的_isPath
。
它(显然)有自己的_originalTexture
。
由_textureCache
加载的两个SKTexture
个对象(带有
相同的图像名称除了textureWithImageNamed:
尽管如此,如上所述,这种纹理 配置与其他种类的纹理共享笨重的图像数据 组态。这意味着缓存是在接近实际的情况下完成的 图像文件。