我目前的任务是iOS键盘扩展程序,其中包括所有iOS支持的表情符号(是的,我知道iOS有一个内置表情符号键盘,但目标是在键盘扩展程序中包含一个)。
对于这个基本上应该是滚动视图的表情符号布局,其中所有表情符号都按网格顺序排列,我决定使用UICollectionView,因为它只创建有限数量的单元格并重用它们。 (有很多表情符号,超过1� 000。)这些单元格只包含一个UILabel,它将表情符号作为文本,并使用GestureRecognizer插入抽头表情符号。
然而,当我滚动列表时,我可以看到内存使用量在16-18MB到33MB左右爆炸。虽然这并没有在我的iPhone 5s上触发内存警告,但它可能在其他设备上也是如此,因为应用程序扩展仅专用于非常少量的资源。
编辑:有时候我会收到内存警告,主要是在切换回“正常”状态时。键盘布局。大多数情况下,切换回来时内存使用量会降至20MB以下,但并非总是如此。
如何减少此表情符号布局使用的内存量?
class EmojiView: UICollectionViewCell {
//...
override init(frame: CGRect) {
super.init(frame: frame)
self.userInteractionEnabled = true
let l = UILabel(frame: self.contentView.frame)
l.textAlignment = .Center
self.contentView.addSubview(l)
let tapper = UITapGestureRecognizer(target: self, action: "tap:")
self.addGestureRecognizer(tapper)
}
override func prepareForReuse() {
super.prepareForReuse()
//We know that there only is one subview of type UILabel
(self.contentView.subviews[0] as! UILabel).text = nil
}
}
//...
class EmojiViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
//...
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
//The reuse id "emojiCell" is registered in the view's init.
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("emojiCell", forIndexPath: indexPath)
//Get recently used emojis
if indexPath.section == 0 {
(cell.contentView.subviews[0] as! UILabel).text = recent.keys[recent.startIndex.advancedBy(indexPath.item)]
//Get emoji from full, hardcoded list
} else if indexPath.section == 1 {
(cell.contentView.subviews[0] as! UILabel).text = emojiList[indexPath.item]
}
return cell
}
//Two sections: recently used and complete list
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 2
}
}
let emojiList: [String] = [
"\u{1F600}",
"\u{1F601}",
"\u{1F602}",
//...
// I can't loop over a range, there are
// unused values and gaps in between.
]
如果您需要更多代码和/或信息,请与我们联系。
编辑:我的猜测是iOS将渲染的表情符号保留在内存中,尽管在重用之前将文本设置为nil
。但我可能完全错了......
编辑:正如JasonNam所说,我使用Xcode的Leaks工具运行键盘。在那里我注意到两件事:
VM: CoreAnimation
上升到大约6-7MB,但我想在滚动浏览集合视图时这可能是正常的。Malloc 16.00KB
,从千字节的值开始,在滚动整个列表时最多可拍摄17MB,因此分配了大量内存,但我实际上看不到任何其他内容使用它。但没有泄漏报告。
EDIT2 :我刚刚使用CFGetRetainCount
(在使用ARC时仍然有效)检查了prepareForReuse
中的nil值后,String对象没有任何引用集。
我在使用iOS 9.2的iPhone 5s上进行测试,但问题也出现在使用iPhone 6s Plus的模拟器中。
EDIT3 :有人遇到完全相同的问题here,但由于名称很奇怪,到目前为止我还没有找到它。似乎唯一的解决方案是在列表中使用UIImageViews和UIImages,因为UICollectionView中的UIImages在单元重用上正确发布。
答案 0 :(得分:6)
非常有趣,在我的测试项目中,我在EmojiView中注释了prepareForReuse部分,内存使用情况变得稳定,项目开始时为19MB,从未超过21MB,(self.contentView.subviews [0] as !UILabel)。text = nil导致我的测试中的问题。
答案 1 :(得分:2)
我认为您不使用故事板来设计集合视图。我搜索了一下,发现在填充集合视图单元格之前需要注册带有标识符的类。尝试在viewDidLoad或其他东西上调用以下方法。
collectionView.registerClass(UICollectionViewCell.self , forCellWithReuseIdentifier: "emojiCell")
答案 2 :(得分:1)
由于您有内存问题,因此应尝试延迟加载标签。
// Define an emojiLabel property in EmojiView.h
var emojiLabel: UILabel!
// Lazy load your views in your EmojiView.m
lazy var emojiLabel: UILabel = {
var tempLabel: UIImageView = UILabel(frame: self.contentView.frame)
tempLabel.textAlignment = .Center
tempLabel.userInteractionEnabled = true
contentView.addSubview(tempLabel)
return tempLabel;
}()
override func prepareForReuse() {
super.prepareForReuse()
emojiLabel.removeFromSuperview()
emojiLabel = nil
}
//...
class EmojiViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
//...
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
//The reuse id "emojiCell" is registered in the view's init.
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("emojiCell", forIndexPath: indexPath) as! EmojiView
//Get recently used emojis
if indexPath.section == 0 {
cell.emojiLabel.text = recent.keys[recent.startIndex.advancedBy(indexPath.item)]
//Get emoji from full, hardcoded list
} else if indexPath.section == 1 {
cell.emojiLabel.text = emojiList[indexPath.item]
}
return cell
}
这样你就可以确定在滚动时会释放标签。
现在我有一个问题。为什么要在EmojiViews中添加手势识别器? UICollectionView已经使用didSelectItemAtIndexPath:delegate实现了此功能。为每个加载的单元格分配额外的gestureRecognizer非常重。
func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){
let cell : UICollectionViewCell = collectionView.cellForItemAtIndexPath(indexPath) as! EmojiView
// Do stuff here
}
总而言之,我建议在EmojiViews.m中删除整个init函数,对标签使用延迟加载,并为选择事件使用didSelectItemAtIndexPath:delegate。
注意:我不习惯快速,所以我的代码可能包含一些错误。