6

If I want to define a custom UIView subclass that does something when its bounds are set, how do I override the setter? Overriding setBounds seems dangerous since, if I understand correctly, the getter and setter names are not part of the public interface and could change anytime.

Of course, I use class_copyPropertyList to query the runtime for a list of properties defined in the class, then query it for the setter's name, and finally use class_addMethod to add the method, after first getting a reference to the earlier method to use for calling the original version.

All this seems hacky. Is there a clean way to do what I want, which is guaranteed to not break on future OS versions? Thanks.

Kartick Vaddadi
  • 4,818
  • 6
  • 39
  • 55
  • 1
    The bounds property is defined in the UIView header, so your comment "the getter and setter names are not part of the public interface and could change anytime" isn't true: `@property(nonatomic) CGRect bounds` This means the getter/setter is `bounds`/`setBounds:` – Jay Peyer Mar 21 '11 at 17:28
  • Instance variables are declared in the header, too, but that doesn't mean they are part of the public interface :) Would it be accurate to say that http://developer.apple.com/library/ios/#documentation/uikit/reference/UIView_Class/UIView/UIView.html is the public interface? That has no mention of setBounds. – Kartick Vaddadi Mar 22 '11 at 06:25

3 Answers3

19

You can override a setter/getter without having to poke around the internal state of the class (i.e. ivars) -- just call the super's methods in your overrides:

- (Thing *)thing {
    // do your extra stuff here
    //  ...

    return [super thing];
}

- (void)setThing:(Thing *)thing {
    // do your extra stuff here
    //  ...

    [super setThing:thing];
}

An alternative that might suit your problem is to use KVO.

Update

Of course, overriding setBounds might not be necessary. See this question -- layoutSubviews gets called if the frame changes, and changing the bounds causes the frame size to also be updated. So consider putting your code into layoutSubviews.

Final update

Ok, here is why Apple is never going to suddenly declare some @property items as using a non-standard method names (as you fear):

It would break everything in the app store.

Think about it this way: at compile time, any code that accesses a property using dot notation, e.g. the obj.x syntactic sugar, is converted to a message of the form [obj x]. Likewise for properties -- they are converted at compile time into regular methods. So, compiled binaries know nothing about dot notation and properties -- they just call regular selectors. So if Apple released an update to iOS that declared some public properties as having non-standard implementation methods, everything in the app store could break. Everything. There is no fault on your behalf in this scenario if your app broke like the rest -- it would be Apple's fault, not yours.

Community
  • 1
  • 1
occulus
  • 16,959
  • 6
  • 53
  • 76
  • That doesn't address my concern -- what if the setter name changes to setViewBounds in iOS 5? I don't find the name setBounds anywhere on the UIView documentation at http://developer.apple.com/library/ios/#documentation/uikit/reference/UIView_Class/UIView/UIView.html so I'm not convinced I can rely on it. Am I missing something? – Kartick Vaddadi Mar 22 '11 at 06:26
  • In other words, the setter name is part of the internal state of the class. – Kartick Vaddadi Mar 22 '11 at 06:27
  • @kartick-vaddadi The setter name is derived from the property `bounds` which is part of the published API and hence is very very unlikely to change for no reason. The pattern for setter names is `setX` -- this isn't going to change, as absolutely everything would break everywhere, not just your code! I am very confident that the methods `setBounds` and `bounds` are not going to change into something else. – occulus Mar 22 '11 at 15:09
  • @occulus -- My understanding is that the property name 'bounds' is part of the interface, but not the name of the setter using which it is implemented. In fact, the whole point of declaring a property is that people use it rather than the underlying accessors, right? Am I missing something? – Kartick Vaddadi Mar 24 '11 at 06:21
  • The key point here is that declaring a read/write property for ivar 'x' defines behind the scenes two methods for you: x (getter) and setX: (setter). Declaring a property actually creates those two methods for you -- it's a form of syntactic sugar. When you write dot notation such as 'myObject.x = thing', behind the scenes that is translated into a call to 'setX'. This is how Objective C does things: dot notation and @property declarations are a kind of syntactic sugar to help you. Behind the scenes, these things just become methods called 'x' and 'setX', and calls to those methods. – occulus Mar 24 '11 at 09:13
  • You can read about this stuff in various places, like http://macdevelopertips.com/objective-c/objective-c-properties-setters-and-dot-syntax.html, and http://www.iphonedevsdk.com/forum/iphone-sdk-tutorials/7295-getters-setters-properties-newbie.html. – occulus Mar 24 '11 at 09:19
  • @kartick-vaddadi Still not convinced? If however you are happy, please consider accepting an answer :) – occulus Mar 25 '11 at 11:04
  • Ok, have added a final update to my answer. If that doesn't convince you, nothing will :p – occulus Mar 29 '11 at 13:35
  • I finally got it. Thanks for taking the time to explain. Much appreciated. – Kartick Vaddadi Apr 02 '11 at 08:44
