内存分配分析器和持续增加的持久内存 - 出现问题的迹象?

问题描述

我正在开发一个应用程序,使用它的利益相关者表示,在整天持续使用后,该应用程序变得缓慢且无法使用/无响应。杀死它并重新开始使其运行良好。

我的设备似乎没有这个问题,但我开始在调试器中查看模拟器/手机中的内存使用情况,并观察到如果我采取在屏幕之间切换的基本操作,我的内存会稳步增加屏幕。这些都是非常复杂的屏幕,但如果我只是前进到“添加新项目”屏幕,然后再回到产品列表屏幕,内存会增加 30MB。如果我一遍又一遍地重复执行相同的操作,我可以将其内存增加到 1.1GB

然后我更进一步,连接我的手机,并运行分析器(特别是内存泄漏)。我发现了一个涉及我使用广告的泄漏,所以我只是将所有代码注释掉以进行测试,当泄漏消失时,内存继续稳步上升。

然后我运行了分配工具,以同样的方式来回运行了几分钟后,输出如下:

XCode Profiler - Allocations

如您所见,它是 1.53GB,如果我继续执行相同的操作,我可以将其增加到 2GB+。奇怪的是,我的手机似乎从不介意,屏幕有时会稍微滞后,否则还不错。当然可以用。

在我开始拆地板之前,我想确认这可能是问题的迹象。关于我可以从哪里开始寻找的任何建议?如果持久内存是问题,那么典型的陷阱或陷阱是什么?什么是“匿名虚拟机?”

非常感谢您阅读到这里,并感谢您的指导!

更新/编辑

经过这里的一些指导,我注意到,奇怪的是,在“添加产品”页面上,每次我访问它时,它都会导致内存跳跃 ~10MB。注释掉代码后,我将其缩小到导致跳转的这一部分(甚至是代码行)。删除代码会使其保持稳定且不会增加

 //Render collection views
    func collectionView(_ collectionView: UICollectionView,cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell",for: indexPath as IndexPath) 
        
            let member: MemberDto = groupMembers[indexPath.item]
            let contactinitials = cell.viewWithTag(1) as! UILabel
            let contactAvatar = cell.viewWithTag(2) as! UIImageView
            contactAvatar.image = UIImage(named: "anonymous")
            contactinitials.text = member.displayName
            contactAvatar.layer.cornerRadius = contactAvatar.frame.size.width / 2
            contactAvatar.clipsToBounds = true
            contactAvatar.contentMode = UIViewContentMode.scaleAspectFill
            contactAvatar.layer.borderWidth = 5.0

            
             
            if (member.profileImage.trimmingCharacters(in: CharacterSet.whitespaces) != "") {
                UserService.getProfilePicture(userId: member.userId) {
                    response in
                    
                    contactAvatar.image = response.value
                }
            }
          

所以,有问题的代码在这里

 contactAvatar.image = response.value

把它加进去,然后来回往这个tableviewcontroller 导致内存不断上升,一直上升到2gb。删除那一行代码(我设置图像的地方)使其稳定在~40-70mb,或者它上升但非常非常缓慢(几十次重复只让它达到80mb)

我意识到我没有缓存这张图片

我决定尝试用我的框架缓存它,并立即解决了问题。我想这行代码是将图像拉入内存或类似的东西?网络调用似乎并不是真正的问题,因为我把它留在了(甚至到目前为止对我的 API 进行了额外的调用)并且这似乎并没有通过增加内存来做太多事情。>

只是一些信息:

  • 在主屏幕上,您点击导航菜单栏中的 + 符号即可进入此屏幕。
  • 我在我的故事板上使用与导航按钮相关的常规转场,将用户带到这里
  • 在这个 vc 上放置 deinit 似乎永远不会成功,即使在那里有打印/代码和断点
  • 在我的 uitableviewcontroller 中进行 API 调用似乎不会导致图像加载,除非我将其与设置图像相结合。如果我打网络电话,但不设置图像,它不会增加

我犯了什么错误?我觉得缓存图像是一个创可贴 - 我记得读过你不应该在 UITableViewController 中调用图像但是有什么替代方法,提前从集合中提取所有用户图像并在 tableview 加载之前缓存它们?

编辑 2

正如@matt 所建议的,这只是一个创可贴。真正的问题仍然存在,因为我知道 deinit() 没有被调用。拉出主要代码块后,我发现了这个

lblMessage.addTapGestureRecognizer {
            self.txtMessage.becomeFirstResponder()
        }
        

映射到扩展类:

public func addTapGestureRecognizer(action: (() -> Void)?) {
        self.isUserInteractionEnabled = true
        self.tapGestureRecognizerAction = action
        let tapGestureRecognizer = UITapGestureRecognizer(target: self,action: #selector(handleTapGesture))
        self.addGestureRecognizer(tapGestureRecognizer)
    }
    public func addLongPressGestureRecognizer(action: (() -> Void)?) {
        self.isUserInteractionEnabled = true
        self.longPressGestureRecognizerAction = action
        let longPressGestureRecognizer = UILongPressGestureRecognizer(target: self,action: #selector(handleLongPressGesture))
        self.addGestureRecognizer(longPressGestureRecognizer)
    }
     
    
    // Every time the user taps on the View,this function gets called,// which triggers the closure we stored
    @objc fileprivate func handleTapGesture(sender: UITapGestureRecognizer) {
        if let action = self.tapGestureRecognizerAction {
            action?()
        } else {
            print("no action")
        }
    }

所以问题一定出在这里。我把它带到一个新线程: Deinit not calling - Cannot find why something is retaining (code provided)

谢谢!希望这对某人有所帮助。

解决方法

是的,这是一个问题,是的,您需要修复它。造成这种事情的两个常见原因是:

  • 你有一个保留周期,至少你的一些视图控制器永远不会消失。

  • 您错误地设计了故事板(或手动转场)序列,因此(例如)您从视图控制器 A present 到视图控制器 B,然后为了“返回”您present从控制器 B 到视图控制器 A。因此,您实际上不会“返回”;相反,您正在第一个视图控制器 A 之上堆积第二个视图控制器,以此类推,永远。

无论哪种方式,您都可以通过在所有视图控制器中实现 deinitprint(self) 来快速测试此类事情是否正在发生。然后玩这个应用程序。如果每次“返回”时都没有在日志中看到打印输出,则说明您遇到了严重的内存问题,因为视图控制器没有在应该释放的时候释放,您需要修复它。>