Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to clear font cache filled with emoji characters?

Tags:

I am developing keyboard extension for iPhone. There is an emoji screen smilar to Apples own emoji keyboard that shows some 800 emoji characters in UICollectionView.

When this emoji UIScrollView is scrolled the memory usage increases and does not drop down. I am reusing cells correctly and when testing with single emoji character displayed 800 times the memory does not increase during scrolling.

Using instruments I found that there is no memory leak in my code but it seems that the emoji glyphs are cached and can take around 10-30MB of memory depending on font size (reseach shows they are actually PNGs). Keyboard extensions can use little memory before they are killed. Is there a way to clear that font cache?


Edit

Adding code example to reproduce the problem:

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()
}

After running and scrolling the UICollectionView I get memory usage graph like this: enter image description here

like image 296
Rasto Avatar asked Jul 01 '15 01:07

Rasto


3 Answers

I ran into the same issue and fixed it by dumping the .png from /System/Library/Fonts/Apple Color Emoji.ttf and using UIImage(contentsOfFile: String) instead of a String.

I used https://github.com/github/gemoji to extract the .png files, renamed the files with @3x suffix.

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!) is properly released so the memory should stay at a low level. So far my keyboard extension hasn't crashed yet.

If the UIScrollView contains a lot of emoji, consider using UICollectionView that retains only 3 or 4 pages in cache and releases the other unseen pages.

like image 69
Matthew Avatar answered Oct 26 '22 00:10

Matthew


I had the same issue and tried many things to release the memory, but no luck. I just changed the code based on Matthew's suggestion. It works, no more memory problem for me including iPhone 6 Plus.

The code change is minimal. Find the change in the UILabel subclass below. If you ask me the challenge is to get the emoji images. I could not figure how gemoji (https://github.com/github/gemoji) works out yet.

    //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)
    }

The emojiToHex() method Matthew provides returns strange values starting with "/" for some emojis. The solution at the given link work with no problems so far. 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)
}

---------- AFTER SOME TIME----

It turned out this emojiToHex method does not work for every emoji. So I end up downloading all emojis by gemoji and map each and every emoji image file (file names are like 1.png, 2.png, etc) with the emoji itself in a dictionary object. Using the following method instead now.

func getImageFileNo(s: String) -> Int
{
        if Array(emo.keys).contains(s)
        {
             return emo[s]!
        }   
        return -1
}
like image 42
Ahmet Akkรถk Avatar answered Oct 25 '22 22:10

Ahmet Akkรถk


I am guessing that you are loading the images using [UIImage imageNamed:], or something that derives from it. That will cache the images in the system cache.

You need to load them using [UIImage imageWithContentsOfFile:] instead. That will bypass the cache.

(And if that's not the problem, then you'll need to include some code in your question so that we can see what's happening.)

like image 45
Ewan Mellor Avatar answered Oct 26 '22 00:10

Ewan Mellor