0

I am creating an app that, in the end of one cycle, generates a QR Code and then creates a PDF file. After running the app several times (tested for almost 5 days) the app stopped doing its other required functionalities. I found out that every time the app generates QR and makes PDF, the memory graph grows higher and higher in successive cycles of app. Debug memory graph indicates 3 memory leaks: one at viewDidAppear event, another at createPDF method, and third at generateQrCode method. I am deleting the PDF file every time user starts a new cycle. I don't know how to handle these memory leaks. Can someone help? Here is the code:

    let html = ""

    override func viewDidAppear(_ animated: Bool) {
        createPDF()
        return
    }

    func createPDF() {
        html = "<div style='display: flex; justify-content: center;'></div>"

        generateQrCode(arg: true, completion: { (success) -> Void in
            print("QR Code Generated")
            let image1 = captureView()
            let imageData1 = image1.pngData() ?? nil
            let base64String1 = imageData1?.base64EncodedString() ?? ""

            if success {
                self.html += "<html><body><div style='text-align: center'><p><br></p><p><b><img width='50%' height='20%' src='data:image/png;base64,\(String(describing: base64String1))'/><p><br></p><p><b></b></p></div></body></html>"
                weak var webView = WKWebView(frame: self.view.frame)
                self.view.addSubview(webView!)
                webView!.navigationDelegate = self
                webView!.loadHTMLString(html, baseURL: nil)
                webView!.isUserInteractionEnabled = false
            } else {
                print("false")
            }
        })
    }

    func generateQrCode(arg: Bool, completion: (Bool) -> ()){
        let data = self.dataToQr.data(using: .ascii, allowLossyConversion: false)
        let filter = CIFilter(name: "CIQRCodeGenerator")
        filter?.setValue(data, forKey: "inputMessage")
        let img = UIImage(ciImage: (filter?.outputImage)!)
        self.qrImageView.image = img
        completion(true)
    }

    func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        let render = UIPrintPageRenderer()
        render.addPrintFormatter(webView.viewPrintFormatter(), startingAtPageAt: 0)

        let page = CGRect(x: 0, y: 0, width: 595.2, height: 841.8) // A4, 72 dpi
        let printable = page.insetBy(dx: 0, dy: 0)

        render.setValue(NSValue(cgRect: page), forKey: "paperRect")
        render.setValue(NSValue(cgRect: printable), forKey: "printableRect")

        let pdfData = NSMutableData()
        UIGraphicsBeginPDFContextToData(pdfData, .zero, nil)

        for i in 1...render.numberOfPages {
            UIGraphicsBeginPDFPage();
            let bounds = UIGraphicsGetPDFContextBounds()
            render.drawPage(at: i - 1, in: bounds)
        }

        UIGraphicsEndPDFContext();

        let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
        pdfData.write(toFile: "\(documentsPath)/\(UserDefaults.standard.string(forKey: "yourName")!)_\(UserDefaults.standard.string(forKey: "gameName")!)_Kapper.pdf", atomically: true)
        print("\(documentsPath)/apper.pdf")
        webView.removeFromSuperview()
    }
Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
Mumtaz Hussain
  • 925
  • 9
  • 23

1 Answers1

2

From what I've seen of the code, your memory leak seems to be caused by the strong reference you have to the self in this closure

completion: { (success) -> Void in
            print("QR Code Generated")
            let image1 = captureView()
            let imageData1 = image1.pngData() ?? nil
            let base64String1 = imageData1?.base64EncodedString() ?? ""

            if success {
                self.html += "<html><body><div style='text-align: center'><p><br></p><p><b><img width='50%' height='20%' src='data:image/png;base64,\(String(describing: base64String1))'/><p><br></p><p><b></b></p></div></body></html>"
                weak var webView = WKWebView(frame: self.view.frame)
                self.view.addSubview(webView!)
                webView!.navigationDelegate = self
                webView!.loadHTMLString(html, baseURL: nil)
                webView!.isUserInteractionEnabled = false
            } else {
                print("false")
            }
        }

Here's what to do:

  1. Create your webView in your viewDidLoad method for the controller, not inside the closure or make it an IBOutlet on your storyboard. Then make the self reference in the closure a weak one like below.
generateQrCode(arg: true, completion: { [weak self] (success) -> Void in
            print("QR Code Generated")
            let image1 = captureView()
            let imageData1 = image1.pngData() ?? nil
            let base64String1 = imageData1?.base64EncodedString() ?? ""

            if success {
                self?.html += "<html><body><div style='text-align: center'><p><br></p><p><b><img width='50%' height='20%' src='data:image/png;base64,\(String(describing: base64String1))'/><p><br></p><p><b></b></p></div></body></html>"
                self?.webView.navigationDelegate = self
                self?.webView.loadHTMLString(html, baseURL: nil)
                self?.webView.isUserInteractionEnabled = false
            } else {
                print("false")
            }
        })

This should work. Let me know how it turns out.

For more context, check out these links: How to Correctly handle Weak Self in Swift Blocks with Arguments

Block retain cycles in Swift?

https://medium.com/@sergueivinnitskii/most-common-memory-leak-trap-in-swift-4565dbae5445

P.S: I haven't answered a StackOverflow question in years. :)

fdamilola
  • 36
  • 3
  • Thanks your answer makes a lot of sense and it is great. The memory leaks detected in debug memory graph is showing those leaks no more. But sadly though the memory graph is still growing. A new debug memory graph points to this line weak var webView = WKWebView() and this time I've made it a class variable. – Mumtaz Hussain Feb 07 '19 at 10:37