Strongly referenced variable may cause memory issues - ios

I have been programming in Swift for a couple months now. Recently, I have focused more on concepts of how Swift as a language works.
Hence, recently while reading apple documentation on Automatic Reference Counting(ARC), I came across the following lines:
This one on top:
In most cases, this means that memory management “just works” in Swift, and you do not need to think about memory management yourself. ARC automatically frees up the memory used by class instances when those instances are no longer needed.
And in the next paragraph, the following:
To make this possible, whenever you assign a class instance to a property, constant, or variable, that property, constant, or variable makes a strong reference to the instance. The reference is called a “strong“ reference because it keeps a firm hold on that instance, and does not allow it to be deallocated for as long as that strong reference remains.
I am a little confused as to what is the dynamics of the situation. I have noted while using storyboards, that you set reference to weak, hence the class looks like this, also what I would call case 1:
Case 1
class SomeClass : UIViewController {
#IBOutlet weak var nameLabel : UILabel!
override func viewDidLoad() {
nameLabel.text = "something."
}
}
Here, the label has one-to-one weak reference with the ViewController, and as soon as the Controller is changed, reference is broken (memory dealloc) since it is weak. Hence, no issues related to the memory.
Pardon me if the above statement is wrong or loosely held. I would be glad if someone confirms my assumption or counters it.
My question is about the second case however, where I do not use storyboards and class looks like below:
Case 2
class SomeClass : UIViewController {
var nameLabel : UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
view.addSubView(nameLabel)
// view.addConstraints...
}
}
For the above case, My assumption is that the ViewController has one-on-one strong reference with the label, and the view inside ViewController also has strong reference with the label.. If the class is changed/ label is removed from subview.. then I think the memory would not be deallocated. Or at least the view controller will maintain a strong reference to the label (as per the docs.)
I confirmed this by removing label from view's subviews and printing out the label (It gave me an instance of UILabel with frame that was at 0 origin and 0 size.) hence an instance that isn't nil.
The only thing I could gather from this was that although the label was removed from UIView, it still maintained a strong reference with the controller, hence permanent state in memory. Am I right?
If this is the case. How should I prevent my code from having such memory issues? The bigger problem is that if I declare my variable like so, I get a nil while adding it as a subview of main view in controller.
weak var nameLabel : UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
If declaring variables like in the second case can cause permanent strong references how should I declare them instead to not have memory issues?
So to conclude, my question is:
In cases where no storyboard outlets are used, and variables are strongly referenced to the view controller, will these references cause memory issues?
If so, what code declaration practice must I follow?
If not so, please provide thoughtful arguments with valid explanations to counter it.
Again, pardon me if I am incorrect anywhere.
Thank you in advance.

The only thing I could gather from this was that although the label was removed from UIView, it still maintained a strong reference with the controller, hence permanent state in memory. Am I right?
No. There's no big issue here.
The label has no strong reference to the view controller — if it did, that would be a retain cycle and would cause both the label and the view controller to leak. For this very reason, a view should never keep a strong reference to its view controller.
Here, however, it's the other way around: the view controller has a strong reference to the label. That's fine. It's true that the label therefore stays in existence after it has been removed from its superview. But that might not be bad. In many cases, it's good! For example, suppose you intend to put the label back into the interface later; you will need to have retained it.
If you are sure you won't need to keep the label around later, then simply use an Optional wrapping a UILabel as your instance property. That way, you can assign nil to the label instance property when you're done with it, and the label will go out of existence.
But in any case there is no leak here and you should just stop worrying. When the view controller goes out of existence, the label will go out of existence too. The label lived longer than it had to, but that's tiny and unimportant on the grand scale of things.

create the label when you need ,then call addsubView to make an strong reference to it and make an weak reference to your member var like this:
class ViewController: UIViewController {
weak var label : UILabel?
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
view.addSubview(label)
self.label = label
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
print(label)
//click first Optional(<UILabel: 0x7fb562c3f260; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7fb562c11c70>>)
//click second nil
label?.removeFromSuperview()
}
}
anyway while the viewcontroller release ,the label will be release and view.subview will be release too.
Demo
i wrote an easy demo make the ViewControllerTest to be the rootviewcontroller
class Test{
weak var label:UILabel?
static let instance = Test()
}
class ViewControllerTest: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let item = UIBarButtonItem(title: "Test", style: .Plain, target: self, action: #selector(self.test))
self.navigationItem.rightBarButtonItem = item
}
func test(){
print(Test.instance.label)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let vc = ViewController()
self.navigationController?.pushViewController(vc, animated: true)
print(vc.nameLabel)
let test = Test.instance
test.label = vc.nameLabel
}
}
class ViewController: UIViewController {
var nameLabel : UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.whiteColor()
view.addSubview(nameLabel)
let item = UIBarButtonItem(title: "Test", style: .Plain, target: self, action: #selector(self.test))
self.navigationItem.rightBarButtonItem = item
}
func test(){
print(Test.instance.label)
}
}

