Memory Allocations Profiler 和稳步增加的持久内存 - 麻烦的迹象?
Memory Allocations Profiler and Steadily Increasing Persistent Memory - Signs of Trouble?
我有一个正在开发的应用程序,使用它的利益相关者说该应用程序变慢了,并且 unusable/unresponsive 经过一整天的持续使用。杀死它并重新开始会使它 运行 正常。
我的设备上似乎没有这个问题,但我开始在调试器中查看 simulator/phone 中的内存使用情况,发现如果我采取以下基本操作,我的内存会稳步增加在屏幕之间切换。这些是非常复杂的屏幕,但如果我只是前进到 'add new item' 屏幕,然后返回产品列表屏幕,内存会增加 30mb。如果我一遍又一遍地继续执行相同的操作,我可以获得 1.1gb 的内存
然后我更进一步,连接了我的 phone 和 运行 分析器(特别是内存泄漏)。我发现一个涉及我使用广告的泄漏,所以我只是注释掉所有代码进行测试,并且在泄漏消失的同时,内存继续稳步上升。
然后我 运行 分配工具,以同样的方式来回运行几分钟后,输出如下:
如您所见,它是 1.53GB,如果我继续执行相同的操作,我可以将其增加到 2GB+。奇怪的是,我的 phone 似乎从不介意,而且屏幕有时会稍微有点滞后,否则还算不错。当然可以用。
在我开始拆地板之前,我想确认这可能是一个问题的迹象。关于我可以从哪里开始寻找的任何建议?如果持久内存是问题所在,那么一些典型的陷阱或陷阱是什么?什么是“匿名虚拟机”?
非常感谢您阅读到这里,感谢您的指导!
UPDATE/EDIT
在这里经过一些指导后,我注意到,奇怪的是,每次访问“添加产品”页面时,它都会导致内存跳跃 ~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 似乎从来没有命中,即使那里有 print/code 和断点
- 从我的 uitableviewcontroller 中进行 API 调用似乎不会导致图像加载,除非我将其与设置图像结合起来。如果我进行网络调用,但不设置图像,它不会增加。
我犯了什么错误?我觉得缓存图像是一个创可贴 - 我记得读过你不应该在 UITableViewController 中调用图像但是有什么选择,提前从集合中提取所有用户图像并在加载 tableview 之前缓存它们?
编辑 2
正如@matt 所说,这只是一个创可贴。真正的问题仍然存在,因为我知道 deinit() 没有被调用。在取出主要代码块后,我发现了这个
lblMessage.addTapGestureRecognizer {
self.txtMessage.becomeFirstResponder()
}
映射到扩展 class:
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")
}
}
所以问题一定出在这里。我将其带到一个新线程:
谢谢!希望这对某人有所帮助。
是的,这是个问题,是的,您需要修复它。造成这种事情的两个通常原因是:
你有一个保留周期,这样至少你的一些视图控制器永远不会消失。
您错误地设计了故事板(或手动转场)序列,因此(例如)您 present
从视图控制器 A 到视图控制器 B,然后为了让你从控制器 B present
“返回”到查看控制器 A。因此你 不是 实际上“返回”;相反,您在第一个视图控制器之上堆积 second 视图控制器 A,依此类推,直到永远。
无论哪种方式,您都可以通过在所有视图控制器中实现 deinit
到 print(self)
来快速测试这种事情是否正在发生。然后玩这个应用程序。如果你每次“返回”时都没有在日志中看到打印输出,那么你有一个严重的内存问题,因为视图控制器没有在应该的时候被释放,你需要修复它。
我有一个正在开发的应用程序,使用它的利益相关者说该应用程序变慢了,并且 unusable/unresponsive 经过一整天的持续使用。杀死它并重新开始会使它 运行 正常。
我的设备上似乎没有这个问题,但我开始在调试器中查看 simulator/phone 中的内存使用情况,发现如果我采取以下基本操作,我的内存会稳步增加在屏幕之间切换。这些是非常复杂的屏幕,但如果我只是前进到 'add new item' 屏幕,然后返回产品列表屏幕,内存会增加 30mb。如果我一遍又一遍地继续执行相同的操作,我可以获得 1.1gb 的内存
然后我更进一步,连接了我的 phone 和 运行 分析器(特别是内存泄漏)。我发现一个涉及我使用广告的泄漏,所以我只是注释掉所有代码进行测试,并且在泄漏消失的同时,内存继续稳步上升。
然后我 运行 分配工具,以同样的方式来回运行几分钟后,输出如下:
如您所见,它是 1.53GB,如果我继续执行相同的操作,我可以将其增加到 2GB+。奇怪的是,我的 phone 似乎从不介意,而且屏幕有时会稍微有点滞后,否则还算不错。当然可以用。
在我开始拆地板之前,我想确认这可能是一个问题的迹象。关于我可以从哪里开始寻找的任何建议?如果持久内存是问题所在,那么一些典型的陷阱或陷阱是什么?什么是“匿名虚拟机”?
非常感谢您阅读到这里,感谢您的指导!
UPDATE/EDIT
在这里经过一些指导后,我注意到,奇怪的是,每次访问“添加产品”页面时,它都会导致内存跳跃 ~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 似乎从来没有命中,即使那里有 print/code 和断点
- 从我的 uitableviewcontroller 中进行 API 调用似乎不会导致图像加载,除非我将其与设置图像结合起来。如果我进行网络调用,但不设置图像,它不会增加。
我犯了什么错误?我觉得缓存图像是一个创可贴 - 我记得读过你不应该在 UITableViewController 中调用图像但是有什么选择,提前从集合中提取所有用户图像并在加载 tableview 之前缓存它们?
编辑 2
正如@matt 所说,这只是一个创可贴。真正的问题仍然存在,因为我知道 deinit() 没有被调用。在取出主要代码块后,我发现了这个
lblMessage.addTapGestureRecognizer {
self.txtMessage.becomeFirstResponder()
}
映射到扩展 class:
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")
}
}
所以问题一定出在这里。我将其带到一个新线程:
谢谢!希望这对某人有所帮助。
是的,这是个问题,是的,您需要修复它。造成这种事情的两个通常原因是:
你有一个保留周期,这样至少你的一些视图控制器永远不会消失。
您错误地设计了故事板(或手动转场)序列,因此(例如)您
present
从视图控制器 A 到视图控制器 B,然后为了让你从控制器 Bpresent
“返回”到查看控制器 A。因此你 不是 实际上“返回”;相反,您在第一个视图控制器之上堆积 second 视图控制器 A,依此类推,直到永远。
无论哪种方式,您都可以通过在所有视图控制器中实现 deinit
到 print(self)
来快速测试这种事情是否正在发生。然后玩这个应用程序。如果你每次“返回”时都没有在日志中看到打印输出,那么你有一个严重的内存问题,因为视图控制器没有在应该的时候被释放,你需要修复它。