Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memory leaks when assigning text to UILabel (iOS, Swift 4, Xcode 9)

I've been working on a new app with no storyboard. All went fine until I tested my application with Instruments: it leaked every time I assigned a string to a label. When I worked with a storyboard, I didn't have leaks like that.

I have read the following resources to find the answer:

  • UILabel memory leak?

  • memory leak in cell with UILabel with ARC

  • Instruments show "_NSContiguousstring" memory leak when scrolling UITableView

  • Potential Leak, assigning NSString-Property to UILabel

  • iOS: Debugging memory leaks for UILabel in swift

  • UILabel and memory leak
  • Generating UILabel inside a UIView / Memory leak

The most popular opinion is that is an Instruments bug, but it looks like a too obvious approach to me.

The leak reproduces in an empty application. In the root view controller:

class ViewController: UIViewController {

var label: UILabel?

override func viewDidLoad() {
    super.viewDidLoad()

    label = UILabel()
    view.addSubview(label!)
    var textForLabel: String? = "Hello"
    label?.text = textForLabel

    //attempt to free the memory
    textForLabel = nil
    label = nil

    //EDIT: added after @J.Doe and @Sh-Khan answers, but it's still leaking
    label.removeFromSuperview()

   }
}

While testing this app in Instruments on a real device (iPhone SE 11.2) I see the following:

enter image description here

When I click on _NSContiguousString, I see that memory leak appears in [UILabel setText:].

enter image description here

I tried to set label as weak, but then it becomes nil when I try to add it as a subview.

So, my questions are:

  • how I can eliminate this memory leak now and in the future?
  • should I create UI elements only in .xib/.storyboard files for that reason?

I am new to iOS development, so I think that I'm missing something obvious. I will highly appreciate any help or advice.

EDIT: According to @Sh-Khan and @J.Doe answers (thank you guys so much!), I added label.removeFromSuperview(), but there is still a leak.

EDIT2: With @J.Doe help, I learned that UILabel gets released from memory by calling removeFromSuperview and setting it to nil afterwards. The memory leak in Instruments remained, but I mark his answer accepted because that's what I wanted to know.

PS: After reading about NSString retain count I think the reason of memory leak might be the fact I am using a string literal that cannot be released, according to the discussion.

like image 534
joliejuly Avatar asked May 12 '18 13:05

joliejuly


2 Answers

Maybe I am wrong, but I think this:

Weak will not increase the reference counter. Therefore, assigning a object label to a weak var label, does not make sense. This is because weak var label will be nil, because the object you created does not have any reference (and therefore it will deinitialize)

Let's count how many reference you have in your code to your created object Label.

label = UILabel() // 1
view.addSubview(label!) // 2
var textForLabel: String? = "Hello"
label?.text = textForLabel

//attempt to free the memory
textForLabel = nil
label = nil // 1

You have 1 reference left in your view to your object Label. Before you do label = nil, call label?.removeFromSuperview(). I think than you have 0 references -> it will deinit.

edit:

Add below subclass of UILabel into your code:

class MyLabel: UILabel {
    deinit {
        print("I am gone!")
    }
}

Change var label: UILabel? to var label: MyLabel?

And

label = UILabel() to label = MyLabel()

And check to logs. Do you see the print "I am gone!"?

Edit2: this prints "I am gone!" in a empty project with this only as code:

import UIKit

class ViewController: UIViewController {

    var label: MyLabel?

    override func viewDidLoad() {
        super.viewDidLoad()

        label = MyLabel()
        view.addSubview(label!)
        let textForLabel: String? = "Hello"
        label?.text = textForLabel

        //EDIT: added after @J.Doe and @Sh-Khan answers, but it's still leaking
        label?.removeFromSuperview()
        label = nil


    }
}

class MyLabel: UILabel {
    deinit {
        print("I am gone!")
    }
}
like image 53
J. Doe Avatar answered Nov 10 '22 09:11

J. Doe


First setting

textForLabel = nil

won't remove or make the label text nil as the label already took a copy of it

second setting

label = nil 

is not enough you have to

label.removeFromSuperview()
like image 2
Sh_Khan Avatar answered Nov 10 '22 11:11

Sh_Khan