ios UICollectionView 性能 - _updateVisibleCellsNow
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/16336772/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UICollectionView performance - _updateVisibleCellsNow
提问by Oskar
I'm working on a custom UICollectionViewLayout
that displays cells organized by day/week/month.
我正在处理一个自定义UICollectionViewLayout
显示按天/周/月组织的单元格。
It is not scrolling smooth, and it looks like the lag is caused by [UICollectionView _updateVisibleCellsNow]
being called on each rendering loop.
它不是平滑滚动,看起来滞后是由[UICollectionView _updateVisibleCellsNow]
在每个渲染循环中调用引起的。
Performance is OK for < 30 items, but at around 100 or more, its terribly slow. Is this a limitation of UICollectionView and custom layouts, or am I not giving the view enough information to perform correctly?
< 30 个项目的性能还可以,但在 100 个或更多项目时,它的速度非常慢。这是 UICollectionView 和自定义布局的限制,还是我没有为视图提供足够的信息来正确执行?
Source here: https://github.com/oskarhagberg/calendarcollection
来源:https: //github.com/oskarhagberg/calendarcollection
布局:https: //github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarWeekLayout.m
Data source and delegate: https://github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarView.m
数据源和委托:https: //github.com/oskarhagberg/calendarcollection/blob/master/CalendarHeatMap/OHCalendarView.m
Time Profile:
时间简介:
Update
更新
Maybe its futile? Some testing with a plain UICollectionViewController
with a UICollectionViewFlowLayout
that is given approximately the same amount of cells/screen results in a similar time profile.
也许它是徒劳的?一些使用具有大致相同数量的单元格/屏幕的平原UICollectionViewController
进行的测试会UICollectionViewFlowLayout
导致类似的时间分布。
I feel that it shouldbe able to handle ~100 simple opaque cells at a time without the jitter. Am I wrong?
我觉得它应该能够在没有抖动的情况下一次处理大约 100 个简单的不透明单元格。我错了吗?
回答by Altimac
Also don't forget to try to rasterize the layer of the cell:
也不要忘记尝试栅格化单元格的图层:
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
I had 10 fps without that, and boom 55fps with! I'm not really familiar with GPU and compositing model, so what does it do exactly is not entirely clear to me, but basically it flatten the rendering of all subviews in only one bitmap (instead of one bitmap per subview?). Anyway I don't know if it has some cons, but it is dramatically faster!
没有那个我有 10 fps,有 55fps !我对 GPU 和合成模型并不是很熟悉,所以它到底做了什么对我来说并不完全清楚,但基本上它只在一个位图(而不是每个子视图一个位图?)中展平所有子视图的渲染。无论如何,我不知道它是否有一些缺点,但它的速度要快得多!
回答by TheBasicMind
I have been doing considerable investigation of UICollectionView performance. The conclusion is simple. Performance for a large number of cells is poor.
我一直在对 UICollectionView 性能进行大量调查。结论很简单。大量单元的性能很差。
EDIT: Apologies, just re-read your post, the number of cells you have should be OK (see the rest of my comment), so cell complexity may also be a problem.
编辑:抱歉,请重新阅读您的帖子,您拥有的单元格数量应该没问题(请参阅我的其余评论),因此单元格复杂性也可能是一个问题。
If your design supports it check:
如果您的设计支持它,请检查:
Each cell is opaque.
Cell content clips to bounds.
Cell coordinate positions do not contain fractional values (e.g. always calculate to be whole pixels)
Try to avoid overlapping cells.
Try to avoid drop shadows.
每个单元格都是不透明的。
单元格内容剪辑到边界。
单元格坐标位置不包含小数值(例如始终计算为整个像素)
尽量避免重叠单元格。
尽量避免阴影。
The reason for this is actually quite simple. Many people don't understand this, but it is worth understanding: UIScrollViews do not employ Core Animation to scroll. My naive belief was that they involved some secret scrolling animation "sauce" and simply requested occasional updates from delegates every now and then to check status. But in fact scroll views are objects which don't directly apply any drawing behaviour at all. All they really are is a class which applies a mathematical function abstracting the coordinate placement of the UIViews they contain, so the Views coordinates are treated as relative to an abstract contentView plane rather than relative to the origin of the containing view. A scroll view will update the position of the abstract scrolling plane in accord with user input (e.g. swiping) and of course there is a physics algorithm as well which gives "momentumn" to the translated coordinate positions.
原因其实很简单。很多人不理解这一点,但值得理解的是:UIScrollViews 并没有使用 Core Animation 来滚动。我天真地认为他们涉及一些秘密的滚动动画“酱”,只是不时地要求代表偶尔更新以检查状态。但实际上滚动视图是根本不直接应用任何绘图行为的对象。它们真正是一个类,它应用数学函数抽象它们包含的 UIViews 的坐标位置,因此 Views 坐标被视为相对于抽象 contentView 平面而不是相对于包含视图的原点。滚动视图将根据用户输入更新抽象滚动平面的位置(例如
Now if you were to produce your own collection view layout object, in theory, you could produce one which 100% reverses the mathematical translation applied by the underlying scrollview. This would be interesting but useless, because it would then appear that the cells are not moving at all as you swipe. But I raise this possibility because it illustrates that the collection view layout object working with the collection view object itself does a very similar operation to the underlying scrollview. E.g. it simply provides an opportunity to apply an additional mathematical frame by frame translation of the attributes of the views to be displayed, and in the main this will be a translation simply of position attributes.
现在,如果您要生成自己的集合视图布局对象,理论上,您可以生成一个 100% 反转底层滚动视图应用的数学转换的对象。这会很有趣但没用,因为当您滑动时,细胞似乎根本没有移动。但是我提出了这种可能性,因为它说明了与集合视图对象一起工作的集合视图布局对象本身执行的操作与底层滚动视图非常相似。例如,它只是提供了一个机会,通过要显示的视图的属性的逐帧转换来应用附加数学框架,并且主要这将是位置属性的简单转换。
It is only when new cells are inserted or deleted moved or reloaded that CoreAnimation is involved at all; most usually by calling:
只有在插入或删除、移动或重新加载新单元格时,才涉及 CoreAnimation;通常通过调用:
- (void)performBatchUpdates:(void (^)(void))updates
completion:(void (^)(BOOL finished))completion
UICollectionView requests cell layoutAttributes for each frame of scrolling and each visible view is laid out for each frame. UIView's are rich objects optimised for flexibility more than performance. Every time one is laid out, there are a number of tests the system does to check it's alpha, zIndex, subViews, clipping attributes etc. The list is long. These checks and any resulting changes to the view are being conducted for each collection view item for each frame.
UICollectionView 为每一帧滚动请求单元格layoutAttributes,并且为每一帧布置每个可见视图。UIView 是针对灵活性而非性能进行了优化的丰富对象。每次布局时,系统都会进行许多测试以检查它的 alpha、zIndex、子视图、剪辑属性等。列表很长。正在对每个框架的每个集合视图项进行这些检查和对视图的任何由此产生的更改。
To ensure good performance all frame by frame operations need to be completed within 17ms. [With the number of cells you have, that is simply not going to happen] bracketed this clause because I have re-read your post and I realise I had misread it. With the number of cells you have, there should not be a performance problem. I have personally found with a simplified test with vanilla cells, containing only a single cached image, the limit tested on an iPad 3 is about 784 onscreen cells before performance starts to drop below 50fps.
为确保良好的性能,所有逐帧操作都需要在 17 毫秒内完成。[根据您拥有的单元格数量,这根本不会发生] 将这一条款括起来,因为我重新阅读了您的帖子,但我意识到我误读了它。对于您拥有的单元格数量,应该不会出现性能问题。我个人发现,使用普通单元进行简化测试,仅包含一个缓存图像,在性能开始下降到 50fps 以下之前,在 iPad 3 上测试的限制约为 784 个屏幕单元。
In practice you will need to keep it less than this.
在实践中,您将需要保持低于此值。
Personally I'm using my own custom layout object and need higher performance than UICollectionView provides. Unfortunately I didn't run the simple test above until some way down the development path and I realised there are performance problems. I'm so I'm going to be reworking the open source back-port of UICollectionView, PSTCollectionView. I think there is a workaround that can be implemented so, just for general scrolling about, each cell item's layer is written using an operation which circumvents the layout of each UIView cell. This will work for me since I have my own layout object, and I know when layout is required and I have a neat trick that will allow the PSTCollectionView to fall back to its normal mode of operation at this time. I've checked the call sequence and it doesn't appear to be too complex, nor does it appear at all unfeasible. But for sure it is non-trivial and some further tests have to be done before I can confirm it will work.
我个人使用我自己的自定义布局对象并且需要比 UICollectionView 提供的更高的性能。不幸的是,我没有运行上面的简单测试,直到在开发路径上走一段路,我才意识到存在性能问题。所以我要重新设计 UICollectionView、PSTCollectionView 的开源后端。我认为有一种可以实现的解决方法,仅用于一般滚动,每个单元格项的层是使用绕过每个 UIView 单元格布局的操作编写的。这对我有用,因为我有自己的布局对象,我知道什么时候需要布局,我有一个巧妙的技巧可以让 PSTCollectionView 在这个时候恢复到正常的操作模式。我已经检查了调用序列,它似乎并不太复杂,也不是完全不可行。但可以肯定的是,这并非微不足道,在我确认它会起作用之前,还必须进行一些进一步的测试。
回答by Andreas Karlsson
Some more observations that might be helpful:
还有一些可能有用的观察:
I am able to reproduce the problem, using flow layout with around 175 items visible at once: it scrolls smoothly in the simulator but lags like hell on iPhone 5. Made sure they are opaque etc.
我能够重现这个问题,使用同时可见大约 175 个项目的流布局:它在模拟器中平滑滚动,但在 iPhone 5 上像地狱一样滞后。确保它们不透明等。
What ends up taking the most time seems to be work with a mutable NSDictionary inside _updateVisibleCellsNow
. Both copying the dictionary, but also looking up items by key. The keys seems to be UICollectionViewItemKey and the [UICollectionViewItemKey isEqual:]
method is the most time consuming method of all. UICollectionViewItemKey contains at least type
, identifier
and indexPath
properties, and the contained property indexPath comparison [NSIndexPath isEqual:]
takes the most time.
最终花费最多时间的似乎是在里面使用了一个可变的 NSDictionary _updateVisibleCellsNow
。既可以复制字典,也可以按键查找项目。键似乎是 UICollectionViewItemKey 并且该[UICollectionViewItemKey isEqual:]
方法是所有方法中最耗时的方法。UICollectionViewItemKey 至少包含type
,identifier
和indexPath
属性,其中包含的属性 indexPath 比较[NSIndexPath isEqual:]
耗时最多。
From that I'm guessing that the hash function of UICollectionViewItemKey might be lacking since isEqual:
is called so often during dictionary lookup. Many of the items might be ending up with the same hash (or in the same hash bucket, not sure how NSDictionary works).
由此我猜测 UICollectionViewItemKey 的散列函数可能缺乏,因为isEqual:
在字典查找过程中经常被调用。许多项目可能以相同的哈希结束(或在同一个哈希桶中,不确定 NSDictionary 是如何工作的)。
For some reason it is faster with all items in 1 section, compared to many sections with 7 items in each. Probably because it spends so much time in NSIndexPath isEqual and that is faster if the row diffs first, or perhaps that UICollectionViewItemKey gets a better hash.
出于某种原因,与每个包含 7 个项目的许多部分相比,1 个部分中的所有项目更快。可能是因为它在 NSIndexPath isEqual 中花费了太多时间,并且如果行首先发生差异会更快,或者 UICollectionViewItemKey 获得更好的哈希值。
Honestly it feels really weird that UICollectionView does that heavy dictionary work every scroll frame, as mentioned before each frame update needs to be <16ms to avoid lag. I wonder if that many dictionary lookups either is:
老实说,UICollectionView 在每个滚动帧都执行繁重的字典工作,这感觉真的很奇怪,正如前面提到的,每个帧更新需要 <16 毫秒以避免延迟。我想知道很多字典查找是否是:
- Really necessary for general UICollectionView operation
- There to support some edge case rarely used and could be turned off for most layouts
- Some unoptimized internal code that hasn't been fixed yet
- Some mistake from our side
- 一般 UICollectionView 操作真的很有必要
- 那里支持一些很少使用的边缘情况,可以为大多数布局关闭
- 一些尚未修复的未优化内部代码
- 我们这边的一些错误
Hopefully we will see some improvement this summer during WWDC, or if someone else here can figure out how to fix it.
希望我们能在今年夏天的 WWDC 期间看到一些改进,或者如果这里的其他人可以弄清楚如何解决它。
回答by Gene Loparco
Here is Altimac's answer converted to Swift 3:
这是转换为 Swift 3 的 Altimac 的答案:
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.main.scale
Also, it should be noted that this code goes in your collectionView delegate method for cellForItemAtIndexPath.
此外,应该注意的是,此代码在 cellForItemAtIndexPath 的 collectionView 委托方法中。
One more tip - to see an app's frames per second (FPS), open up Core Animation under Instruments (see image).
另一个提示 - 要查看应用程序的每秒帧数 (FPS),请在 Instruments 下打开 Core Animation(见图)。
回答by NSSplendid
Big thumbs up to the two answers above!
Here's one additional thing you can try: I've had big improvements in UICollectionView
performance by disabling auto layout. While you will have to add some additional code to layout the cell interiors, custom code seems to be tremendously faster than auto layout.
大拇指以上两个答案!这是您可以尝试的另一件事:UICollectionView
通过禁用自动布局,我在性能上有了很大的改进。虽然您必须添加一些额外的代码来布局单元格内部,但自定义代码似乎比自动布局快得多。
回答by Ash Furrow
The issue isn't the number of cells you're displaying in the collection view total, it's the number of cells that are on screen at once. Since the cell size is very small (22x22), you have 154 cells visible on screen at once. Rendering each of these is what's slowing your interface down. You can prove this by increasing the cell size in your Storyboard and re-running the app.
问题不在于您在集合视图总数中显示的单元格数量,而在于屏幕上一次显示的单元格数量。由于单元格尺寸非常小 (22x22),因此您可以同时在屏幕上看到 154 个单元格。渲染每一个都会减慢你的界面速度。您可以通过增加 Storyboard 中的单元格大小并重新运行应用程序来证明这一点。
Unfortunately, there's not much you can do. I'd recommend mitigating the problem by avoiding clipping to bounds and trying not to implement drawRect:
, since it's slow.
不幸的是,您无能为力。我建议通过避免裁剪到边界并尽量不实现来缓解这个问题drawRect:
,因为它很慢。
回答by RainCast
Beside the listed answers (rasterize, auto-layout, etc.), you may also want to check for other reasons that potentially drags down the performance.
除了列出的答案(光栅化、自动布局等),您可能还想检查可能会降低性能的其他原因。
In my case, each of my UICollectionViewCell contains another UICollectionView (with about 25 cells each), when each of the cell is loading, I call the inner UICollectionView.reloadData(), which significantly drags down the performance.
就我而言,我的每个 UICollectionViewCell 都包含另一个 UICollectionView(每个大约有 25 个单元格),当每个单元格加载时,我调用内部 UICollectionView.reloadData(),这会显着降低性能。
Then I put the reloadData inside the main UI queue, the issue is gone:
然后我把 reloadData 放在主 UI 队列中,问题就消失了:
DispatchQueue.main.async {
self.innerCollectionView.reloadData()
}
Carefully looking into reasons like these might help as well. Thanks! :)
仔细研究这些原因也可能有所帮助。谢谢!:)
回答by cirronimbo
In few cases it is due to Auto-layout in UICollectionViewCell. Turn it off (if you can live without it) and scrolling will become butter smooth :) It's an iOS issue, which they havnt resolved from ages.
在少数情况下,这是由于 UICollectionViewCell 中的自动布局。把它关掉(如果没有它你也能活下去),滚动就会变得非常顺畅:) 这是一个 iOS 问题,他们已经很久没有解决了。
回答by CWitty
If you are implementing a grid layout you can work around this by using a single UICollectionViewCell
for each row
and add nested UIView
's to the cell
. I actually subclassed UICollectionViewCell
and UICollectionReusableView
and overrode the prepareForReuse
method to remove all of the subviews. In collectionView:cellForItemAtIndexPath:
I add in all of the subviews
that originally were cells setting their frame to the x
coordinate used in the original implementation, but adjusting it's y
coordinate to be inside the cell
. Using this method I was able to still use some of the niceties of the UICollectionView
such as targetContentOffsetForProposedContentOffset:withScrollingVelocity:
to align nicely on the top and left sides of a cell. I went from getting 4-6 FPSto a smooth 60 FPS.
如果您正在实施网格布局,您可以通过UICollectionViewCell
为每个使用单个row
并将嵌套UIView
的添加到cell
. 我实际上子类化UICollectionViewCell
并UICollectionReusableView
覆盖了prepareForReuse
删除所有子视图的方法。在collectionView:cellForItemAtIndexPath:
我添加所有subviews
原来是单元格的设置它们的框架到x
原始实现中使用的坐标,但调整它的y
坐标到cell
. 使用这种方法,我仍然能够使用UICollectionView
诸如targetContentOffsetForProposedContentOffset:withScrollingVelocity:
在单元格的顶部和左侧很好地对齐的一些细节。我从获得4-6 FPS到平稳的60 FPS。
回答by iFoul
Just like @Altimac, I also used this code to solve a problem with iPad scrolling on UICollectionView :
就像@Altimac 一样,我也使用此代码解决了 iPad 在 UICollectionView 上滚动的问题:
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
I also had 6-10fps, and I now have 57fps
我也有 6-10fps,现在我有 57fps