0

I have a rather basic question where I'm actually not sure whether it's a bug in UIKit or intended behaviour.

It seems to be common agreement when declaring view properties in a UIViewController which should be added to the view controller's subviews and thus displayed on the screen, that these properties should be weak.

The rationale behind this weak declaration makes sense because the view controller already owns the subview through its subviews property, so another strong reference wouldn't add any value here. This is also confirmed in many Stackoverflow posts, e.g. this one.

I am now running into an issue with UIButton. When I want to add a button programmatically and first declare it as a property of a UIViewController and then call [self.view addSubview:self.someButton], the button only shows up when it's declared as strong, but not when being declared as weak.

Here is the minimal example code to comprehend my issue:

@interface ButtonTestViewController ()
// only works with strong! 
// when declared weak, no button appears on the screen and the
// logging output doesn't contain the button as a subview...
@property (nonatomic, strong) UIButton *someButton;
@end

@implementation ButtonTestViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self.someButton setCenter:self.view.center];
    [self.view addSubview:self.someButton];
    NSLog(@"subviews: %@", self.view.subviews);
}

- (UIButton *)someButton
{
    if (!_someButton) {
        UIButton *someButton = [UIButton buttonWithType:UIButtonTypeSystem];
        CGRect someButtonFrame = CGRectMake(0.0, 0.0, 100.0, 44.0);
        someButton.frame = someButtonFrame;
        [someButton setTitle:@"Do something" forState:UIControlStateNormal];
        [someButton addTarget:self action:@selector(someButtonPressed) forControlEvents:UIControlEventTouchUpInside];
        _someButton = someButton;
    }
    return _someButton;
}

- (void)someButtonPressed
{
    NSLog(@"button pressed...");
}

@end

I also set up a small gist, where I also added another UI element (a UITextField) next to the UIButton for comparison. The UITextField is also shown when being declared weak. So, is this a bug in UIKit or is there actually a reason why UIButtons can't be declared weak?

Community
  • 1
  • 1
nburk
  • 22,409
  • 18
  • 87
  • 132

2 Answers2

2

The fact that outlets are create as weak is mainly for 2 reason:

  • There is no need to create them as strong since they arre already owned by their superview
  • Once upon a time there was the common use to nil them in -viewDidUnload to avoid the creation of zombies after a memory warning, now you get it for free. If the view is gone you already have them at nil

Reatain cycle are not created in this way, it's an object A that retains B that retains A that creates a retain cycle, the button itself retains no-one.
You issue is due to the fact that if you add the button instance directly to a weak variable, before adding it to someone that keeps the ownership, it will be released right after.
You can create your button as a weak but you should add the value in another way:

  • create simple local variable such as UIButton *someButton = [UIButton whatever]
  • add it as a subview to your view
  • now you can safely pass the reference to your weak variable since the button is owned by the view
Andrea
  • 26,120
  • 10
  • 85
  • 131
  • ahh yes that definitely makes sense! but how can it be explained then that the same approach does work for other ui elements such as `UITextField`? why doesn't this one get set to nil before being added to the `subviews` if declared `weak`? – nburk Apr 08 '16 at 09:46
  • 1
    I'm am a strong fan of the Occam razor, it always the simple hypothesis to win. I've seen the gist and I think UItextField should behave in the same way as the UIButton. I have 3 hypothesis: The first is telling me that you snippet is not exactly what you have tried, the second that there is a bug in UIKit, the third that the "nilling"depends on the run loop at runtime so sooner or later will be nil – Andrea Apr 08 '16 at 09:56
  • great reply! I can almost certainly decline your hypothesis 1 & 3: 1) the code is 100% identical to the one I have setup in Xcode, so that can't be it (will be possible to just copy, paste and run in a vanilla Xcode project). 3) I have come across this scenario in a different project and created the test project to verify... so I don't think this behaviour is non-deterministic but actually some kind of inconsistency (I'd hesitate to even call it a bug) in `UIKit` – nburk Apr 08 '16 at 10:01
  • Unfortunately I can't test it right now, but I'm curious to dig into the issue, if I remember I will try at home. Ciao! :-) – Andrea Apr 08 '16 at 10:11
1

You can't make subviews weak when you don't use the Interface Builder. You have to use strong in your case. Otherwise the button is released before it is added to the view.

dasdom
  • 13,975
  • 2
  • 47
  • 58