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

Использование большой памяти в UICollectionView

Мое текущее назначение - расширение клавиатуры iOS, которое, среди прочего, предлагает все поддерживаемые iOS Emoji (да, я знаю, что iOS имеет встроенную клавиатуру Emoji, но цель состоит в том, чтобы включить ее в расширение клавиатуры).

Для этого макета Emoji, который в основном должен быть просмотром прокрутки со всеми emojis в нем в порядке сетки, я решил использовать UICollectionView, поскольку он создает только ограниченное количество ячеек и повторно использует их. (Есть довольно много emojis, более 1 000.) Эти ячейки просто содержат UILabel, который содержит emoji в качестве своего текста, с GestureRecognizer, чтобы вставить вытащенный Emoji.

Однако, когда я просматриваю список, я вижу, как использование памяти взрывается где-то около 16-18 МБ до более 33 МБ. Хотя это еще не вызывает предупреждение о памяти на моем iPhone 5, оно может также появляться на других устройствах, поскольку расширения приложений предназначены только для очень редкого количества ресурсов.

EDIT. Иногда я получаю предупреждение о памяти, в основном при переключении на "нормальную" раскладку клавиатуры. В большинстве случаев использование памяти уменьшается при 20 Мбайт при переключении, но не всегда.

Как уменьшить объем памяти, используемый этим макетом Emoji?


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 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 сохраняет отображаемый emojis где-то в памяти, несмотря на то, что перед повторным использованием текст был nil. Но я могу быть совершенно неправым...

РЕДАКТИРОВАТЬ. Как предложил JasonNam, я запускал клавиатуру с помощью инструмента Xcode Leaks. Там я заметил две вещи:

  • VM: CoreAnimation при прокрутке увеличивается примерно до 6-7 МБ, но я думаю, это может быть нормально при прокрутке списка коллекций.
  • Malloc 16.00KB, начиная с значения в килобайтах, при прокрутке по всему списку увеличивается до 17 МБ, поэтому выделяется много памяти, но я не вижу ничего на самом деле используя.

Но никаких утечек не сообщалось.

EDIT2: я только что проверил с помощью CFGetRetainCount (который все еще работает при использовании ARC), что объекты String не имеют ссылок, оставшихся после установки значения nil в prepareForReuse.

Я тестирую iPhone 5 с iOS 9.2, но проблема также возникает в симуляторе с использованием iPhone 6s Plus.

EDIT3: у кого-то была такая же проблема здесь, но из-за странного названия я не нашел его до сих пор, Кажется, единственным решением является использование UIImageViews с UIImages в списке, так как UIImages в UICollectionView правильно выпущены при повторном использовании ячеек.

4b9b3361

Ответ 1

Это довольно интересно, в моем тестовом проекте я прокомментировал часть prepareForReuse в EmojiView, и использование памяти стало устойчивым, проект начался с 19 МБ и никогда не превышал 21 МБ, (self.contentView.subviews [0] as! UILabel).text = nil вызывает проблемы в моем тесте.

Ответ 2

Я думаю, что вы не используете раскадровку для создания коллекционного представления. Я обыскал и обнаружил, что вам нужно зарегистрировать класс с идентификатором, прежде чем вы заполняете ячейку просмотра коллекции. Попробуйте вызвать следующий метод в viewDidLoad или что-то в этом роде.

collectionView.registerClass(UICollectionViewCell.self , forCellWithReuseIdentifier: "emojiCell")

Ответ 3

Поскольку у вас есть проблемы с памятью, вы должны попробовать ленивую загрузку своих ярлыков.

// 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 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. Выделение дополнительных жестовRecognizers для каждой загруженной ячейки довольно тяжело.

func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath){

    let cell : UICollectionViewCell = collectionView.cellForItemAtIndexPath(indexPath) as! EmojiView
    // Do stuff here
}

Подводя итог, я бы рекомендовал избавиться от всей вашей функции init в EmojiViews.m, использовать ленивую загрузку для меток и didSelectItemAtIndexPath: делегировать для событий выбора.

NB: я не привык к быстрому, поэтому мой код может содержать несколько ошибок.