3

I occasionally instantiate a class from my view controller by passing in the view controller instance itself so that the objects that I create can invoke methods of the controller to update the view.

Is that always, often, or never a bad practice?

Concretely:

ViewController.h has

-(void)updateButtonValue:(NSString*)value;

MyObject.h has

-(id)initWithViewController:(ViewController*)aViewController;

I instantiate that class from my view controller with:

[[MyObject alloc] initWithViewController:self];

thus allowing that MyObject instance to update a button value in my view by a simple call like:

MyObject.m

[self.viewController updateButtonValue:@"example"];

It does not seem ideal since I am passing to MyObject much more (the view controller itself) than it should need, but it is certainly quick and functional. If there is a cleaner approach, such as relying on protocols, that is also succinct, a brief code sample would be much appreciated.

Lolo
  • 3,935
  • 5
  • 40
  • 50

1 Answers1

4

It is always bad practice to pass a class-typed pointer in, as you are tightly coupling your objects together (each object needs to know the class of the other, they might as well be a single object). This is what the delegate pattern is for. It minimises the info MyObject needs (minimally, nothing more than a pointer type id - preferably, a protocol specified by MyObject to offer it some behavioural guarantees)

So to translate your example

MyObject.h

replace...

-(id)initWithViewController:(ViewController*)aViewController;

with...

-(id) init; 

(which you can dispense with if you have no further reason to override)

and...

@property (nonatomic, weak) id delegate;

Instantiation in myViewController (which does need to #include MyObject) ...

MyObject* object = [[MyObject alloc] init];

Followed by

object.delegate = self;

(Note that object gets a pointer to myViewController without needing to know anything else about it)

Now you can do this from inside object:

 [self.delegate updateButtonValue:@"example"];

However ... you will want to ensure that your delegate can receive the message updateButtonValue:

To do this, you declare a protocol in MyObject.h with the signature of this method

@protocol MyObjectDelegate

  - (void) updateButtonValue:(NSString*)string;

@end

And in your viewController, declare that you conform to this protocol using <> in the interface line

@interface ViewController <MyObjectDelegate>

(this is no big deal, ViewController already has to #include MyObject to alloc/init it, so needs no more info to do this)

And expand your property declaration thus:

@property (nonatomic, weak) id <MyObjectDelegate> delegate

Now you have given the compiler enough information for it to ensure that you can only pass conformant messages around. The brilliant thing here is that MyObject can confidently pass messages to MyViewController without needing to know anything about MyViewController other than that it is reached via the delegate pointer.

foundry
  • 31,615
  • 9
  • 90
  • 125
  • I wish I could upvote twice. Clear and detailed, answering exactly what I wanted to know. Note: I needed to go all the way to declaring the protocol like you suggested before my code could work properly. Without it, I could indeed invoke my updateButtonValue but the view itself wasn't updated. Not entirely sure why. – Lolo Feb 14 '13 at 04:52
  • Would bindings be a possible design pattern that would be useful here? For example in an object, high up in the controller hierarchy (AppDelegate or NSDocument), you could bind to values lower down, and call custom methods when the observed properties changed. Just wondering because I use the delegate pattern a lot but I starting to play a little bit with bindings in my code. – Daniel Farrell Feb 14 '13 at 11:39