1

我正在开发一个与“Instagram”具有相同 UI 的应用程序。我有一个供用户滚动并查看朋友添加的新图片的提要墙,类似于 instagram 。每个 UITableViewcell 都有一张图片和自己的部分 Header 视图。我通过为每个单元格制作部分来实现这一点。但我担心内存泄漏

这是我的代码

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

        let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 45))
        headerView.tag = 101

        let imageView :UIImageView   = UIImageView(frame: UtilityManager.setFrameWithOutHeight(CGRectMake(22, 5 , 30, 30)))
        imageView.backgroundColor = UIColor.blueColor()
        imageView.tag = 101
        imageView.layer.cornerRadius = 15.0
        imageView.clipsToBounds = true
        imageView.layer.borderWidth = 1
        imageView.layer.borderColor = UIColor.grayColor().CGColor
        headerView.addSubview(imageView)

        var label = UILabel(frame: UtilityManager.setFrameWithOutHeight(CGRectMake(50, 5, 320 - 60 , 35)))
        label.textAlignment = NSTextAlignment.Left
        label.backgroundColor = UIColor.clearColor()
        label.textColor = UIColor(red: 1, green: 0.28, blue: 0.27, alpha: 1)
        label.text = "I'am a test label"
        label.font = UIFont(name: Constants.FONT_MEDIUM, size: 12)
        headerView.addSubview(imageView)
        headerView.backgroundColor = UIColor.clearColor()

    return headerView
}

顺便说一句,我正在使用 Swift。它太酷了不是吗;)。让我详细解释一下这个委托函数来制作自定义部分的标题。每次当我的任何单元格或部分出现在屏幕上时,即使我滚动回已经分配了 headerView 的部分,也会调用此委托方法。因此,在滚动时会为一个部分多次创建 headerView。但我知道这是错误的方法,必须检查天气 headerView 是否已创建。如果我的视图确实存在于内存中,是否有任何方法可以检查代码,然后无需再次分配内存。有人请帮忙:)。即使是一点点帮助,我也会感激不尽。

4

2 回答 2

5

您通常需要 Cocoa 为表格单元提供的相同内存管理和单元重用模式,因此使用UITableViewHeaderFooterView. viewDidLoad:通话中tableView.registerClass(UITableViewHeaderFooterView.classForCoder(), forHeaderFooterReuseIdentifier:"Header")

现在,您可以在委托方法中按需将这些视图出列,例如:

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {

    let headerView: UITableViewHeaderFooterView
    if let reusedView = tableView.dequeueReusableHeaderFooterViewWithIdentifier("Header") as? UITableViewHeaderFooterView {
        // Remove the existing contents of the reused view
        map(reusedView.contentView.subviews) { 
            $0.removeFromSuperview() 
        }
        headerView = reusedView
    } else {
        headerView = UITableViewHeaderFooterView(reuseIdentifier:"Header")
    }

    let imageView = ... // Set up image view
    let label = ... // Set up label  

    headerView.contentView.addSubview(imageView)
    headerView.contentView.addSubview(label)

    return headerView
}

Cocoa 现在根据需要处理和重用这些标题视图。

如果您子类化并添加图像和标签属性,则此实现更加直接UITableViewHeaderFooterView(然后您不必删除然后将子视图添加到出队视图,只需用名称替换它们)。此外,如果您为您的班级设计一个 nib 或通过故事板为您的视图使用表格视图单元格,则出队将永远不会返回 nil。

既然您提到您可能有“无限单元格”,那么您可能担心在内存中保存太多数据源(也许您有大量用于这些图像视图的大图像)。在这种情况下,您需要设置某种缓存。看看NSCache你是否达到了这一点。无需实现自己的缓存。

