【发布时间】:2021-07-09 23:26:39
【问题描述】:
我有一个正在开发的应用程序,使用它的利益相关者说,在持续使用一整天后,该应用程序变得缓慢且无法使用/无响应。杀死它并重新开始会使它运行良好。
我的设备上似乎没有这个问题,但我开始在调试器中查看模拟器/手机的内存使用情况,并观察到如果我采取在屏幕之间切换的基本操作,我的内存会稳步增加屏幕。这些是相当复杂的屏幕,但如果我只是前进到“添加新项目”屏幕,然后回到产品列表屏幕,内存就会增加 30mb。如果我一遍又一遍地做同样的动作,我可以让它达到 1.1gb 的内存
然后我更进一步,连接我的手机并运行分析器(特别是内存泄漏)。我发现了一个涉及我使用广告的泄漏,所以我只是注释掉了所有代码进行测试,虽然泄漏消失了,但内存继续稳定增长。
然后我运行了分配工具,在以同样的方式来回运行几分钟后,输出如下:
如您所见,它是 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 进行了额外的调用),而且这似乎对内存增加没有太大作用。
只是一些信息:
- 在主屏幕中,您可以点击导航菜单栏中的 + 符号以进入此屏幕。
- 我在我的故事板上使用了一个与导航按钮相关联的常规 segue,将用户带到这里
- 在这个 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)
谢谢!希望这对某人有所帮助。
【问题讨论】:
-
我的第一个工具一般是运行app,锻炼一下,回到静止状态(即app的“主屏幕”),重复,然后看“Debug Memory Graph”,看看对于我知道应该被释放的任何我自己列出的对象。通过这种方式,我专注于自己的强引用循环(在我担心操作系统对象之前),即我可能已经引入的强引用循环。话虽如此,您的内存增长得足够快,我会特别注意您自己的大型资产(图像、视频等),这些资产可能不会被释放和/或对内存压力没有反应。
-
但是我们无法为您提供帮助。我们需要可重现的问题示例。
-
但是您必须确定问题是真正的泄漏(在 Swift 中很少见)、废弃的内存(例如强引用周期)还是未使用的内存(例如草率或过度激进的缓存做法)。
-
谢谢@Rob 我刚刚添加了代码,并提供了一些关于有问题的代码行的额外细节。有什么想法吗?
标签: ios swift xcode memory-leaks instruments