I don't think strongly referenced variables to view controller cause any memory issues.
Normally views are deallocated before deallocating their view controller. For example, in in your code, when deallocating the view, ARC decreases the counter pointing to namelabel, so it passes from 2 to 1. Then, when deallocating the view controller it decreases the counter again, from 1 to 0. Once there are 0 references pointing to namelabel its removed.

A weak reference is a reference that does not keep a strong hold on
the instance it refers to, and so does not stop ARC from disposing of
the referenced instance. This behavior prevents the reference from
becoming part of a strong reference cycle. You indicate a weak
reference by placing the weak keyword before a property or variable
declaration
> Weak references must be declared as variables, to indicate that their
value can change at runtime. A weak reference cannot be declared as a
constant.
Because a weak reference does not keep a strong hold on the instance
it refers to, it is possible for that instance to be deallocated while
the weak reference is still referring to it. Therefore, ARC
automatically sets a weak reference to nil when the instance that it
refers to is deallocated. Because weak references need to allow nil as
their value, they always have an optional type. You can check for the
existence of a value in the weak reference, just like any other
optional value, and you will never end up with a reference to an
invalid instance that no longer exists
Source: Apple docs
A weak reference is just a pointer to an object that doesn't protect the object from being deallocated by ARC. While strong references increase the retain count of an object by 1, weak references do not. In addition, weak references zero out the pointer to your object when it successfully deallocates. This ensures that when you access a weak reference, it will either be a valid object, or nil.
Hope can help you to understand better a weak reference, be it related to a storyboard item or created programmatically.

I always explain it to my students like this.
With a strong reference, you can see a value, and you have a lasso around it. You have a say in whether the value remains alive.
With a weak reference, you can see it, but there's no lasso. You have no say in whether the value lives or not.

