代码之家  ›  专栏  ›  技术社区  ›  ekscrypto

如何在有大量项目的uiCollectionView上减少内存使用?

  •  0
  • ekscrypto  · 技术社区  · 6 年前

    我正在尝试使用uiCollectionView而不是uiTableView来显示大量的项目(>15000),而且uiCollectionView似乎预先为集合所需的整个ContentView大小分配了一个像素缓冲区。根据项目大小,模拟器最多显示所需的6.75GB内存。

    我希望集合视图基于其协议非常类似于uitableview,不会分配任何像素缓冲区,而只依赖于单元格的支持/呈现。

    我正在使用故事板文件定义集合视图和单元格,两者都具有opaque=false。我看过许多关于堆栈溢出的文章,大多数都与内存泄漏有关,因此我对如何解决该问题有点困惑。

    好奇的是,这里是整个代码库(除了故事板):

    myCollectionViewCell.swift

    导入uikit 类myCollectionViewCell:uiCollectionViewCell{ static let identifier=“菌丝识别器” }

    uicolor+随机。快速

    导入uikit 扩展uicolor{ static func randomcolor()->uicolor{ 设为red=cgfloat(drand48()) 设为绿色=cgfloat(drand48()) 设蓝色=cgfloat(drand48()) 返回uicolor(红色:红色,绿色:绿色,蓝色:蓝色,alpha:1.0) } }

    视图控制器.swift

    导入uikit 类视图控制器:uiviewcontroller、uicollectionviewdatasource、uicollectionviewdelegate、uicollectionviewdelegateflowlayout{ func numberofsections(在CollectionView:uiCollectionView中)->int{ 返回10000 } func集合视图(uCollectionView:uiCollectionView,NumberOfitemsInSection:int)->int{ 返回30000 } func集合视图(uCollectionView:uiCollectionView,cellForItemAt indexPath:indexPath)->uiCollectionViewCell{ 让cell=collectionview.dequeuerusablecell(带有reuseIdentifier:myCollectionViewcell.identifier,for:indexPath)作为!myCollectionView单元格 cell.backgroundcolor=uicolor.randomcolor()。 返回单元格 } @iboutlet弱var集合视图:uiCollectionView! 重写func viewdidload()。{ super.viewdidload()。 //在加载视图后执行任何其他设置,通常是从NIB加载。 } }

    某些用例下的内存使用情况: =

    我希望集合视图,基于它与uitableview非常相似的协议,不会分配任何像素缓冲区,而仅仅依赖于单元格的支持/呈现。

    我正在使用故事板文件定义集合视图和单元格,两者都具有opaque=false。我看过很多关于栈溢出的文章,大多数都与内存泄漏有关,所以我对如何解决这个问题有点困惑。

    好奇的是,这里是整个代码库(而不是故事板):

    myCollectionViewCell.swift

    import UIKit    
    class MyCollectionViewCell: UICollectionViewCell {
        static let identifier = "myCellIdentifier"
    }
    

    uicolor+随机.swift

    import UIKit
    extension UIColor {
        static func randomColor() -> UIColor {
            let red = CGFloat(drand48())
            let green = CGFloat(drand48())
            let blue = CGFloat(drand48())
            return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
        }
    }
    

    视图控制器.swift

    import UIKit
    class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
    
        func numberOfSections(in collectionView: UICollectionView) -> Int {
            return 10000
        }
    
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return 30000
        }
    
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCollectionViewCell.identifier, for: indexPath) as! MyCollectionViewCell
            cell.backgroundColor = UIColor.randomColor()
            return cell
        }
    
    
        @IBOutlet weak var collectionView: UICollectionView!
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
        }
    }
    

    Storyboard

    某些用例下的内存使用情况:

    1 回复  |  直到 6 年前
        1
  •  0
  •   ekscrypto    6 年前

    layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]?

    import UIKit
    
    protocol LargeDataSourceProtocol {
        func largeNumberOfSections() -> Int
        func largeNumberOfItems(in section: Int) -> Int
        func largeNumberToCollectionViewCacheSize() -> Int
        func associateLargeIndexPath(_ largeIndexPath: IndexPath) -> IndexPath
    }
    
    class LargeDataSourceCoordinator: NSObject, UICollectionViewDataSource, LargeDataSourceProtocol {
    
        var cachedMapEntries: [IndexPath: IndexPath] = [:]
        var rotatingCacheIndex: Int = 0
    
        func largeNumberToCollectionViewCacheSize() -> Int {
            return 1024 // arbitrary number, increase if rendering issues are visible like cells not appearing when scrolling
        }
    
        func largeNumberOfSections() -> Int {
            // To do: implement logic to find the number of sections
            return 10000 // simplified arbitrary number for sake of demo
        }
    
        func largeNumberOfItems(in section: Int) -> Int {
            // To do: implement logic to find the number of items in each section
            return 30000  // simplified arbitrary number for sake of demo
        }
    
        func associateLargeIndexPath(_ largeIndexPath: IndexPath) -> IndexPath {
            for existingPath in cachedMapEntries where existingPath.value == largeIndexPath {
                return existingPath.key
            }
            let collectionViewIndexPath = IndexPath(item: rotatingCacheIndex, section: 0)
            cachedMapEntries[collectionViewIndexPath] = largeIndexPath
            rotatingCacheIndex = (rotatingCacheIndex + 1) % self.largeNumberToCollectionViewCacheSize()
            return collectionViewIndexPath
        }
    
        func numberOfSections(in collectionView: UICollectionView) -> Int {
            return 1
        }
    
        func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
            return self.largeNumberToCollectionViewCacheSize()
        }
    
        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
            let cell = MyCollectionViewCell.dequeue(from: collectionView, for: indexPath)
            guard let largeIndexPath = cachedMapEntries[indexPath] else { return cell }
    
            // retrieve the data at largeIndexPath.section, largeIndexPath.item
            // configure cell accordingly
            cell.addDebugText("section: \(largeIndexPath.section)\nitem: \(largeIndexPath.item)")
            return cell
        }
    }
    

    import UIKit
    
    class LargeDataSourceLayout: UICollectionViewLayout {
    
        let cellSize = CGSize(width: 100, height: 100)
    
        var cellsPerRow: CGFloat {
            guard let collectionView = self.collectionView else { return 1.0 }
            return (collectionView.frame.size.width / cellSize.width).rounded(.towardZero)
        }
    
        var cacheNumberOfItems: [Int] = []
        private func refreshNumberOfItemsCache() {
            guard
                let largeDataSource = self.collectionView?.dataSource as? LargeDataSourceProtocol
                else { return }
            cacheNumberOfItems.removeAll()
            for section in 0 ..< largeDataSource.largeNumberOfSections() {
                let itemsInSection: Int = largeDataSource.largeNumberOfItems(in: section)
                cacheNumberOfItems.append(itemsInSection)
            }
        }
    
        var cacheRowsPerSection: [Int] = []
        private func refreshRowsPerSection() {
            let itemsPerRow = Float(self.cellsPerRow)
            cacheRowsPerSection.removeAll()
            for section in 0 ..< cacheNumberOfItems.count {
                let numberOfItems = Float(cacheNumberOfItems[section])
                let numberOfRows = (numberOfItems / itemsPerRow).rounded(.awayFromZero)
                cacheRowsPerSection.append(Int(numberOfRows))
            }
        }
    
        override var collectionViewContentSize: CGSize {
            // To do: update logic as per your requirements
            refreshNumberOfItemsCache()
            refreshRowsPerSection()
            let totalRows = cacheRowsPerSection.reduce(0, +)
            return CGSize(width: self.cellsPerRow * cellSize.width,
                          height: CGFloat(totalRows) * cellSize.height)
        }
    
        override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
            // To do: implement logic to compute the attributes for a specific item
            return nil
        }
    
        private func originForRow(_ row: Int) -> CGFloat {
            return CGFloat(row) * cellSize.height
        }
    
        private func pathsInRow(_ row: Int) -> [IndexPath] {
            let itemsPerRow = Int(self.cellsPerRow)
            var subRowIndex = row
            for section in 0 ..< cacheRowsPerSection.count {
                let rowsInSection = cacheRowsPerSection[section]
                if subRowIndex < rowsInSection {
                    let firstItem = subRowIndex * itemsPerRow
                    let lastItem = min(cacheNumberOfItems[section],firstItem+itemsPerRow) - 1
                    var paths: [IndexPath] = []
                    for item in firstItem ... lastItem {
                        paths.append(IndexPath(item: item, section: section))
                    }
                    return paths
                } else {
                    guard rowsInSection <= subRowIndex else { return [] }
                    subRowIndex -= rowsInSection
                }
            }
            // if caches are properly updated, we should never reach here
            return []
        }
    
        override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
            guard let largeDataSource = self.collectionView?.dataSource as? LargeDataSourceProtocol else { return nil }
    
            let firstRow = max(0,Int((rect.minY / cellSize.height).rounded(.towardZero)))
            var row = firstRow
            var attributes: [UICollectionViewLayoutAttributes] = []
            repeat {
                let originY = originForRow(row)
                if originY > rect.maxY {
                    return attributes
                }
    
                var originX: CGFloat = 0.0
                for largeIndexPath in pathsInRow(row) {
                    let indexPath = largeDataSource.associateLargeIndexPath(largeIndexPath)
                    let itemAttribute = UICollectionViewLayoutAttributes(forCellWith: indexPath)
                    itemAttribute.frame = CGRect(x: originX, y: originY, width: cellSize.width, height: cellSize.height)
                    attributes.append(itemAttribute)
                    originX += cellSize.width
                }
    
                row += 1
            } while true
        }
    }
    

    import UIKit
    
    class MyCollectionViewCell: UICollectionViewCell {
    
        static let identifier = "MyCollectionViewCell"
    
        static func dequeue(from collectionView: UICollectionView, for indexPath: IndexPath) -> MyCollectionViewCell {
            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as? MyCollectionViewCell ?? MyCollectionViewCell()
            cell.contentView.backgroundColor = UIColor.random()
            return cell
        }
    
        override func prepareForReuse() {
            super.prepareForReuse()
            removeDebugLabel()
        }
    
        private func removeDebugLabel() {
            self.contentView.subviews.first?.removeFromSuperview()
        }
    
        func addDebugText(_ text: String) {
            removeDebugLabel()
            let debugLabel = UILabel(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
            debugLabel.text = text
            debugLabel.numberOfLines = 2
            debugLabel.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)
            debugLabel.textColor = UIColor.black
            debugLabel.textAlignment = .center
            self.contentView.addSubview(debugLabel)
        }
    }
    

    import UIKit
    
    extension UIColor {
        static func random() -> UIColor {
            //random color
            let hue = CGFloat(arc4random() % 256) / 256.0
            let saturation = (CGFloat(arc4random() % 128) / 256.0) + 0.5 // 0.5 to 1.0, away from white
            let brightness = (CGFloat(arc4random() % 128) / 256.0 ) + 0.5 // 0.5 to 1.0, away from black
            return UIColor(hue: hue, saturation: saturation, brightness: brightness, alpha: 1.0)
        }
    }
    

    iPhone simulator screenshot memory requirements