Like many Enterpise Java developers - my first exposure to the Inversion of Control principle (IOC) was when applying the then radical Spring Framework some years ago. I believe that there was an IOC/DI micro container project being incubated at Apache Jakarta prior to Spring but it certainly didn't enjoy the same adoption rate.
We now find that we rely heavily on the Dependency Injection provided by our containers and frameworks. They take the responsibility for the monotinous, repetative, and tedious creational and configuration concerns of our classes leaving with much cleaner code. With some small amount of configuration they create our objects, configure them with appropriate values, and wire then up - injecting references into dependent objects. As developers this makes out lives much easier - we're no longer writing lots of boiler plate code - knowing that the frameworks will take care of it for us. Indeed we find the IOC principal in the latest releases of the EJB specification and in popular frameworks such as Spring, Seam and Guice.
But what does this have todo with iPhone development? Since starting work on a large iPhone project I have mostly concentrated my efforts on improving the data access and business logic layers of our application. However, over time I began to move towards the view tier - refactoring some of our UIViewContollers. More often than not the existing view controllers where monolithic classes servicing quite complex views. The views themselves could be broken down into logical units so I worked hard to move the various responsibilities of the view controllers into seperate classes that then became composite members of a parent controller - for example:
- MyViewController
Became:
- MyParentViewController
- MyListViewController
- MyToolbarController
- MyDetailViewController
While this neatly divided and encapsualted responsibility - it created many dependencies that needed to be satisfied. For example, the MyViewController would have to construct it's sub-controllers and pass in references to UI components such as UIButtons etc. so that they could then be managed by the sub-controller object. To do of this I would have to write a lot of boring, error-prone code - passing view components in to object via init method arguments or properties. As well as being a little tedious this also prevents the code being as clean as it could be.
Fortunately I was missing an iPhone SDK Inversion of Control trick. The Interface Builder application is commonly used to create views using a hierarchical view component model - a concept that I assume most are familiar with - you might draw a rectangular view, add a few buttons and arrange them etc. The magic begins when you specify which UIViewController class should act as controller; Once the class has be set you can begin drawing connecting lines between the actual view components and the member variable names in that class - you describe you object dependencies using a visual metaphor - very cool!
This object dependency configuration is stored in the view's XIB (nib) file and is used by the iPhone frameworks to instantiate and wire up your objects at runtime.
While this is fairly intuitive for situations where we have views mapping to single monlithic controllers, it takes a little more work when we have composite controllers. In this scenario subsets of our view components might be managed by a class that is not and does not need to be a fully fledged UIViewController. However, this sub-contoller class will be a member of a parent composite UIViewController that pulls together multiple sub-controllers - each with their own distinct responsibilities for various view components within the larger view. Clearly, we would like to use Interface Builder to direct the instantiation of these sub-controllers and satisfy the referential dependencies between them, the parent controller, and the view components.
We can do this in Interface Builder by using the 'Object' component from the component library. We create a new object for the NIB by dragging the Object component from the library. We can then specify the class of the object we wish to instantiate using the Inspector.
However, this new object - created when the NIB is loaded - will be orphaned as it's not currently associated with any other objects. Lets assume that the parent controller may need to communicate with the sub-controller. In this event we need to create an IBOutlet in the MyParentViewController class:
@interface MyParentViewController : UIViewController {
@private
IBOutlet MySubController* subController;
...
In addition to this lets assume that the responsibility of the MySubController includes something button related and hence must have a reference to a UIButton in the view. In the MySubController class we need to create another IBOutlet - this time for the button:
@interface MySubController : NSObject {
@private
IBOutlet UIButton* button;
...
Notice that MySubController does not extend UIViewController - it doesn't need to as it's responsibilities are small and may be focused purely on a subset of view components. We can now wire these objects up in Interface Builder:
Now when the NIB is loaded the sub-controller object will be instantiated and a reference injected into the parent controller - all without any code or additional effort. With this approach we can begin to break-up those monolithic view controllers into classes that adhere to the Single Responsibility Principle so that our view controllers aren't doing 'too much'. In addition to this we can keep our classes nice and clean as we don't have to litter them with extra properties and init method arguments to pass references to dependent objects. We can rely on the platform to satisfy these dependencies by injection according to the relationships we defined previously in Interface Builder.
Clearly the above example does not live in the real world. However, I have found that for complex views an controllers the approach works extremely well.
No comments:
Post a Comment