Re: Why so many public properties all up in my grizzle?
Re: Why so many public properties all up in my grizzle?
- Subject: Re: Why so many public properties all up in my grizzle?
- From: William Squires <email@hidden>
- Date: Mon, 19 Mar 2012 19:28:35 -0500
On Mar 16, 2012, at 4:00 PM, Brian Lambert wrote:
> I’ve been developing iOS applications full-time for about 6 months now and
> I love it. I feel pretty strong on the platform now.
>
> I have a lingering question about something that’s really been bugging the
> heck out of me, though, that I thought I would ask the list and get some
> feedback on.
>
> It seems to be the case that when people are developing a UIViewController
> subclass or a UIView subclass that they expose *all* the implementation
> details of that class through public properties vs. using ivars.
>
> Here’s an example. For the purpose of this post, I’ve wrote a simple iPhone
> app with one view. The link below is a screen shot of it:
>
> https://s3.amazonaws.com/Softwarenerd/MyApp.jpg
>
> For now, I used Interface Builder to generate the UI. In doing so, I wound
> up with:
>
> #import <UIKit/UIKit.h>
>
> // MyViewController interface.
> @interface MyViewController : UIViewController
>
> // Properties.
> @property (weak, nonatomic) IBOutlet UILabel * labelMyLabel;
>
> // buttonDoItTouchUpInside action.
> - (IBAction)buttonDoItTouchUpInside:(id)sender;
>
> @end
>
> This means that my UILabel called labelMyLabel is publicly available.
> Anyone who has access to an instance of MyViewController can do anything
> they want to with my label, including replacing it.
>
> Also, anyone who has an instance of MyViewController can call my
> buttonDoItTouchUpInside action.
>
> For example, in my AppDelegate, I can do:
>
> - (BOOL)application:(UIApplication *)application
> didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
> {
> self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen]
> bounds]];
>
> MyViewController * myViewController = [[MyViewController alloc]
> initWithNibName:@"MyViewController" bundle:nil];
> [self setMyViewController:myViewController];
> [[self window] setRootViewController:[self myViewController]];
> [[self window] makeKeyAndVisible];
>
> // Mess with MyViewController!!!! HAHAHAHA FU, MyViewController!!!!
> [[myViewController labelMyLabel] setText:@"This is ridiculous!!!!"];
> return YES;
> }
>
> To me, this totally ridiculous. It breaks well-established rules of
> encapsulation.
>
In part, I see your problem (the interface is just meant for IB, not for other programmers), but remember, the point of a controller object is to mediate between some view object (which may have subviews, etc...) and one or more model objects. The fact that the header declares your IBOutlets and IBActions is intentional, otherwise your design would be too-tightly coupled; this isn't good. Remember, MVC is a paradigm, not a hard-and-fast, set-in-stone set of laws. There are times when you should (and need to) break some of the design principles in order to uphold others; it all depends on what your design is attempting to do. Not only does IB need to know where the outlets and actions are, so do any other designers (including yourself at a later date) who then want to make an alternate view on your data; all they need to is connect the new view (xib) to your existing controller and wire up the outlets and actions. Sometimes we have to make trade-offs in order to get a design that works. In this case, the trade-off is exposing outlets and actions to possible code misuse, in order to be able to use a visual builder (IB) to make our user interfaces in a straight-forward manner.
It's also part of what makes a dynamic language like ObjC so powerful; but - as always - with power comes responsibility. You could - if needed - substitute a UILabel subclass for the original, and your controller wouldn't know the difference, since - to it - what it has a reference to IS a UILabel, and acts like a UILabel, so it must be a UILabel! This is sometimes referred to as 'duck typing'; if it looks like a duck and acts like a duck, it's probably a duck; use it as such.
HTH!
Also, this post really belongs on the Cocoa list as this is a programming sort of problem, not a problem with Xcode usability.
> From my analysis, it appears to be nothing more than an artifact of how
> rehydrating NIBs works, and it totally compromises good OO software design
> by leaking all the implementation details of MyViewController to the
> outside world.
>
> Everyone, all the books, training materials, samples, and so on, just seem
> to accept this style of doing things as being normal. In fact, one book I
> read *encouraged* this technique of using public properties for ALL the
> internal state of a class over using ivars. It said public properties were
> preferable.
>
Here I agree with you; not everything should be public, just outlets and actions, or any model you need bindings too (or KVC/KVO compliance). Else, make it @private, or - better yet - make a named category at the top of your implementation file if you have any methods you don't want exposed to the outside world. Even if you put them in an @private section, it still announces, 'hey, there's some hidden stuff here!'.
> What in the world is the deal with this?? :-) Can anyone explain this do
> me?
>
> Building this class without Interface builder, here’s how I coded it:
>
> // MyViewController implementation.
> @implementation MyViewController
> {
> @private
> UILabel * labelMyLabel_;
> UIButton * buttonDoIt_;
> }
>
> - (void)viewDidLoad
> {
> [super viewDidLoad];
>
> [[self view] setBackgroundColor:[UIColor whiteColor]];
>
> labelMyLabel_ = [[UILabel alloc] initWithFrame:CGRectMake(20.0, 20.0,
> 280.0, 21.0)];
> [labelMyLabel_ setText:@"I dare you to press Do It!"];
> [[self view] addSubview:labelMyLabel_];
>
> buttonDoIt_ = [UIButton buttonWithType:UIButtonTypeRoundedRect];
> [buttonDoIt_ setFrame:CGRectMake(20.0, 49.0, 72.0, 37.0)];
> [buttonDoIt_ setTitle:@"Do It" forState:UIControlStateNormal];
> [buttonDoIt_ addTarget:self action:@selector(buttonDoItTouchUpInside:)
> forControlEvents:UIControlEventTouchUpInside];
> [[self view] addSubview:buttonDoIt_];
> }
>
> - (void)viewDidUnload
> {
> [super viewDidUnload];
> }
>
> // buttonDoItTouchUpInside action.
> - (void)buttonDoItTouchUpInside:(id)sender
> {
> [labelMyLabel_ setText:@"You pressed Do It!"];
> }
>
> @end
>
> To me, this is how things should be. The implementation details of how my
> view works are hidden.
>
> Am I missing something?
>
> @property sure is convenient, but it seems to be misused a lot. A class
> should expose properties that are public, and hide implementation details
> that are not.
>
> Thanks!
>
> Brian
> _______________________________________________
>
> Cocoa-dev mailing list (email@hidden)
>
> Please do not post admin requests or moderator comments to the list.
> Contact the moderators at cocoa-dev-admins(at)lists.apple.com
>
> Help/Unsubscribe/Update your Subscription:
>
> This email sent to email@hidden
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden