我正在为iPhone开发键盘扩展程序。苹果自己的表情符号键盘有一个表情符号屏幕,在UICollectionView
中显示了大约800个表情符号字符。
当滚动此表情符号UIScrollView
时,内存使用量会增加而不会下降。我正在重复使用单元格,当使用单个表情符号字符进行测试时,显示800次内存在滚动期间不会增加。
使用乐器我发现我的代码中没有内存泄漏,但似乎表情符号字形被缓存,并且根据字体大小可以占用大约10-30MB的内存(研究显示它们实际上是PNG)。键盘扩展可以在被杀之前使用很少的内存。有没有办法清除字体缓存?
添加代码示例以重现问题:
let data = Array("☺️✨✊✌️✋☝️⭐️☀️⛅️☁️⚡️☔️❄️⛄️☕️❤️️⚽️⚾️⛳️").map {String($0)}
class CollectionViewTestController: UICollectionViewController {
override func viewDidLoad() {
collectionView?.registerClass(Cell.self, forCellWithReuseIdentifier: cellId)
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellId, forIndexPath: indexPath) as! Cell
if cell.label.superview == nil {
cell.label.frame = cell.contentView.bounds
cell.contentView.addSubview(cell.label)
cell.label.font = UIFont.systemFontOfSize(34)
}
cell.label.text = data[indexPath.item]
return cell
}
override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
}
class Cell: UICollectionViewCell {
private let label = UILabel()
}
运行并滚动UICollectionView
后,我得到了内存使用情况图:
答案 0 :(得分:10)
我遇到了同样的问题并通过从/ System / Library / Fonts / Apple Color Emoji.ttf转储.png并使用UIImage(contentsOfFile:String)而不是String来修复它。
我使用https://github.com/github/gemoji提取.png文件,使用@ 3x后缀重命名文件。
func emojiToHex(emoji: String) -> String {
let data = emoji.dataUsingEncoding(NSUTF32LittleEndianStringEncoding)
var unicode: UInt32 = 0
data!.getBytes(&unicode, length:sizeof(UInt32))
return NSString(format: "%x", unicode) as! String
}
let path = NSBundle.mainBundle().pathForResource(emojiToHex(char) + "@3x", ofType: "png")
UIImage(contentsOfFile: path!)
UIImage(contentsOfFile:path!)已正确释放,因此内存应保持在较低水平。到目前为止,我的键盘扩展程序还没有崩溃。
如果UIScrollView包含大量表情符号,请考虑使用UICollectionView,它只在缓存中保留3或4页,并释放其他看不见的页面。
答案 1 :(得分:3)
我有同样的问题并尝试了很多东西来释放记忆,但没有运气。我刚刚根据Matthew的建议更改了代码。它有效,对我来说没有更多的内存问题,包括iPhone 6 Plus。
代码更改很少。在下面的UILabel子类中找到更改。如果你问我挑战是获得表情符号图像。我无法想象gemoji(https://github.com/github/gemoji)是如何工作的。
//self.text = title //what it used to be
let hex = emojiToHex(title) // this is not the one Matthew provides. That one return strange values starting with "/" for some emojis.
let bundlePath = NSBundle.mainBundle().pathForResource(hex, ofType: "png")
// if you don't happened to have the image
if bundlePath == nil
{
self.text = title
return
}
// if you do have the image
else
{
var image = UIImage(contentsOfFile: bundlePath!)
//(In my case source images 64 x 64 px) showing it with scale 2 is pretty much same as showing the emoji with font size 32.
var cgImage = image!.CGImage
image = UIImage( CGImage : cgImage, scale : 2, orientation: UIImageOrientation.Up )!
let imageV = UIImageView(image : image)
//center
let x = (self.bounds.width - imageV.bounds.width) / 2
let y = (self.bounds.height - imageV.bounds.height) / 2
imageV.frame = CGRectMake( x, y, imageV.bounds.width, imageV.bounds.height)
self.addSubview(imageV)
}
emojiToHex()方法Matthew为某些emojis提供了以“/”开头的奇怪值。到目前为止,给定链接的解决方案没有问题。 Convert emoji to hex value using Swift
func emojiToHex(emoji: String) -> String
{
let uni = emoji.unicodeScalars // Unicode scalar values of the string
let unicode = uni[uni.startIndex].value // First element as an UInt32
return String(unicode, radix: 16, uppercase: true)
}
----------有些时候------
事实证明,这种emojiToHex方法并不适用于每个表情符号。因此,我最终通过gemoji下载所有表情符号,并将每个表情符号图像文件(文件名如1.png,2.png等)与表情符号本身映射到字典对象中。现在改用以下方法。
func getImageFileNo(s: String) -> Int
{
if Array(emo.keys).contains(s)
{
return emo[s]!
}
return -1
}
答案 2 :(得分:1)
我猜你正在使用[UIImage imageNamed:]
加载图像,或者是从它派生的东西。这会将图像缓存在系统缓存中。
您需要使用[UIImage imageWithContentsOfFile:]
加载它们。这将绕过缓存。
(如果这不是问题所在,那么您需要在问题中加入一些代码,以便我们可以看到发生了什么。)
答案 3 :(得分:0)
许多表情符号由sequences表示,其中包含多个unicode标量。 Matthew's answer适用于基本的表情符号,但它只返回表情符号序列中的第一个标量,如国旗。
下面的代码将获得完整的序列,并创建一个匹配gemoji导出文件名的字符串。
一些简单的笑脸表情符号也有fe0f
选择器。但gemoji在导出时不会将此选择器添加到文件名中,因此应将其删除。
func emojiToHex(_ emoji: String) -> String
{
var name = ""
for item in emoji.unicodeScalars {
name += String(item.value, radix: 16, uppercase: false)
if item != emoji.unicodeScalars.last {
name += "-"
}
}
name = name.replacingOccurrences(of: "-fe0f", with: "")
return name
}
答案 4 :(得分:0)
我也在这附近待过,经过大量测试,我得出以下结论:
虽然字体高速缓存确实会占用扩展程序的内存空间,并在Xcode Debug Navigator和Memory Report中占用总使用量,但与其余预算的处理方式却不尽相同。
有人引用50 MB作为扩展限制,在Apple文档中,我认为我看到的是30 MB或32 MB。我们在30到40 MB之间的各个位置看到内存警告,这太不一致了,无法满足于任何特定的值,但是似乎很具体的一件事是发生在53 MB的内存异常,该内存异常被注销Xcode就是那个数字。如果我使用空白的键盘扩展名并在其中填充了40 MB的图像视图,那是一回事,但是如果我使用30 MB然后使用20 MB的字形,则发现我的键盘没有关闭。 / p>
根据我的观察,字体缓存看起来似乎已经清理干净了,但并没有达到您认为必要的频率(特别是如果无用的组合内存值超过30或32 MB时您变得紧张)。
如果您将自己的内存使用预算为例如30 MB,那么您应该是安全的,前提是您不引入一种情况,即一次需要全部23 MB(即53-30)的字形。这将受到表情符号网格的密集程度甚至所用字体大小的影响。在这里,通常的理解是,如果您要从表情符号集合视图的一端滚动到另一端,您将经过23 MB以上的字体字形,但是如果其余的内存占用是合理的(即30 MB)或以下),字体缓存应该有机会清理。
在测试中,我尝试使用更多的字形来自动执行对扩展程序的轰炸,我认为我能够击败字体缓存清理过程,从而导致崩溃。
因此,考虑到UICollectionView的用例及其滚动速度,如果您确实推动了30 MB的内存预算并且滚动速度非常快,则有可能使应用程序崩溃。您可能会允许自己达到这个53 MB的硬限制。
鉴于以上所有内容-具有完整的键盘扩展名,只要我保持自己的大约30 MB(非字体字形)占用空间,即使快速更改表情符号类别,我也不会崩溃并快速滚动。 但是,我确实会以这种方式遇到系统内存警告,这使我重新产生了怀疑。
与使用UIImage(contentsOfFile)
相比,此方法的另一个问题是,除了字体缓存正在执行的操作之外,很难使用内存报告的整体内存占用量来检查您的应用程序。也许有一种方法可以将它们分开,但是我不知道。
答案 5 :(得分:-1)
就我而言,简单的CATextLayer
有助于减少应用程序的内存使用量。当我使用UILabel
渲染表情符号时,键盘扩展内存从〜16MB增加到〜76MB。将UILabel
替换为CATextLayer
后,键盘扩展内存从〜16MB增加到仅〜26MB。
先前的UICollectionViewCell
子类设置:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
// somewhere in the header file
// @property (nonatomic, strong, readonly) UILabel *textLabel;
_textLabel = [UILabel new];
self.textLabel.font = [UIFont fontWithName:@"HelveticaNeue" size:28];
self.textLabel.textAlignment = NSTextAlignmentCenter;
[self addSubview:self.textLabel];
// some auto layout logic using Masonry
[self.textLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.size.equalTo(self);
make.center.equalTo(self);
}];
return self;
}
我的UICollectionViewCell
子类设置为CATextLayer
:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
// somewhere in the header file
// @property (nonatomic, strong, readonly) CATextLayer *textLayer;
_textLayer = [CATextLayer new];
self.textLayer.frame = CGRectMake(0, 0, 33, 33);
self.textLayer.font = CFBridgingRetain([UIFont fontWithName:@"HelveticaNeue" size:28].fontName);
self.textLayer.fontSize = 28;
self.textLayer.alignmentMode = kCAAlignmentCenter;
[self.layer addSublayer:self.textLayer];
return self;
}
更新
对不起,他们忘记添加self.textLayer.contentsScale = [[UIScreen mainScreen] scale];
来获取纯文本。不幸的是,这使内存使用量从约16MB增加到了约44MB,但仍然比UILabel
解决方案更好。
使用UICollectionViewCell
设置最终的CATextLayer
子类:
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
[self.layer setRasterizationScale:[[UIScreen mainScreen] scale]];
// somewhere in the header file
// @property (nonatomic, strong, readonly) CATextLayer *textLayer;
_textLayer = [CATextLayer new];
self.textLayer.frame = CGRectMake(0, 0, 33, 33);
self.textLayer.font = CFBridgingRetain([UIFont fontWithName:@"HelveticaNeue" size:28].fontName);
self.textLayer.fontSize = 28;
self.textLayer.alignmentMode = kCAAlignmentCenter;
NSDictionary *newActions = @{
@"onOrderIn": [NSNull null],
@"onOrderOut": [NSNull null],
@"sublayers": [NSNull null],
@"contents": [NSNull null],
@"bounds": [NSNull null]
};
self.textLayer.actions = newActions;
[self.layer addSublayer:self.textLayer];
[self.layer setShouldRasterize:YES];
return self;
}