Currently I'm hard coding another object directly when needed and I'd like the ability to take this in through the init method (I assume?). In the code below I create a new HatService in the viewDidLoad method and I'd prefer not to do this this for testability / coupling reasons.
- (void)viewDidLoad
{
[super viewDidLoad];
HatService* hatService = [[HatService alloc] init];
[hatService getHatById:self];
}
First question - How should this be done in objective-c?
Second question - Should I be worried about this or doing it at all?
Update
Here is what I'm starting with but I never seem to hit the first init method during runtime. I have both declared in the interface - anything else I missed that would allow me to override this init method and inject the service dependency?
Note - I can hit this manually when I create the object myself but when the appDeleage does the work I don't see it hit this (how does the app delegate create this object without calling my init method?)
-(id)initWithHatService:(HatService *)service
{
if (self = [super init])
{
[self setService:service];
}
return self;
}
-(id)init
{
HatService* hatService = [[HatService alloc] init];
return [self initWithHatService:hatService];
}
The designated initializer for UIViewController is - (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle. You'll need to override this method if you want your view controller to be properly instantiated from a nib file, if that's what you're trying to do. Without seeing more code, it's impossible to tell, but this method is definitely being called somewhere.
Second, you need to re-write your -init method to look like this:
- (id)init {
return [self initWithHatService:nil];
}
Every init method should call your designated initializer.
Finally, when you're dealing with view controllers, folks usually don't pass in properties in the init method. Instantiate the new controller, then pass it whatever service properties you like.
MyViewController *newContrlr = [[MyViewController alloc] initWithNibName:nil bundle:nil];
newContrlr.hatService = [[[HatService alloc] init] autorelease];
If you're asking how to pass an object to another object's init method, just declare your init method as taking one or more arguments. i.e. - (id) initWithHatService:(HatService *)serv; init methods aren't special in any way except convention in objective-c.
Unrelated: your -getHatById: method is curious. What are the memory management semantics of that? get-prefixed methods are pretty uncommon in Cocoa programs.
It's quite easy to implement the dependency injection design pattern in Objective-C. You could do it manually by creating an 'application assembly' class that defines individual collaborators.
Alternatively, you could use a framework to assist. I recently created a DI container called Typhoon: http://www.typhoonframework.org
Related
I have 3 Objective-C classes:
Animal - a subclass of NSObject
Feline - a subclass of Animal
Cat - subclass of Feline
Each of these three classes implement (what I thought was) its own private method (-private_Setup), for setup:
e.g. in Animal:
-(instancetype)init
{
self = [super init];
if (self) {
[self private_Setup];
}
return self;
}
Same thing in the Feline and Cat classes.
A list of required classes is provided at runtime, so I am creating instances of the various classes, depending on a list of class names.
e.g.
NSString *className = ... // #"Animal", #"Feline" or #"Cat".
id animal = [[NSClassFromString(className) alloc] init];
Problem:
If I create an instance of Cat, -private_Setup is being called multiple times, for each step in the inheritance chain. For example, the calling chain for a Cat:
-[Cat init]
-[Feline init]
-[Animal init]
-[Cat private_Setup] // First!
then, from:
-[Feline init]
-[Cat private_Setup] // Second!
then, from:
-[Cat init]
-[Cat private_Setup] // Third!
Thus, what I thought was a method private to each class, is being called after each -init in the hierarchy.
Could someone please advise how I could either fix this issue or redesign my setup strategies? Thankyou.
[edited, appended for clarity]
Some form of setup is required at each level of the inheritance, to supply data particular to that level. e.g. Cat specific settings.
Thus, what I need is for a Cat object to be fully set up as an Animal, as a Feline, and as a Cat.
I guess one approach is to have different setup method names at each level. e.g. setupAnimal, setupFeline, setupCat.
e.g. in Animal:
-(instancetype)init
{
self = [super init];
if (self) {
[self setupAnimal];
}
return self;
}
// in Feline:
-(instancetype)init
{
self = [super init];
if (self) {
[self setupFeline];
}
return self;
}
// in Cat:
-(instancetype)init
{
self = [super init];
if (self) {
[self setupCat];
}
return self;
}
Is there a better, more preferred design than this?
I think you should call private_Setup method just once - in Animal's init. That's all. Implement in all subclasses, call just once in supercalass
You're saying that you need a different form of setup for each subclass. Of course! That's why what you do is you implement private_Setup in every subclass. Implement just like you need it for every particular subclass. It's called overriding. Inside overridden method call [super private_Setup]. You'll get exactly what you need.
You are saying super init and super's init calls private_Setup, so why are you surprised?
I guess you're surprised because you expect each init in the chain to call its own class's private_Setup. But the thing to understand is that meaning of self changes depending on the original class of the instance that started the chain (polymorphism).
So, for example, if you call self private_Setup from Animal's init during the process of initializing a Cat, it is Cat's private_Setup that will be called. Thus, with the arrangement you've made, it is exactly true that on initialization of a Cat, Cat's private_Setup will be called three times and none of the others will be called.
The solution is simple: don't use an extra method. It is a capital mistake to have an initializer call any methods (and the problem you're having is one of the reasons why it's a mistake). Instead, simply perform the setup in init itself. That is what it's for.
If I understand correctly, you are implying that the call to private_Setup happens in all of your subclasses' init methods too.
Remove the call to private_Setup from all but the top (e.g. Animal) init method. As long as all of them call [super init], you'll get exactly one call to private_Setup.
I am just curious if lets per say I have a singleton property of webView I am trying to assign at the initialization point of another viewController. The compiler is generating the error indicating "Incompatible pointer types".
I am not sure why it is doing so, as the Super Class of that Class is still UIViewController. Any help here would be really appreciated.
Thanks.
Code below:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nil bundle:nil];
if (self) {
// Work your initialising magic here as you normally would
if ([[CartCheckout sharedInstance] universalVC]) {
//self = [[CartCheckout sharedInstance] universalVC];
NSLog(#"testing");
self = [[CartCheckout sharedInstance] universalVC];
}
NSLog(#"initWithNibName allocated");
}
return self; }
This is happening because the view controller of the actual instance you are trying to do the assignment to is not of the correct type, it doesnt matter that they share a super class.
self in this case is of one and only one type, the type of the view controller subclass.
Also, even if they were the same type, Objective C is not going to let you replace the instance of the class in an init method with anything.
init methods are instance allocation methods, if you return a pointer to something besides the instance you are actually in, you are going to crash immediately following.
So you have two problems, one, you are trying to inject a singleton instance into an init method, and two you are trying to hijack the container of a view.
Neither of these will work. Instantiate the correct type of viewController to go with your view at the time you need it.
What you want instead is this:
MyUniversalViewController *universalVC = [MyUniversalViewController alloc] initWithNibName:#"blabla
Trying to switch types in the constructor is too late.
#implementation NVController
//Plain Init method
-(id)init
{
self=[super init];
if(self)
{
}
return self;
}
//CustomInit Method
-(id)initWithRootViewController:(UIViewController *)rootViewController
{
self=[super initWithRootViewController:rootViewController];
if(self)`enter code here`
{
}
return self;
}
#end
NVController *instance=[[NVController alloc] initWithRootViewController:nil];
Here In above case ,Since I call only initWithRootViwController, another constructor init is also called. Any help would be appreciated.
This happens because you did not implement your initializers correctly.
In Objective C there is a concept of designated initializer, a single init function of your class that all other initializers must call. It is the designated initializer that calls [super init] directly; all other initializers need to call [super init] indirectly by invoking the designated initializer.
In your particular case you need to move the code common to both your init and initWithRootViewController:, if any, into the initWithRootViewController: initializer, and rewrite the plain init as follows:
-(id)init {
return [self initWithRootViewController:nil];
}
** EDIT :** (in response to the comment indicating that this solution causes an infinite recursion) I think the reason why you get infinite recursion has to do specifically with implementation details of UINavigationController, which should not be inherited. According to Apple's documentation,
The UINavigationController class implements a specialized view controller that manages the navigation of hierarchical content. This class is not intended for subclassing. Instead, you use instances of it as-is in situations where you want your application’s user interface to reflect the hierarchical nature of your content.
EDIT: The prohibition against subclassing has been lifted in iOS 6 - see the documentation for UINavigationController.
I guess initWithRootViewController: is implemented like this:
-(id)initWithRootViewController:(UIViewController *)rootViewController
{
self=[self init];
if(self)
{
// do something with rootViewController
}
return self;
}
is possible pass an args on an init method? I'm working with UITableViewController classes and UINavigatorController, when I push a new view whit:
[self.navigationController pushViewController:[[Class alloc] init] animated:YES];
I would also pass a string to that controller, is it possible?
It is not only possible, but it is normal and very common. In fact, it is quite common to have multiple initializers in classes where each initializer has a different combination of args, allowing you to initialize a class in different ways.
As you find yourself creating more than one initializer for a class, you should be sure to follow the best practices for making one initializer the "designated initializer". Here is a link to an article that demonstrates the principle of designated initializers.
Sure, just define your own initializer for your view controller subclass:
- (id)initWithStyle:(UITableViewStyle)style andSomeParameter:(NSString *)param
{
if (self = [super initWithStyle:style]) {
myInstanceVariable = [param retain];
}
return self;
}
(This is for a subclass of UITableViewController.)
- (id) init
{
[super init];
//initialitation of the class
return self;
}
I know that when I am inheriting from another class I am suppose to call super.init
Does this apply to "inheriting from NSObject" ?
Yes, usually you have something like:
- (id) init
{
if (self = [super init]) {
// instantiation code
}
return self;
}
Technically, yes, because Apple's documentation says init... methods should always include a call to super. However at present, the NSObject implementation of -init does nothing, so omitting the call wouldn't prevent your code from working.
The downside of omitting the call to super is that your code wouldn't be as robust to future changes; for example if you later changed the inheritance, or if (God forbid) Apple changed NSObject's -init method so that it actually did something essential.