For your situation to avoid occurrence of Memory leak for a second. You can go with Matt answer.
For better understanding, create a custom UILabel class under MRC flag in build phases->Complie sources.
In custom class, override retain and release method. Put breakpoints on them.
Use that custom UILabel class in your view controller with ARC flag ON. Go with matt answer or use below optional declaration of UILabel.
import UIKit
class ViewController: UIViewController {
var label:UILabel? = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.text = "something"
return label
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(self.label!)
//namelabel goes out of scope when method exists.
//self.view has 1+ ref of self.label
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.label?.removeFromSuperview()//-1 ref of self.label
self.label = nil
print(self.label)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You will have clear picture of how ARC works and why weak ref of UILabel causes crash while adding to UIView.

Related

Strong references and UIView memory issues

I'm dealing with some deallocation issue and perhaps strong or circular referencing that can't figure out. I have three UIViews instantiating like below:
There is one main ViewController which I have added a UIView inside it in storyboard and the UIView has a weak outlet inside the class like:
class ViewController : UIViewController {
//MARK: - outlets
#IBOutlet weak var firstView: FirstUiview!
}
second UIView is added as a subview to the first view programmatically like:
class FirstUiview : UIView {
//creating an instance of secondUiView
lazy var mySecondView: SecondViewClass = {
let dv = SecondViewClass()
dv.backgroundColor = UIColor.red
return dv
}()
//sometime later by clicking on a button
self.addSubview(mySecondView)
//a button will be tapped to remove mySecondView;
//later will be called at some point upon tapping:
func removingSecondViewByTapping() {
if mySecondView.isDescendant(of: self) {
mySecondView.removeFromSuperview()
}
}
}
Now the SecondViewClass is :
class SecondViewClass : UIView {
//in this class I create bunch of uiview objects like below:
lazy var aView : UIView = {
let hl = UIView()
hl.tag = 0
hl.backgroundColor = UIColor.lightGray
return hl
}()
self.addSubview(aView) //... this goes on and I add other similar views the same way.
//creating an instance of thirdView
var let thirdView = UIView()
self.addSubview(thirdView)
}
Now if user taps the button to remove mySecondView and then add it again at some other time (still in the same ViewController) I expect all the subviews of mySecondView to have been released and gone but they are all there. I would appreciate it a lot if someone can point it to me where am I keeping a strong reference or if there is a circular referencing issue? or perhaps something else?
You have two strong references to your views, your custom property and the view hierarchy reference established when you call addSubview. When you remove the view from the view hierarchy, your class, itself, still has its strong reference to it.
You could solve this by making your reference optional, and when you call removeFromSuperview, also manually set your reference to nil. Or, perhaps easier, you might resolve this by using weak references, letting the view hierarchy maintain the strong references for you. And because your custom property is weak, when you remove it from the view hierarchy (thus eliminating the only strong reference to it), your weak reference will automatically become nil:
class FirstView: UIView {
weak var secondView: SecondView? // note the `weak` reference, which is obviously an optional
//sometime later by clicking on a button
func doSomething() {
let subview = SecondView()
subview.backgroundColor = .red
self.addSubview(subview)
secondView = subview
}
// a button will be tapped to remove secondView;
// later will be called at some point upon tapping ...
func removingSecondViewByTapping() {
secondView?.removeFromSuperview()
}
}

When using Computed variable and Snapkit: No common superview between views

So here's the thing, I'm declaring a property like this:
var aNameLabel: UILabel {
guard let foo = Applicant.sharedInstance.realName else {
return UILabel(text: "获取姓名失败", color: .whiteColor())
}
return UILabel(text: foo, color: .whiteColor())
}
And when I try to add constraint to the aNameLabel after I did someView.addSubView(aNameLabel), the app would crash every time at this constraint-adding thing, and says No common superview between views
However, when I change the variable into a let constant like this:
let aNameLabel = UILabel(text: "Allen", color: .whiteColor())
The constraint will be added with no complaint. Somebody can help me with this?
UPDATE
With the help of #par , I've changed my code into this:
var aNameLabel: UILabel = {
guard let foo = Applicant.sharedInstance.realName else {
return UILabel(text: "BAD", color: .whiteColor())
}
return UILabel(text: foo, color: .whiteColor())
}()
And then the aNameLabel would always be assigned with value "BAD", while actually my guard let is successful. How do I fix this?
The problem is that you are creating a new UILabel every time you access the aNameLabel variable (a computed property function runs every time you access it). Presumably you are doing the same thing for the superview of this view (when you access someView in someView.addSubview() in your example above). If so, that's why there's no common superview and you are crashing.
You should create only one instance of each UIView used by your view controller, so creating a variable as a constant as you've shown is a great approach, or you can use a closure-initializer pattern like so:
var aNameLabel: UILabel = {
return UILabel(...)
}()
Notice in the above example the parentheses after the closing brace. Because it's a closure-initializer it will only be called once just like a let constant.
Often a UIView created with let isn't appropriate because constant properties need to be initialized before init() returns, and if you're creating views in a view controller you won't have a chance to add views until loadView() is called. In this case, declare your UIViews as implicitly-unwrapped optionals. They will be set to nil by init() which meets the initialization requirement, then you can set them to actual views later when viewDidLoad() is called, for example:
class MyViewController: UIViewController {
var someSubview: UIView!
override func viewDidLoad() {
super.viewDidLoad()
someSubview = UIView()
view.addSubview(someSubview)
// now set constraints with SnapKit
}
}

UISliders and UILabels and viewControllers

How would I be able to increase/decrease the size of my UILabel by using UISlider which is in a different viewController?
I have a viewController1 that has the UILabel1 and I have viewController2 which has a UISlider. With the UISlider I have another label,UILabel2, just to see how big the text will be. I want UILabel1 to increase/decrease also instead of just one label to increase/decrease.
The code being used for UISLider is,
#IBOutlet weak var label: UILabel!
#IBOutlet weak var slider: UISlider!
#IBAction func sizeChanged(sender: UISlider) {
let senderValue = CGFloat(sender.value)
label.font = UIFont(name: label.font.fontName, size: senderValue)
}
This code with UILabel is for viewController2 and I want to change the size of another UILabel thats in viewController1.
This is viewController1:
import UIKit
class ViewController1: ViewController {
#IBOutlet weak var label1: UILabel!
#IBOutlet weak var scrollView1: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
scrollView1.contentSize.height = 5000
scrollView1.contentSize.width = 375
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
This is viewController2:
#IBOutlet weak var label: UILabel!
#IBOutlet weak var slider: UISlider!
#IBAction func sizeChanged(sender: UISlider) {
let senderValue = CGFloat(sender.value)
label.font = UIFont(name: label.font.fontName, size: senderValue)
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
Any help would be great.
In computer programming, when something seems difficult or complicated, you should look for ways to break it down into smaller problems that are easier to solve. Yours is a fine example. You asked:
How would I be able to increase/decrease the size of my UILabel by using UISlider which is in a different viewController?
So, what are the steps that anyone would need to perform to make this happen? There are basically three steps here:
Get a slider's value.
Send a value from one view controller to another.
Use a value to set the size of a label.
How do I get a slider's value? This is straightforward. Set the slider's target to the view controller that manages it and its action to some action in the view controller. That method will be called when the slider changes. The action will look like this:
#IBAction func sliderValueChanged(sender: UISlider) {
let value = sender.value
// do something with the value
}
How do I send data from one view controller to another? There are lots of questions on SO that cover this. Of those, Passing Data Between View Controllers is perhaps the canonical one. There are lots of options, including:
Have one view controller call a method in the other. If the view controller with the slider has a reference to the one with the label, it only has to call some method when the slider changes to pass the new value to the other controller. Or, maybe it's the view controller with the label that has a reference to the one with the slider, which is pretty typical if the slider's controller is created by the label's controller. In that case, the label's controller can call a method to retrieve the value.
Broadcast the data to anyone who is listening using notifications. When the slider changes, it's action can post a notification with the new value. Any object, including the controller with the label, can listen for that notification and act on it.
Use a proper data model. The MVC (model-view-controller) paradigm is big in Cocoa, and if your app is anything beyond trivial it should have its own data model. That model may be a reasonable place to store the slider's latest setting, and the controller with the label can read it from there when its view appears.
Stash the value somewhere. Global variables are a short path to a badly design application, but their simplicity is appealing to beginners. A better choice may be the defaults system, which at least lets the value persist when the app quits.
So, lots of options there. Forget about the slider and the label and think about how the view controllers in your app should communicate with each other. Once you figure that out, the slider setting is just one more thing that they have to say to each other. The style you choose will tell you what to put in the action method above in place of the comment.
How do I set the size of a label? It's a little unclear what you mean by setting the size. Do you want to change the font size, or the width of the label, or the width and height? In any case, there are accessors for all the properties of a label that you might want to set, so check out the docs. When the label's view controller gets a new value via one of the methods above, it should update the appropriate property of the label. You typically connect the label to an IBOutlet property in the view controller to give the controller easy access to the label.
I think, you can look at NSNotificationCenter's functionality, especially at NSNotification's userInfo:
parameter. You can pass your slider's value to userInfo from first VC and then listen to this notification in second VC.
Great example of this method in Objective-C:
https://stackoverflow.com/a/7896761

