Kingfisher кэширует данные в оперативной памяти

Лента новостей мне нравится, у меня возникла следующая проблема, если например пользователь загрузит более 300 новостей, то приложение уже будет занимать более 300 мегабайт памяти. Один раз во время теста у меня все же получилось didReceiveMemoryWarning и помогло только полное очищение dataSource. Я также использую Kingfisher для кэширования изображений. Каков наилучший выход из этой ситуации? Кэшировать первые данные и если пользователь вернется в начало (к самым новым данным), то загрузить их из кеша или как-то лучше? Спасибо.

Обновление: это новостная модель JSON.

["peopleProperties": ["numberOfPeopleDescription": "Здесь никого нет", "numberOfPeople": 0, "availableSeats": 0], "isPrivateStatus": false, "additionalInfo": ["примечание": ""], "ownerID": "", "ticketsInfo": ["tickets": []], "isTest": false, "isNewPendingRequest": false, "dateProperties": ["isEditable": true, "iso8601": "", "день": "", "endTimeStamp": 0.0, "isFlexDate": правда, "isFlexTime": правда, "timeStamp": 0.0], "boolProperties": ["isPartnerGeneratedCard": ложь, "isAutoGeneratedCard": правда, " isUserCreatedCard": false, "isAdminCreatedCard": false], "location": ["formattedAddress": "692 N Robertson Blvd (на бульваре Санта-Моника), Западный Голливуд, Калифорния 90069, США", "fullLocationName": "692 N Robertson Blvd", "координата": ["долгота": -118.38528500025966, "широта": 34.083373986214625]], "id": "", "photoURLsProperties": ["placePhotoURLs": ["пример"], "placeLogoURLs": []], "services": ["serviceURL": "", "serviceID": "41cf5080f964a520a61e1fe3", "index": 1], "version": 1, "title": "The A bbey Food & Bar", "ownerName": "", "phones": [:]]

ОБНОВЛЕНИЕ 1. Иногда дело доходит до сбоя приложения. Мой тестовый контроллер

import UIKit
import SVProgressHUD
class CardTestTableViewController: UITableViewController {

    // MARK: - Managers

    fileprivate let firCardDatabaseManager = FIRCardDatabaseManager()
    fileprivate let apiManager = ableCardsAPIManager()

    // MARK: - API Manager's properties

    fileprivate var firstCardsCount = 0
    fileprivate var isSecondTypeRequestLaunch = false

    /// Main cards array
    fileprivate var cardsModels = [CardModel]()
    fileprivate var firCardsModels = [CardModel]()
    fileprivate var backendCardsModels = [CardModel]()

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .white
        definesPresentationContext = true
        requestAllData()

        // table view
        registerCells()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        navigationController?.setNavigationBarHidden(false, animated: false)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        debugPrint("didReceiveMemoryWarning")
        cardsModels.removeAll()
        tableView.reloadData()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if cardsModels.count > 0 {
            debugPrint("cardsModels.first!.toJSON()", cardsModels.first!.toJSON(), "cardsModels.first!.toJSON()")

        }
        return cardsModels.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = cardTestTableViewCell(tableView, indexPath: indexPath)

        let lastElement = cardsModels.count - 15
        if indexPath.row == lastElement {
            secondRequest(indexPath.row)
        }

        return cell
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        return 298
    }


}

extension CardTestTableViewController {

    fileprivate func registerCells() {
        let nib = UINib(nibName: CardTestTableViewCell.defaultReuseIdentifier, bundle: Bundle.main)
        tableView.register(nib, forCellReuseIdentifier: CardTestTableViewCell.defaultReuseIdentifier)
    }


}

// MARK: - Cells 

extension CardTestTableViewController {

    fileprivate func cardTestTableViewCell(_ tableView: UITableView, indexPath: IndexPath) -> CardTestTableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: CardTestTableViewCell.defaultReuseIdentifier, for: indexPath) as! CardTestTableViewCell

        let card = cardsModels[indexPath.row]

        cell.setupData(card)

        return cell
    }

}

// MARK: - Requests

extension CardTestTableViewController {

    @objc fileprivate func requestAllData() {
        let requestGroup = DispatchGroup()
        if let topViewController = UIApplication.topViewController() {
            if topViewController.isKind(of: CardViewController.self) {
                SVProgressHUD.show()
            }
        }
        firCardsModels.removeAll()
        backendCardsModels.removeAll()

        requestGroup.enter()
        firCardDatabaseManager.getCardModelsByUserLocation(success: { [weak self] (userCardsModels) in
            debugPrint("Finish +++ fir", userCardsModels.count)
            self?.firCardsModels = userCardsModels
            requestGroup.leave()
        }) { (error) in
            // TODO: - Think about it: Do not show an error, because we have cards with FourSquare
            debugPrint("FIRCardDatabaseManager error", error.localizedDescription)
            requestGroup.leave()
        }

        requestGroup.enter()
        apiManager.requestCards(10, secondRequestLimit: 50, isFirstRequest: true, success: { [weak self] (cards) in
            self?.backendCardsModels = cards
            requestGroup.leave()
        }) { (error) in
            requestGroup.leave()
        }

        requestGroup.notify(queue: .main) { [weak self] in
            guard let _self = self else { return }
            _self.cardsModels.removeAll()
            _self.cardsModels.append(contentsOf: _self.firCardsModels)
            _self.cardsModels.append(contentsOf: _self.backendCardsModels)

            self?.tableView.reloadData()
            // for api manager
            self?.firstCardsCount = _self.cardsModels.count

            SVProgressHUD.dismiss()
        }
    }


