我有以下代码可用于更新模型的图像。
extension MyModel {
public func updateImage(completionHandler: () -> Void) {
let imagePickerController: UIImagePickerController = UIImagePickerController()
let delegate = UpdateImageHandler(completionHandler: completionHandler)
imagePickerController.delegate = delegate
UIApplication.topViewController()?.present(imagePickerController, animated: true)
}
}
class UpdateImageHandler: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let completionHandler: () -> Void
init(completionHandler: () -> Void) {
self.completionHandler = completionHandler
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
print("HERE")
guard
let image: UIImage = info[.editedImage] as? UIImage,
let data = image.pngData()
else {
completionHandler()
return
}
print("HERE 1")
APIManager.uploadImage(data: data) {
completionHandler()
}
}
}
问题是imagePickerController
方法从未被调用。我希望它能打印HERE
和HERE 1
。但目前它什么也没打印。
我相信这是因为delegate
在调用该方法之前已从内存中释放。
如何解决这个问题,以使delegate
直到完成imagePickerController
(已调用APIManager.uploadImage
CompetitionHandler)后才被释放?
通常我可以将UpdateImageHandler
附加到将包含它的实例上。问题是此代码是扩展名。 MyModel
是一个协议。
当然,这要比这复杂得多,但是我认为这代表了理解该问题所需的最少代码。
答案 0 :(得分:0)
您应该像这样重新编写协议:
protocol MyModel
{
var updateImageHandler: UpdateImageHandler { get set }
func updateImage(completionHandler: @escaping () -> Void)
}
然后扩展中的函数将如下所示:
extension MyModel
{
public mutating func updateImage(completionHandler: @escaping () -> Void)
{
let imagePickerController: UIImagePickerController = UIImagePickerController()
self.updateImageHandler = UpdateImageHandler(completionHandler: completionHandler)
imagePickerController.delegate = updateImageHandler
UIApplication.topViewController()?.present(imagePickerController, animated: true)
}
}
这确保不会释放updateImageHandler
。
答案 1 :(得分:0)
首先,我想说您的updateImage
函数不应成为协议的一部分,仅因为UI内容不会给数据层带来负担。
第二,您面临的问题是由于UpdateImageHandler
一旦所有对它的强引用消失就被释放了。而且在您的代码中,只有一个强引用-delegate
中的updateImage
局部变量,一旦函数返回,该强引用就会被销毁,并带有UpdateImageHandler
。
回到第一段,这样的函数应该存在于UI级别,因为它们涉及ViewController操作。例如,如果您要在另一个视图控制器中执行此操作,则可以在该视图控制器中保存一个强引用,例如通过控制器中的专用属性。
但是,如果您热衷于当前的实现(设计),或者现在无法重构,则可以使用自引用解决方案。请注意,在解决应用程序中的设计问题之前,我强烈建议您将其作为临时解决方案。
因此,关于上述内容,您需要的是UpdateImageHandler
对其自身进行引用:
class UpdateImageHandler {
private(set) var selfRef: UpdateImageHandler?
init(completionHandler: () -> Void) {
self.completionHandler = completionHandler
selfRef = self
}
请注意,我省略了部分代码,以免增加解决方案的噪音。
以上内容可确保UpdateImageHandler
实例在有机会完成其工作之前不会被释放。不幸的是,以上代码创建了一个保留周期,这意味着实例将永远不会被释放。我们也需要修复此问题,我们需要在某个地方打破保留周期,这可以在图像选择器委托方法中完成:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
// do your job
selfRef = nil // this breaks the cycle, allowing deallocation
}
但这还不够,如果用户取消了图像选择器怎么办?您还需要解决这种情况,在这种情况下也要打破循环:
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
selfRef = nil
}
在所有可能的情况下执行此操作很重要,以避免保留周期仍然存在,从而导致内存泄漏的情况。