部分标题的UITableView自定义视图(内存泄漏)。制作部分标题自定义视图的正确方法是什么?

时间:2015-07-20 17:33:03

标签: ios swift uitableview memory-leaks uitableviewrowaction

我正在开发一个与“Instagram”中的用户界面相同的应用。我有一个饲料墙,其中用户滚动并查看朋友添加的新图片,类似于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已经创建或不。如果我的视图确实存在于内存中,那么有没有办法检查代码,那么就不需要再次分配内存了。 有人请帮助:)。我甚至会感激一点帮助。

2 个答案:

答案 0 :(得分:4)

您通常需要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并添加图像和标签属性(然后您不必删除然后将子视图添加到出列视图中,只需按名称替换它们),此实现就会更直截了当。此外,如果您为班级设计笔尖或使用表格视图单元格通过故事板进行查看,则队列将永远不会返回为零。

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

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

你会注意到,当上面的代码试图将一个单元格出列时,它会在if let语句中测试它是否为零。这很重要,因为如果您只是加载表视图,它在队列中没有任何单元格/视图给您,所以如果它是nil,那么您从头开始实例化一个新视图。实例化它时,为其提供重用标识符字符串。如果此视图稍后在屏幕外显示,Cocoa知道将其放入特殊队列以便稍后再回复给您。如上所述,但是,如果您的视图是从笔尖或故事板加载的,则队列将始终返回非零值。关于从笔尖加载的最后一个细节,您需要使用registerNib:forHeaderFooterReuseIdentifer:的{​​{1}}方法注册重用标识符。

现在你可以看到Cocoa如何非常高效地处理这些单元格/视图,不需要你在用户滚动表格视图时实例化新视图,你可能会对我上面的代码感到有些失望,但仍然会删除所有旧的子视图和实例化新的UITableViewUIImageView,确实你应该感到失望。推荐的解决方法是通过继承UILabel,或者你可以真正子类化任何UITableViewHeaderFooterView。为imageView和label创建一个属性。现在,不要使用旧内容销毁子视图,然后再次创建这些视图,只需使用新的适当内容(UIViewimageView.image = newUIImage)替换其内容。信不信由你,这将节省大量的计算。 label.text = newString的创建成本有些高。子类化的一种草率(尽管可能更灵活)替代方法是使用UIView属性将标记放在要插入的视图上,然后使用tag获取对这些视图的引用。

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

祝你好运@Ankush!知道headerView.viewWithTag(anInt)周围的绳索是一项很棒的iOS技能,我相信你将来会发现它很有用!

答案 1 :(得分:1)

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

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就会保留您保留的视图,并在不再需要时将其释放。