table 视图被关闭后单元格未被释放,被闭包的上下文引用
Cell isn't being deallocated after table view is dismissed, being referenced by closure's context
我正在创建自定义 table 视图单元格,它允许用户添加、拍摄或查看上传的照片。
我发现即使在 table 视图关闭后,这个单元格也会永远保留在内存中,这造成了奇怪的 memory graph。 我希望正确关闭单元格,但我很难理解发生了什么。
图表显示我的单元格被 addPhotoTapAction.context.
强烈引用
addPhotoTapAction: ((ItemInfoCell) -> Void)? 是单元格的 class 变量,用于存储处理用户输入的闭包。闭包在视图控制器中定义:
let infocell = tableView.dequeueReusableCell(withIdentifier: K.infocellID) as! ItemInfoCell
if item?.imageUrl == nil {
self.imageManager.actionController?.actions[2].isEnabled = false
} else {
self.imageManager.actionController?.actions[2].isEnabled = true
}
infocell.addPhotoTapAction = { [unowned self] _ in
infocell.addPhotoButton.isEnabled = false
self.imageManager.pickImage(self) { [weak self] image in
self?.imageToSave = image
infocell.itemPhoto.image = self?.imageToSave
infocell.addPhotoButton.tintColor = UIColor(ciColor: .clear)
infocell.addPhotoButton.isEnabled = true
self?.imageManager.actionController?.actions[2].isEnabled = true
}
pickImage方法如下图。它用于向动作控制器显示图像选择器选项(拍照或从库中选择):
func pickImage(_ viewController: UIViewController, _ callback: @escaping ((UIImage) -> ())) {
picker.delegate = self
picker.mediaTypes = ["public.image"]
picker.allowsEditing = true
pickImageCallback = callback
self.viewController = viewController
actionController!.popoverPresentationController?.sourceView = viewController.view
viewController.present(actionController!, animated: true, completion: nil)
}
...并存储回调以用于选择器的 didFinishPickingMediaWithInfo 调用:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion: nil)
if let image = info[.editedImage] as? UIImage {
let squareImage = makeSquare(image)
pickImageCallback?(squareImage)
} else if let image = info[.originalImage] as? UIImage {
let squareImage = makeSquare(image)
pickImageCallback?(squareImage)
}
viewController = nil
}
我尝试手动将带闭包的变量设置为 nil,然后从 [weak self] 切换到 [unowned self] 以及两者的组合。运气不好。
我认为 pickImage(self),或者在闭包中使用 class' 属性,即使在使用 [weak/unowned] 捕获列表,但我仍然不确定并且无法修复它。
更新:ItemInfoCell class'代码
class ItemInfoCell: UITableViewCell {
@IBOutlet weak var itemPhoto: UIImageView!
@IBOutlet weak var itemLabel: UILabel!
@IBOutlet weak var addPhotoButton: UIButton!
var addPhotoTapAction: ((ItemInfoCell) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
@IBAction func takePhoto(_ sender: Any) {
if let addPhoto = self.addPhotoTapAction {
addPhoto(self)
}
}
}
问题是您在其回调中访问了信息单元。如果您在自己的回调中使用变量,您应该通过将其添加到捕获列表来将其标记为弱变量。
在你的代码中应该是这样的:
infocell.addPhotoTapAction = { [unowned self, weak infocell] _ in
...
}
在你的代码中,有一个从 infocell 到闭包的强引用,以及你指的是 infocell 的内部闭包,即两者之间有一个强引用。
infocell.addPhotoTapAction = { [unowned self] _ in
infocell.addPhotoButton.isEnabled = false
...
}
这会导致保留周期并且不允许释放单元格。
由于您已经在为自己使用捕获列表,您可以通过将 infocell 添加到捕获列表来轻松解决此问题,如下所示:
infocell.addPhotoTapAction = { [weak self, weak infocell] _ in
guard let self = self else { return }
infocell.addPhotoButton.isEnabled = false
...
}
一般来说,只有在完全确定引用不会在执行闭包之前变成nil
时才建议使用unowned
,因为使用unowned
就像强行展开一个可选值。如果是 nil
,您的应用程序将崩溃。所以 weak self
通常是更安全的方法。
我正在创建自定义 table 视图单元格,它允许用户添加、拍摄或查看上传的照片。
我发现即使在 table 视图关闭后,这个单元格也会永远保留在内存中,这造成了奇怪的 memory graph。 我希望正确关闭单元格,但我很难理解发生了什么。
图表显示我的单元格被 addPhotoTapAction.context.
强烈引用addPhotoTapAction: ((ItemInfoCell) -> Void)? 是单元格的 class 变量,用于存储处理用户输入的闭包。闭包在视图控制器中定义:
let infocell = tableView.dequeueReusableCell(withIdentifier: K.infocellID) as! ItemInfoCell
if item?.imageUrl == nil {
self.imageManager.actionController?.actions[2].isEnabled = false
} else {
self.imageManager.actionController?.actions[2].isEnabled = true
}
infocell.addPhotoTapAction = { [unowned self] _ in
infocell.addPhotoButton.isEnabled = false
self.imageManager.pickImage(self) { [weak self] image in
self?.imageToSave = image
infocell.itemPhoto.image = self?.imageToSave
infocell.addPhotoButton.tintColor = UIColor(ciColor: .clear)
infocell.addPhotoButton.isEnabled = true
self?.imageManager.actionController?.actions[2].isEnabled = true
}
pickImage方法如下图。它用于向动作控制器显示图像选择器选项(拍照或从库中选择):
func pickImage(_ viewController: UIViewController, _ callback: @escaping ((UIImage) -> ())) {
picker.delegate = self
picker.mediaTypes = ["public.image"]
picker.allowsEditing = true
pickImageCallback = callback
self.viewController = viewController
actionController!.popoverPresentationController?.sourceView = viewController.view
viewController.present(actionController!, animated: true, completion: nil)
}
...并存储回调以用于选择器的 didFinishPickingMediaWithInfo 调用:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion: nil)
if let image = info[.editedImage] as? UIImage {
let squareImage = makeSquare(image)
pickImageCallback?(squareImage)
} else if let image = info[.originalImage] as? UIImage {
let squareImage = makeSquare(image)
pickImageCallback?(squareImage)
}
viewController = nil
}
我尝试手动将带闭包的变量设置为 nil,然后从 [weak self] 切换到 [unowned self] 以及两者的组合。运气不好。
我认为 pickImage(self),或者在闭包中使用 class' 属性,即使在使用 [weak/unowned] 捕获列表,但我仍然不确定并且无法修复它。
更新:ItemInfoCell class'代码
class ItemInfoCell: UITableViewCell {
@IBOutlet weak var itemPhoto: UIImageView!
@IBOutlet weak var itemLabel: UILabel!
@IBOutlet weak var addPhotoButton: UIButton!
var addPhotoTapAction: ((ItemInfoCell) -> Void)?
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
@IBAction func takePhoto(_ sender: Any) {
if let addPhoto = self.addPhotoTapAction {
addPhoto(self)
}
}
}
问题是您在其回调中访问了信息单元。如果您在自己的回调中使用变量,您应该通过将其添加到捕获列表来将其标记为弱变量。
在你的代码中应该是这样的:
infocell.addPhotoTapAction = { [unowned self, weak infocell] _ in
...
}
在你的代码中,有一个从 infocell 到闭包的强引用,以及你指的是 infocell 的内部闭包,即两者之间有一个强引用。
infocell.addPhotoTapAction = { [unowned self] _ in
infocell.addPhotoButton.isEnabled = false
...
}
这会导致保留周期并且不允许释放单元格。
由于您已经在为自己使用捕获列表,您可以通过将 infocell 添加到捕获列表来轻松解决此问题,如下所示:
infocell.addPhotoTapAction = { [weak self, weak infocell] _ in
guard let self = self else { return }
infocell.addPhotoButton.isEnabled = false
...
}
一般来说,只有在完全确定引用不会在执行闭包之前变成nil
时才建议使用unowned
,因为使用unowned
就像强行展开一个可选值。如果是 nil
,您的应用程序将崩溃。所以 weak self
通常是更安全的方法。