Подтвердить что ты не робот

Как очистить кеш шрифтов, заполненный символами emoji?

Я разрабатываю расширение клавиатуры для iPhone. Существует экран emoji, похожий на Apple, на клавиатуру emoji, на которой показано около 800 символов emoji в UICollectionView.

При прокрутке этого emoji UIScrollView использование памяти увеличивается и не падает. Я правильно использую ячейки, и при тестировании с одним символом emoji, отображаемым в 800 раз, во время прокрутки память не увеличивается.

Используя инструменты, я обнаружил, что в моем коде отсутствует утечка памяти, но похоже, что глифы emoji кэшируются и могут занимать около 10-30 МБ памяти в зависимости от размера шрифта (повторное шоу показывает, что они являются фактически 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 я получаю график использования памяти следующим образом: enter image description here

4b9b3361

Ответ 1

Я столкнулся с той же проблемой и исправил ее, сбросив .png из /System/Library/Fonts/Apple Color Emoji.ttf и используя 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: путь!) правильно выпущен, поэтому память должна оставаться на низком уровне. Пока расширение клавиатуры еще не разбилось.

Если UIScrollView содержит много emoji, подумайте об использовании UICollectionView, который сохраняет только 3 или 4 страницы в кеше и освобождает другие невидимые страницы.

Ответ 2

У меня была такая же проблема, и я попытался много чего выпустить, но не повезло. Я просто изменил код, основанный на предположении Мэтью. Он работает, и у меня больше нет проблем с памятью, включая iPhone 6 Plus.

Изменение кода минимально. Найдите изменения в подклассе UILabel ниже. Если вы спросите меня, задача состоит в том, чтобы получить изображения emoji. Я не мог понять, как работает 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. Решение по данной ссылке работает без проблем до сих пор. Преобразовать emoji в шестнадцатеричное значение с помощью 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 не работает для каждого эмози. Таким образом, я в конечном итоге загружаю все emojis с помощью gemoji и сопоставляю каждый файл образа emoji (имена файлов похожи на 1.png, 2.png и т.д.) С самим emoji в объекте словаря. Теперь используйте следующий метод.

func getImageFileNo(s: String) -> Int
{
        if Array(emo.keys).contains(s)
        {
             return emo[s]!
        }   
        return -1
}

Ответ 3

Я предполагаю, что вы загружаете изображения, используя [UIImage imageNamed:], или что-то, что происходит от него. Это будет кэшировать изображения в системном кеше.

Вам нужно загрузить их с помощью [UIImage imageWithContentsOfFile:]. Это будет обходить кеш.

(И если это не проблема, тогда вам нужно будет включить код в свой вопрос, чтобы мы могли видеть, что происходит.)

Ответ 4

Многие emojis представлены последовательностями, которые содержат более одного сканера unicode. Ответ Matthew хорошо работает с базовым emojis, но он возвращает только первый скаляр из последовательностей emojis, таких как флаги страны.

В приведенном ниже коде будут отображаться полные последовательности и создать строку, которая соответствует файлам gemoji.

Некоторые простые смайлики emojis также имеют селектор 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
}

Ответ 5

Я тоже ходил по домам и после многочисленных испытаний пришел к следующему выводу:

Хотя кэш шрифтов вносит свой вклад в объем памяти вашего расширения и общее использование в Xcode Debug Navigator и Memory Report, он не обрабатывается так же, как остальная часть вашего бюджета.

Некоторые люди ссылаются на ограничение в 50 МБ, а в документах Apple я упоминал 30 или 32 МБ. Мы видим предупреждения памяти в различных точках между 30 и 40 МБ, и это слишком непоследовательно, чтобы быть довольным каким-либо конкретным значением, но одна вещь, которая кажется конкретной, - это исключение памяти, которое возникает при 53 МБ, которое выходит из системы Xcode как именно этот номер. Если я возьму пустое расширение клавиатуры и заполню его даже 40 МБ изображений, это одно, но если я использую 30 МБ, а затем 20 МБ использования шрифта, я обнаружу, что моя клавиатура не выключена.

По моим наблюдениям, кэш шрифтов выглядит очищенным, но не так часто, как вам может показаться необходимым (особенно, если вы нервничаете, когда это бесполезное объединенное значение памяти превышает 30 или 32 МБ).

Если вы планируете использовать собственную память, скажем, в 30 МБ, вы должны быть в безопасности, при условии, что вы одновременно не вводите необходимость в 23,5 МБ глифов шрифтов. При этом понимается, что если вы прокрутите от одного конца представления коллекции смайликов до другого, вы пройдете более 23 МБ глифов шрифтов, но если оставшаяся часть памяти будет разумной (т.е. 30 МБ) или ниже), кэш шрифтов должен получить шанс на очистку.

В своем тестировании я попытался автоматизировать бомбардировку расширения с помощью гораздо большего количества шрифтов, и я думаю, что смог справиться с процессом очистки кэша шрифтов, что привело к сбою.

Поэтому, учитывая сценарий использования UICollectionView и скорость его прокрутки, может произойти сбой приложения, если вы действительно выдвинули бюджет памяти 30 МБ, а также прокрутили его очень быстро. Вы можете позволить себе преодолеть это жесткое ограничение в 53 МБ.

Учитывая все вышеперечисленное - с полноценным расширением клавиатуры, пока у меня остается около 30 МБ моего собственного (не шрифтового) места, я не столкнулся с сбоем, даже при быстром изменении категорий смайликов и быстрой прокрутке, Я, однако, сталкиваюсь с предупреждениями системной памяти таким образом, что вызывает у меня сомнения.