2
@property(nonatomic) CGRect bounds; 

is shorthand for

-(CGRect)bounds; 
-(void)setBounds:(CGRect)bounds; 

,

view.bounds = rect; 

is shorthand for

[view setBounds:rect];

,

CGRect rect = view.bounds;

is shorthand for

CGRect rect = [view bounds];

Dot notation and @property declarations are SYNTACTIC SUGAR. They are for shortening code and convenience. Messages and selectors are always always underneath them, and can always be relied on to be a stable, if not the most stable, part of the interface.

Overriding "setBounds:" is a safe way to do this. "setBounds:" is not explicitly named in the public interface, because it is declared as a @property. However the standard is that the setter with "set"-capitalized property name is always created (unless its readonly).

  • Please see my comments above. The @property can be changed to use a different setter name. – Kartick Vaddadi Mar 22 '11 at 06:27
  • Yup, but Apple wouldn't do that, because it would break so many things. They're far more likely to change the internal ivar variable name (which is hidden from you) but leave alone the public property name. – occulus Mar 22 '11 at 15:13
  • @kartick-vaddadi It is part of the interface declaration which is an established contract with the consumers of the library. Its as safe to rely on that as on any other object or function. You might as well worry about every single method name in the entire framework. –  Mar 22 '11 at 20:47
  • @Saltymule, please see this, pasted again for your convenience: My understanding is that the property name 'bounds' is part of the interface, but not the name of the setter using which it is implemented. In fact, the whole point of declaring a property is that people use it rather than the underlying accessors, right? Am I missing something? – Kartick Vaddadi Mar 24 '11 at 06:22
  • A property uses accessors under the 'hood'. Properties are not 'separate' from accessors -- properties just give you a convenient way to have standard accessors generated for you by the compiler! – occulus Mar 24 '11 at 12:48
  • @kartick-vaddadi I guess the only assurance I can give you is that setBounds: has been the accessor name used for versions 2 to 4.3. If you need any further information on property declarations you should refer to page 71 of http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjectiveC/ObjC.pdf –  Mar 24 '11 at 17:13
  • I'm sorry, but we're talking past each other. I've tried to be as clear as I can, but let me try one last time: I know about how accessors work. My question is: how do you know what setter name the accessor maps to? That is, how do you know that when I do view.bounds = blah, it does a setBounds? That is declared in the header file, but it can be changed anytime to something like: @property (setter=setMybounds) CGRect bounds; The property name is not part of the public interface -- it is not documented on apple's website. Everything in the .h isn't part of the interface (instance variables). – Kartick Vaddadi Mar 26 '11 at 06:57
  • @Saltymule, it's bad to rely on things that are not part of the public interface, just because they happened to work that way so far. – Kartick Vaddadi Mar 26 '11 at 07:02
0

There is an NSViewBoundsDidChange notification in OSX. That does appear to be the better solution, although fears of overriding the @property methods seem unfounded. I happened on this post with the same question about overriding the accessors, and you have convinced me to prefer notifications.