Swift setting variable inline or in function?

What is the Swift standard for setting a variable you already know the value for? Here are the 2 different ways I'm thinking of.
Option 1: Declaring the variable in the class and then setting it in the ViewDidLoad method
class ViewController: UIViewController {
var refreshControl: UIRefreshControl!
var sampleString: String!
override func viewDidLoad() {
super.viewDidLoad()
refreshControl = UIRefreshControl()
sampleString = "Hello"
}
}
Option 2: Declaring the variable in the class and setting it inline
class ViewController2: UIViewController {
var refreshControl = UIRefreshControl()
var sampleString = "Hello"
override func viewDidLoad() {
super.viewDidLoad()
}
}
Which is the preferred way to do this in Swift? Thanks in advance!
First of all, you have two fundamentally different types in your two examples. In the first example, the type is an implicitly unwrapped optional String (i.e., String!), which means it can accept the nil value. In the second example, it is just String. If the value does not need to be nil assignable, the second option is better.
With regard to your actual question. I would say the second option is preferable, as you initialize the value earlier and there is no chance that you will use it before it is initialized. This would be equivalently good to declaring the type as String and deferring the initialization to an init method.
The viewDidLoad method is only useful for UIViewController instances, and doesn't get invoked until the view is loaded (which typically is during presentation). Waiting to initialize a value until then is probably not preferred and wouldn't be useful in objects that don't subclass UIViewController.

swift: handling unwrapped optional in viewDidLoad

I have actually some troubles handling unwrapped optional constant on UIViewController.
Because UI components exist only after viewDidLoad calls, It seems I can't use the 'let' constant modifier on my constant variables who need GUI dependancy.
Here is an example:
class ViewController: UIViewController {
#IBOutlet weak var blueSquare: UIView!
var animator:UIDynamicAnimator!
required init(coder aDecoder: NSCoder) {
// If animator was constant, it should be initialized here.
// But blueSquare is not initialized at this time, so I can't
// call UIDynamicAnimator(referenceView: blueSquare)
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
// At this time, the initialisation is ok. But animator is now
// actually modifiable
self.animator = UIDynamicAnimator(referenceView: blueSquare)
}
}
As you can see, animator is written as if it can be modifiable. The intend isn't.
My question is: is there any nice pattern who can put 'animator' as constant ? I didn't find any documentation dealing with viewDidLoad and constants with GUI dependency paradigm.
That is why you should leverage optionals.
When you declare:
var animator:UIDynamicAnimator!
You make the compiler believe that animator doesn't have to be initialized before calling init(coder). That's a dangerous game you are playing here. Instead you'd much rather use:
var animator:UIDynamicAnimator?
so using the var before it is initialized is reported as an error. Then you can set it up in viewDidLoad and things are back to where they should be.
You are right about assuming that blueSquare can be used only later in the controller's lifecycle and this has nothing to do with Swift syntax; it is a framework constraint. Therefore you need to have animator declared as a dangling reference till it can be inited to something useful. That's what optional provides to you.

Resources