应 OP 的要求,我将详细说明这个 dequeue 系统在 Cocoa 中是如何工作的。您的代码中可能有类似的代码tableView:cellForRowAtIndexPath:委托方法。想象一下,您的用户正在向下滚动您的表格视图。在表格视图的顶部,您的单元格和标题会离开屏幕,而新的单元格和标题会出现在表格视图的底部。所有这些单元格都具有相似的格式。现在假设 Cocoa 没有做任何特别的事情(没有细胞队列)。消失的单元格将被释放,而出现的单元格将从头开始创建。相反,Cocoa 试图提高效率并保留这些单元格/标题的队列。它不会在任何时候创建任何需要在屏幕上显示的内容。当其中一个单元格离开屏幕时,它会将其放入队列中,然后该单元格可供您稍后出列(从队列中取出)。当您将单元格或标题出列时(您根据其重用标识符字符串请求它),它将为您提供屏幕外(或其中之一)的确切视图。一旦你得到它,你可以根据需要进行配置。是的,在上面的代码中,您仍然必须创建UIImageViewUILabel(我将在下面解释你如何可以并且应该做得更好),但是UIView不需要更改的主要和任何类型的装饰视图仍然存在(重用它们没有计算成本)。所以基本上 Cocoa 可以帮助你回收这些视图,使事情变得非常高效和安全。

您会注意到,当上面的代码尝试使单元格出列时,它位于一个if let测试它是否为 nil 的语句中。这很重要,因为如果您只是加载表格视图,它在队列中没有任何单元格/视图可以提供给您,所以如果它为零,那么您从头开始实例化一个新视图。当你实例化它时,你给它一个重用标识符字符串。如果此视图稍后离开屏幕,Cocoa 会知道将其放入其特殊队列中以便稍后回馈给您。但是,正如我上面提到的,如果您的视图是从 nib 或情节提要加载的,则出列将始终返回非 nil。关于从 nib 加载的最后一个细节,您需要registerNib:forHeaderFooterReuseIdentifer:使用UITableView.

现在您可以看到 Cocoa 如何非常有效地处理这些单元格/视图,不需要您在用户滚动表格视图时实例化新视图,您可能对我上面的代码有点失望,因为它仍然让您删除了所有旧子视图并实例化 new UIImageViews 和UILabels,确实你应该感到失望。解决此问题的推荐方法是子类化UITableViewHeaderFooterView,或者您可以真正将任何子类化UIView。为 imageView 和 label 创建一个属性。现在不要用旧内容破坏子视图,然后再次创建这些视图,只需用新的适当内容(imageView.image = newUIImagelabel.text = newString)替换它们的内容。信不信由你,这将节省大量计算。 UIView创建 s 的成本有点高。子类化的一种草率(尽管可能更灵活)的替代方法是在您要使用该tag属性插入的这些视图上放置标签,然后稍后使用headerView.viewWithTag(anInt).

我讨厌所有这些关于标题的讨论都是徒劳的,但我会做最后的观察。我注意到您说每个单元格都有自己的标题。如果这是真的,您最好只制作一个大的表格单元格(将标题内容放在正常的表格视图单元格内容之上)。您可以一起跳过使用标题。不过,我不知道你的应用程序中发生的一切,所以我会让你对那个做出判断。

祝你好运@Ankush!了解周围的绳索UITableView是一项很棒的 iOS 技能,我相信您将来会发现它很有用!

于 2015-07-22T23:16:02.420 回答
2

您需要自己实现标题视图的缓存。该框架不会为你做这件事。您可以只创建一个数组实例变量来保存所有分配的视图。然后在委托方法中创建和缓存或简单地返回标题视图。

var headerViews = [UIView]()

func createHeaderView() -> UIView {
    let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 45))

    ...

    return headerView
}

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    while section >= headerViews.count {
        headerViews.append(createHeaderView())
    }

    var headerView = headerViews[section]

    // Set up the header view (image, text, ...)

    return headerView
}

请注意,这可能不是最有效的方法,因为一旦创建了所有标题视图,您就会保留它们。当您知道同时显示的标题视图的最大数量时,您可以合并某种缓存逐出策略并重用现有的标题视图。


更新

下面是上述代码的变体,它最多可重用 20 个标题视图。只要彼此相隔 20 个部分的标题不同时显示在屏幕上,这将起作用。

var headerViews = [UIView]()

func createHeaderView() -> UIView {
    let headerView = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 45))

    ...

    return headerView
}

func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
    let maxNumHeaderViews = 20
    let index = section % maxNumHeaderViews

    while index >= headerViews.count {
        headerViews.append(createHeaderView())
    }

    var headerView = headerViews[index]

    // Set up the header view (image, text, ...)

    return headerView
}

但是,关键点可能是您在问题中发布的代码中没有内存泄漏。Cocoa 将保留您返回的视图,只要它显示并在不再需要时释放它。

于 2015-07-20T19:11:18.013 回答