迅捷强代表参考

时间:2019-02-08 02:43:15

标签: ios swift

我有以下代码可用于更新模型的图像。

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方法从未被调用。我希望它能打印HEREHERE 1。但目前它什么也没打印。

我相信这是因为delegate在调用该方法之前已从内存中释放。

如何解决这个问题,以使delegate直到完成imagePickerController(已调用APIManager.uploadImage CompetitionHandler)后才被释放?

通常我可以将UpdateImageHandler附加到将包含它的实例上。问题是此代码是扩展名。 MyModel是一个协议。

当然,这要比这复杂得多,但是我认为这代表了理解该问题所需的最少代码。

2 个答案:

答案 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
}

在所有可能的情况下执行此操作很重要,以避免保留周期仍然存在,从而导致内存泄漏的情况。