Now that the GateGuru team has been developing on the iOS 5 SDK for awhile, I thought I'd pull together some lessons learned.
- Declare your IBOutlet and IBActions within the private category within your implementation file. This makes them visible to interface builder without cluttering up the external interfaces and leaking implementation details.
- Declare your IBOutlet properties as (nonatomic, weak). The weak reference implies a non-owning reference and ARC will nil it as necessary.
- Delegate properties should be (nonatomic, weak) as well.
- The designated initializer for UIView's is initWithFrame: when initialized programmatically. When loaded from a Storyboard or custom Nib, it is initWithCoder:
- The designated initializer for UIViewController's is initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil when initialized programmatically. It is initWithCoder: when loaded from a Storyboard or Nib.
- Use key-value observation rather than overriding getters and setters.
- Use NSKeyValueObservingOptionInitial to trigger a callback of KVO for values that are set before the observer is configured.
- Use the User Defined Attributes within Interface Builder to configure arbitrary views without configuring them in code. This encourages separation of view concerns from controller logic.
- Make custom view classes and let them lay themselves out rather than positioning/styling within the controller body.
- When implementing a controller/view, ensure that is can be used programmatically or from a nib/storyboard by factoring common initialization into into another method.
- Take note of the sequencing of callbacks for your controller. initWithCoder: will be called as the controller is initialized from the Storyboard, then awakeFromNib will be called after the controller is instantiated from the view, then viewDidLoad will be called once the outlets are connected.
- Take note of the sequencing of callbacks for your view. initWithCoder: will be called as the view is initialized from the Storyboard, then awakeFromNib will be called once the outlets are connected, then layoutSubviews is called once the view should be configured for display.
- Do not use lazy initialization of subviews on getters. It breaks the getter/setter semantics and makes things hard to reason about ("Asking a question should not change its answer"). Lazy init only makes sense if there's a compelling case for it (i.e. performance reasons). Otherwise initialize your subviews during init.
- Use layoutSubviews to, you know, layout your subviews. Generally initializing with CGRectZero in the initializer is a good approach and then lay them out in layoutSubviews. This will automatically be called when the view hierarchy changes and your view needs to be updated. You also cause it to happen by calling [self setNeedsLayout]. This is a good way to trigger updates when KVO observed properties change (i.e. the text on a UI element has been changed and now you need to adjust the width).
- Try to think in terms of KVC across the board. This decouples our components and integrates well with RestKit.
- If you don't know about them, please read about the Law of Demeter and the Liskov Substitution Principle. They are valuable insights for how to design classes.