2017-06-02 22 views
1

我的確喜歡新聞,我遇到以下問題,例如,如果用戶上傳超過300條新聞,那麼應用程序已經佔用超過300兆字節的內存。一旦在測試中,我確實得到didReceiveMemoryWarning,它只幫助完全清理dataSource。我也使用Kingfisher來緩存圖像。這種情況的最佳方式是什麼?緩存第一個數據,如果用戶將返回頂部(到最新的數據),然後從緩存中加載它們,或者如果有更好的方法?謝謝。Kingfisher將數據緩存在RAM中

更新:這是新聞JSON模型。

[ 「peopleProperties」: 「numberOfPeopleDescription」: 「沒有人在這裏」, 「numberOfPeople」:0, 「availableSeats」:0], 「isPrivateStatus」:假的, 「additionalInfo」: 「注意」: 「」 ],「ownerID」:「」,「ticketsInfo」:[「tickets」:[]],「isTest」:false,「isNewPendingRequest」:false,「dateProperties」:[「isEditable」:true,「iso8601」 「,」day「:」「,」endTimeStamp「:0.0,」isFlexDate「:true,」isFlexTime「:true,」timeStamp「:0.0],」boolProperties「:[」isPartnerGeneratedCard「:false,」isAutoGeneratedCard「:true ,「isUserCreatedCard」:false,「isAdminCreatedCard」:false],「location」:[「formattedAddress」:「692 N Robertson Blvd(位於Santa Monica Blvd),美國加利福尼亞州西好萊塢90069」,「fullLocationName」 692 N Robertson Blvd「,」coordinate「:[」longitude「:-118.38528500025966,」latitude「:34.083373986214625]],」id「:」「,」photoURLsProperties「:[」placePhotoURLs「:[例如「],」placeLogoURLs「:[]],」services「:[」serviceURL「:」「,」serviceID「:」41cf5080f964a520a61e1fe3「,」index「:1],」version「:1,修道院食品&酒吧」, 「OWNERNAME」: 「」, 「手機」:[:]

更新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框架相關的代碼時,就沒有內存泄漏和應用程序崩潰。

+0

使用SDWebImage。 – KKRocks

+0

嗨@KKRocks請告訴我,在什麼翠鳥不適合?這個相同的庫只能快速寫入。接下來我想問一下,你認爲這不是一個大型數據集(目前最多可容納1000個模型)的問題。 – Alexander

+0

您能否分享您的新聞提要數據的示例單個對象。 –

回答

1

我解決了我的問題,事實上Kingfisher最初將所有圖像存儲在RAM中,它具有如果應用程序收到內存警告,那麼它應該釋放內存,但在我的情況下,這不是。所以我爲Kingfisher設定了限制,即你只能使用1兆字節的RAM內存。

AppDelegate放在這個函數和函數調用圖像緩存didFinishLaunchingWithOptions

fileprivate func setupKingfisherSettings() { 
     ImageCache.default.maxMemoryCost = 1 
    } 
+0

奇怪的是,看到這樣一個着名的庫需要設置來處理這個簡單的問題,所有的圖像應該緩存在磁盤上而不是RAM – Tj3n

+0

@ Tj3n請看這裏https://github.com/onevcat/Kingfisher/issues/697 – Alexander