    fileprivate func secondRequest(_ index: Int) {
        // the second request
        debugPrint("swipe index", index, "firstCardsCount", firstCardsCount)

        // This is for how much to the end of the deck, we ask for more cards.
        let muchMoreIndex = 15
        let checkNumber = firstCardsCount-1 - index - muchMoreIndex
        debugPrint("checkNumber", checkNumber)

        if checkNumber == 0 || checkNumber < 0 {
            guard !isSecondTypeRequestLaunch else { return }
            isSecondTypeRequestLaunch = true
            apiManager.requestCards(0, secondRequestLimit: 50, isFirstRequest: false, success: { [weak self] (backendCards) in
                DispatchQueue.main.async {
                    guard let _self = self else { return }
                    _self.cardsModels.append(contentsOf: backendCards)
                    _self.firstCardsCount = _self.cardsModels.count
                    _self.isSecondTypeRequestLaunch = false

                    _self.tableView.reloadData()
                }

                }, fail: { [weak self] (error) in
                    self?.isSecondTypeRequestLaunch = false
            })
        }
    }

}

import UIKit
import Kingfisher

class CardTestTableViewCell: UITableViewCell {

    @IBOutlet private weak var titleLabel: UILabel!
    @IBOutlet private weak var cardImageView: UIImageView!
    @IBOutlet private weak var profileImageView: UIImageView!

    override func prepareForReuse() {
        cardImageView.image = nil
        profileImageView.image = nil
    }

    func setupData(_ card: CardModel) {
        downloadImages(card)
        setupLabelsData(card)
    }

    private func downloadImages(_ card: CardModel) {
        if let placeAvatarURLString = card.photoURLsProperties.placePhotoURLs.first {
            if let placeAvatarURL = URL(string: placeAvatarURLString) {
                cardImageView.kf.indicatorType = .activity
                cardImageView.kf.setImage(with: placeAvatarURL)
            } else {
                cardImageView.image = UIImage(named: "CardDefaultImage")
            }
        } else if let eventLogoURLPath = card.photoURLsProperties.placeLogoURLs.first {
            if let url = URL(string: eventLogoURLPath) {
                cardImageView.kf.indicatorType = .activity
                cardImageView.kf.setImage(with: url)
            } else {
                cardImageView.image = UIImage(named: "CardDefaultImage")
            }
        } else {
            cardImageView.image = UIImage(named: "CardDefaultImage")
        }

        guard card.boolProperties.isAutoGeneratedCard != true && card.boolProperties.isAdminCreatedCard != true else {
            profileImageView.image = #imageLiteral(resourceName: "ProfileDefaultIcon")
            return
        }

        let firImageDatabaseManager = FIRImageDatabaseManager()
        firImageDatabaseManager.downloadCardUserProfileImageBy(card.ownerID) { [weak self] (url, error) in
            DispatchQueue.main.async {
                guard error == nil else {
                    self?.profileImageView.image = #imageLiteral(resourceName: "ProfileDefaultIcon")
                    return
                }
                guard let _url = url else {
                    self?.profileImageView.image = #imageLiteral(resourceName: "ProfileDefaultIcon")
                    return
                }
                self?.profileImageView.kf.indicatorType = .activity
                self?.profileImageView.kf.setImage(with: _url)
            }
        }
    }

    private func setupLabelsData(_ card: CardModel) {
        titleLabel.text = card.title
    }

}

Обновление 2. Когда я закомментировал код, который связан с фреймворком Kingfisher, то утечки памяти и краха приложения нет.


person Alexander Khitev    schedule 02.06.2017    source источник
comment
используйте SDWebImage для кэширования изображений.   -  person KKRocks    schedule 02.06.2017
comment
Привет @KKRocks Подскажите пожалуйста, в чем зимородок не подходит? Эта же библиотека написана только на swift. Далее я хочу спросить, вы думаете, это не проблема большого набора данных (который на данный момент может содержать до 1000 моделей).   -  person Alexander Khitev    schedule 02.06.2017
comment
Не могли бы вы поделиться примером одного объекта данных вашей новостной ленты.   -  person Muhammad Ali Yousaf    schedule 02.06.2017
comment
Привет @MuhammadAliYousaf да, я могу. Я сделал это в виде json, чтобы не копировать классы модели. Так удобно?   -  person Alexander Khitev    schedule 02.06.2017
comment
Это не должно быть проблемой, если вы не храните UIImage в своем источнике данных, если вы это сделаете, удалите его, также предложите удалить зимородка и попробуйте SDWebImage, чтобы увидеть, исправит ли он, любая библиотека может иметь проблемы, если обе есть, это ошибка вашего кода   -  person Tj3n    schedule 02.06.2017
comment
@ Tj3n Я решил свою проблему. Спасибо за ответ   -  person Alexander Khitev    schedule 02.06.2017


Ответы (1)


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

Я поместил эту функцию в AppDelegate и вызвал функцию didFinishLaunchingWithOptions

fileprivate func setupKingfisherSettings() {
        ImageCache.default.maxMemoryCost = 1
    }
person Alexander Khitev    schedule 02.06.2017
comment
Странно видеть, что такая известная библиотека нуждается в настройке для решения этой простой проблемы, все изображения должны быть кэшированы на диске, а не в ОЗУ. - person Tj3n; 02.06.2017
comment
@Tj3n, пожалуйста, посмотрите здесь github.com/onevcat/Kingfisher/issues/697 - person Alexander Khitev; 02.06.2017
comment
Это не изменилось: ImageCache.default.memoryStorage.config.totalCostLimit - person Ahmadreza; 18.07.2021