Travis Griggs
Travis Griggs

Reputation: 22252

Confused by behavior of property setter

(this question probably needs a more descriptive title, feel free to improve it)

I have a UIView subclass with a property:

@property (weak, nonatomic) UILabel *label;

In an initialize method I have:

[self addSubview: (self.label = [UILabel new])];

I like the terseness of this, but I have questions how it works.

For one, I get a warning:

Assigning retained object to weak property; object will be released after assignment

Warning aside, it actually seems to work. Is that because before the release machinery can run, the addSubview: re-retains it?

If I understand correctly, the self.label = ... code is just sugar for a [self setLabel: ...]. But if I override the property access with my own setLabel: implementation, its signature will be

- (void) setLabel: (UILabel*) label;

So the return value is void. But it's being fed into the the addSubview: send and working? So how does that work?

Update

One of the things that frustrates doing a one liner here, is that Objective-C tend to eschew returning useful information from methods like addSubview: which is divergent from the Smalltalk influence which spawned Objective-C in the first place. In Smalltalk, it would be expected/common for the addSubview: method to return the added object. If this was the case, one could write these expressions as:

self.label = [self addSubview: [UILabel new]];

This would allow the strong/weak semantics to be happy. The addSubview: would do the strong retain before the it got to the weak label setter.

I fight this problem with both subviews and recognizers, so I thought I'd be clever, and write a category to do something like that (I inverted the receiver/argument so it would be easily distinguishable from an alternate addSubview: signature).

@interface UIView (UIView_Adding)
    - (UIView*) addedToView: (UIView*) superview;
@end

@interface UIGestureRecognizer (UIView_Adding)
    - (UIGestureRecognizer*) addedToView: (UIView*) view;
@end

@implementation UIView (UIView_Adding)
- (UIView*) addedToView: (UIView*) superview {
    [superview addSubview: self];
    return self;
}
@end

@implementation UIGestureRecognizer (UIView_Adding) 
- (UIGestureRecognizer*) addedToView: (UIView*) view {
    [view addGestureRecognizer: self];
    return self;
}
@end

Unfortunately, this just invokes the Law of Conservation of Ugly. I get rid of one kind of warning and get another. With that category included, I can now write:

self.label = [[UILabel new] addedToView: self];

But this generates a warning that self.label is meant to hold a specific subclass of UIView, namely a UILabel and the return type of addedToView: is `UIView'. I'm not aware of an Objective-C pseudo type which means be the right darn subtype of this known super type to make property types happy. :(

Final Update

I discovered the instancetype type. By changing my category method signatures to return these types, everything just works. I'm new enough to instancetype to not know if I'm abusing something here, but I'm thrilled it worked.

Upvotes: 2

Views: 128

Answers (1)

rmaddy
rmaddy

Reputation: 318824

The warning comes from:

self.label = [UILabel new]

since your label property is weak.

You are correct that it ends up working because the label is retained by the call to addSubview:.

But if the label is ever removed from its superview, the label property will become nil and the label will be lost. So if there is any chance that the label will be removed but you want to keep a reference to the label (maybe to add it back later), then change your property to be strong.

You are also correct that self.label = is really just [self setLabel:]. The reason that this can be passed to addSubview: is due to the fact that an expression such as x = y has a value equal to the assignment.

So:

[self addSubview: (self.label = [UILabel new])];

if equivalent to:

UIView *view = (self.label = [UILabel new]);
[self addSubview:view];

Upvotes: 2

Related Questions