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