Better way than write dozens of empty getters? - objective-c

I use lazy instantiation on my properties, to have my class created and used as fast as possible. To achieve this, I write lots of 'empty' getters like this:
- (VMPlacesListFilter *)currentFilter
{
if (!_currentFilter) {
_currentFilter = [[VMPlacesListFilter alloc] init];
}
return _currentFilter;
}
They are all the same: if the instance variable is nil, call the -alloc and -init on the class of the property, then return the instance variable. Very common and straightforward.
If I don't create this getter by myself, Objective-C's automatic synthesization creates a getter for me, which does only the returning part (does not init the object if the instance variable is nil).
Is there any way to avoid writing this boilerplate code?

Nope, I'm afraid there's no good way around it, if you really want to have lazy initialization. Personally, I usually save lazy initialization for stuff that could really be time consuming or memory intensive (say, loading images or view controllers), and initialize cheap stuff (like simple data structures or model objects) in init.
- (instancetype) init {
self = [super init];
if( self ) {
_cheapThing1 = [NSMutableArray array];
_cheapThing2 = [[MyModelObject alloc] init];
}
return self;
}
- (ExpensiveThing*) expensiveThing
{
if( _expensiveThing == nil ) {
_expensiveThing = [[ExpensiveThing alloc] init];
}
return _expensiveThing;
}
Unless you're loading something from disk or the network, I wouldn't worry too much about initialization time. Of course, profile it.
I know this is an Objective-C question, but it's worth noting that Swift has lazy initialization built-in.
lazy var currentFilter = VMPlacesListFilter()

First off, I totally agree with #zpasternack that "lazy load" should not be misused. However, automatically generating setters and getters is completely doable with the power of Objective-C runtime. In fact, CoreData is doing this.
Anyway, I have come up with some stupid code implementing a class called LazyClass, in which you can declare dynamic properties like lazyArray (see below). Using dynamic method resolution, when the property is accessed for the first time, a getter that calls the corresponding class's default +alloc and -init method will be automatically added to the class. All underlying instance variables are stored in an NSMutableDictionary called myVars. Of course you can manipulate ivars through the runtime API as well, but using a dictionary should save some work.
Please note that this implementation just shows the basic idea of how it works. It lacks error checking and is not supposed to be shipped.
LazyClass.h
#interface LazyClass : NSObject
#property NSMutableDictionary *myVars;
// lazily initialized property
#property NSArray *lazyArray;
#end
LazyClass.m
#import "LazyClass.h"
#import <objc/objc-runtime.h>
#implementation LazyClass
#dynamic lazyArray;
- (instancetype)init {
self = [super init];
self.myVars = [NSMutableDictionary dictionary];
return self;
}
- (NSMutableDictionary *)getMyVars {
return self.myVars;
}
// the generated getter method
id dynamicGetterMethodIMP(id self, SEL _cmd) {
// selector name, which is also the property name
const char *selName = sel_getName(_cmd);
NSString *selNSName = [NSString stringWithCString:selName encoding:NSUTF8StringEncoding];
NSString *keyPath = [NSString stringWithFormat:#"myVars.%#", selNSName];
if (![self valueForKeyPath:keyPath]) {
// get the actual type of the property
objc_property_t property = class_getProperty([self class], selName);
const char *attr = property_getAttributes(property);
NSString *attrString = [[NSString alloc] initWithCString:attr encoding:NSUTF8StringEncoding];
NSString *typeAttr = [[attrString componentsSeparatedByString:#","] firstObject];
NSString *typeName = [typeAttr substringWithRange:NSMakeRange(3, typeAttr.length - 4)];
// the default initialization
Class typeClass = NSClassFromString(typeName);
[self setValue:[[typeClass alloc] init] forKeyPath:keyPath];
}
return [self valueForKeyPath:keyPath];
}
// the generated setter method
void dynamicSetterMethodIMP(id self, SEL _cmd, id value) {
// get the property name out of selector name
// e.g. setLazyArray: -> lazyArray
NSString *propertyName = NSStringFromSelector(_cmd);
propertyName = [propertyName stringByReplacingOccurrencesOfString:#"set" withString:#""];
propertyName = [propertyName stringByReplacingOccurrencesOfString:#":" withString:#""];
propertyName = [NSString stringWithFormat:#"%#%#", [propertyName substringToIndex:1].lowercaseString, [propertyName substringFromIndex:1]];
NSString *keyPath = [NSString stringWithFormat:#"myVars.%#", propertyName];
[self setValue:value forKeyPath:keyPath];
}
// dynamic method resolution
+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
if ([NSStringFromSelector(aSEL) containsString:#"set"]) {
class_addMethod([self class], aSEL, (IMP)dynamicSetterMethodIMP, "^?");
} else {
class_addMethod([self class], aSEL, (IMP)dynamicGetterMethodIMP, "v#:");
}
return YES;
}
#end
Documentation

If it's the verboseness that bothers you, I suppose you could compress lazy initialisers that only need one-line initialization using the ternary operator:
- (VMPlacesListFilter *)currentFilter
{
return _currentFilter ? : (_currentFilter = [[VMPlacesListFilter alloc] init]);
}
DISCLAIMER: I don't do this, but it's interesting that it can be done

Related

Factory methods in Objective-C

I make class factories like so,
#implementation Universe {
NSString *foo;
}
+ (instancetype)universeWithMeaning:(NSString *)meaning
{
return [[self alloc] initUniverseWithMeaning:meaning];
}
- (id)initUniverseWithMeaning:(NSString *)meaning
{
if (self = [super init]) {
foo = meaning;
}
return self;
}
- (void)showMeaning
{
NSLog(#"%#", foo);
}
#end
And create object like this,
Universe *universe = [Universe universeWithMeaning:#"42"];
[universe showMeaning]; // Prints 42
This works great, but the method signature of initUniverseWithMeaning: is the same as that of universeWithMeaning:, except that it's an instance method which allows it to save instance variables to the created object.
Is there a way to this without having to implement the initUniverseWithMeaning: instance method?
I know its necessary to be inside of an instance method to be able to access instance variables, so I've been experimenting with blocks. My idea was to pass a block containing instance variable assignations to the class method which would somehow execute it in the instance context.
Implementation,
#implementation Cat {
NSString *lives;
}
+ (Cat *)newCat:(void(^)(void))cat
{
cat(); // **Problem 1**
}
- (void)showLives
{
NSLog(#"%#", lives);
}
#end
Usage,
Cat *cat = [Cat newCat:^void (void) {
self.lives = 9; // **Problem 2**
}];
[cat showLives]; // I'd like this to print 9
Problem 1: How to create a Cat object and execute cat() inside it?
Problem 2: How to make self refer to the object in the block's execution environment?
Anyway, this is more of a curiosity than anything else, it's would only be practically useful to save me from writing alloc (I would just need to include a method prototype for initUniverseWithMeaning: in the .h file.)
For your problem 1 and 2, you can try this
#interface Cat ()
#property (strong) NSString *lives;
#end
#implementation Cat
+ (Cat *)newCat:(void(^)(Cat *me))cat
{
Cat *newcat = [[self alloc] init];
cat(newcat);
return newcat;
}
- (void)showLives
{
NSLog(#"%#", lives);
}
#end
Cat *cat = [Cat newCat:^(Cat *me) {
me.lives = 9;
}];
[cat showLives]; // print 9
but I can't see much use of it... Isn't this simpler?
Cat *cat = [Cat new];
cat.lives = 9;
[cat showLives];
For your real problem
Is there a way to this without having to implement the initUniverseWithMeaning: instance method?
+ (instancetype)universeWithMeaning:(NSString *)meaning
{
Universe *universe = [[self alloc] init];
if (universe) universe->foo = meaning;
return universe;
}
The first example you've posted is the correct way of creating Objective-C factory methods.
An Objective-C factory method is nothing more than a class method wrapper around an instance level init method. Generally speaking, every factory method should have a paired init method that takes the same number and type of arguments.
fooWithBar:(NSString *)bar should be paired with initWithBar:(NSString *)bar, etc.
An exception might come in when you have an init method that takes arguments, but you've create a handful of factory methods with default arguments for this method. For example:
- (instancetype)initWithString:(NSString *)string;
+ (instancetype)fooWithString:(NSString *)string {
return [[self alloc] initWithString:string];
}
+ (instancetype)fooWithBar {
return [[self alloc] initWithString:#"bar"];
}
Now, you can create the object with in the method, then modify it, and return the modified object.
For example:
+ (instancetype)fooWithString:(NSString *)string {
Foo *f = [[self alloc] init];
f.str = string;
return f;
}
But honestly, it's just better to have an initWithString: method.
Every class should have a designated initializer and every object of that class should go through the designated initializer.

Does Objective-C support Generics?

I wonder whether Objective-C offers any support for generics?
For instance, consider a method:
-(void) sort: (NSMutableArray *) deck {
}
Is there any way for me to make it only deal with Deck of Cards?
Is something like this possible to enforce?
-(void) sort: (NSMutableArray <Card *>) deck {
}
Objective-C supports lightweight Generics since 2015, with the Xcode 7.
The Xcode 7 compiler will give you the compiler warning if there is a type mismatch.
For example, the following line will raise a compiler warning as the second object in the array causes type mismatch. The array allows only NSString objects.
NSArray <NSString *> *myArray = [#"str2", #1, #"str2"];
You can use the introspection tools offered by the objective-c runtime.
Basically, it means you can check if all objects in an array either are a kind of class (Class A or one subclass of it) or a member of class (class A), or if a objects conforms to a protocol or responds to a selector (a certain method is present).
-(void) sort: (NSMutableArray *) deck {
for(id obj in deck){
if(obj isKindOfClass:[A class]]){
//this is of right class
}
}
}
You could write a Category method on NSArray that checkouts this on every object.
BOOL allAreKindOfA = [array allObjectsAreKindOfClass:[A class]];
Normally you actually don't need this very often, as you know what you put inside a collection.
If you need to check the type or ability of an object in a Array, this might be an indicator, that your Architecture is broken
Another option could be a subclass of NSMutableArray that only accepts certain classes. But be aware of the subclassing notes for NSMutableArray and NSArray, as these are Class-Clusters and therefore not easy to subclass.
Note: In my other answer I created a NSMutableArray subclass, that uses a block to test, if a certain requirement is fulfilled. If you test against class-membership, this will do exactly what you want. Use the second block for error handling.
As of Xcode 7's release, Apple has added support for Objective-C generics.
NSArray <NSString *> *arrayOfStrings = #[#"a", #"b"];
NSDictionary <NSString *, NSDate *> *dictionaryOfDates = #{ #"a" : #1 };
Inspired by MonomorphicArray I came up with another idea:
Create a subclass on NSMutableArray, that takes two blocks:
AddBlock — a block that test, if one or more requirements are full filed and adds the object only, if its passes the test
FailBlock — a block, that defines what happens, if the test was not successful.
The AddBlock could test for a certain class membership like
^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
}
and the FailBlock can raise an exception, fail silently or add the element, that failed the test, to another Array. If no failBlock is provided, a default block will raise an error.
The blocks will define, if an array acts like an generic array, or as a filter.
I will give an complete example for the second case.
VSBlockTestedObjectArray.h
#import <Foundation/Foundation.h>
typedef BOOL(^AddBlock)(id element);
typedef void(^FailBlock)(id element);
#interface VSBlockTestedObjectArray : NSMutableArray
#property (nonatomic, copy, readonly) AddBlock testBlock;
#property (nonatomic, copy, readonly) FailBlock failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock Capacity:(NSUInteger)capacity;
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock;
-(id)initWithTestBlock:(AddBlock)testBlock;
#end
VSBlockTestedObjectArray.m
#import "VSBlockTestedObjectArray.h"
#interface VSBlockTestedObjectArray ()
#property (nonatomic, retain) NSMutableArray *realArray;
-(void)errorWhileInitializing:(SEL)selector;
#end
#implementation VSBlockTestedObjectArray
#synthesize testBlock = _testBlock;
#synthesize failBlock = _failBlock;
#synthesize realArray = _realArray;
-(id)initWithCapacity:(NSUInteger)capacity
{
if (self = [super init]) {
_realArray = [[NSMutableArray alloc] initWithCapacity:capacity];
}
return self;
}
-(id)initWithTestBlock:(AddBlock)testBlock
FailBlock:(FailBlock)failBlock
Capacity:(NSUInteger)capacity
{
self = [self initWithCapacity:capacity];
if (self) {
_testBlock = [testBlock copy];
_failBlock = [failBlock copy];
}
return self;
}
-(id)initWithTestBlock:(AddBlock)testBlock FailBlock:(FailBlock)failBlock
{
return [self initWithTestBlock:testBlock FailBlock:failBlock Capacity:0];
}
-(id)initWithTestBlock:(AddBlock)testBlock
{
return [self initWithTestBlock:testBlock FailBlock:^(id element) {
[NSException raise:#"NotSupportedElement" format:#"%# faild the test and can't be add to this VSBlockTestedObjectArray", element];
} Capacity:0];
}
- (void)dealloc {
[_failBlock release];
[_testBlock release];
self.realArray = nil;
[super dealloc];
}
- (void) insertObject:(id)anObject atIndex:(NSUInteger)index
{
if(self.testBlock(anObject))
[self.realArray insertObject:anObject atIndex:index];
else
self.failBlock(anObject);
}
- (void) removeObjectAtIndex:(NSUInteger)index
{
[self.realArray removeObjectAtIndex:index];
}
-(NSUInteger)count
{
return [self.realArray count];
}
- (id) objectAtIndex:(NSUInteger)index
{
return [self.realArray objectAtIndex:index];
}
-(void)errorWhileInitializing:(SEL)selector
{
[NSException raise:#"NotSupportedInstantiation" format:#"not supported %#", NSStringFromSelector(selector)];
}
- (id)initWithArray:(NSArray *)anArray { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithArray:(NSArray *)array copyItems:(BOOL)flag { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfFile:(NSString *)aPath{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithContentsOfURL:(NSURL *)aURL{ [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(id)firstObj, ... { [self errorWhileInitializing:_cmd]; return nil;}
- (id)initWithObjects:(const id *)objects count:(NSUInteger)count { [self errorWhileInitializing:_cmd]; return nil;}
#end
Use it like:
VSBlockTestedObjectArray *stringArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
return [element isKindOfClass:[NSString class]];
} FailBlock:^(id element) {
NSLog(#"%# can't be added, didn't pass the test. It is not an object of class NSString", element);
}];
VSBlockTestedObjectArray *numberArray = [[VSBlockTestedObjectArray alloc] initWithTestBlock:^BOOL(id element) {
return [element isKindOfClass:[NSNumber class]];
} FailBlock:^(id element) {
NSLog(#"%# can't be added, didn't pass the test. It is not an object of class NSNumber", element);
}];
[stringArray addObject:#"test"];
[stringArray addObject:#"test1"];
[stringArray addObject:[NSNumber numberWithInt:9]];
[stringArray addObject:#"test2"];
[stringArray addObject:#"test3"];
[numberArray addObject:#"test"];
[numberArray addObject:#"test1"];
[numberArray addObject:[NSNumber numberWithInt:9]];
[numberArray addObject:#"test2"];
[numberArray addObject:#"test3"];
NSLog(#"%#", stringArray);
NSLog(#"%#", numberArray);
Note: This code is not fully tested. Probably some of the unimplemented method should be implemented for usage in real world programs.
Not directly, no. There a few ways to simulate it, but it requires a lot of wrapper code, boilerplate code, and runtime overhead. I just switch to Objective-C++ and use C++ templates when I want or need proper generics.
So if you wanted to introduce typesafety/checks to an NSArray, you could approach it using something like this:
template <typename T>
class t_typed_NSMutableArray {
public:
t_typed_NSMutableArray() : d_array([NSMutableArray new]) {}
~t_typed_NSMutableArray() { [d_array release]; }
/* ... */
T* operator[](const size_t& idx) {
T* const obj([this->d_array objectAtIndex:idx]);
assert([obj isKindOfClass:[T class]]);
return obj;
}
void addObject(T* const obj) {
assert([obj isKindOfClass:[T class]]);
[this->d_array addObject:obj];
}
private:
NSMutableArray * const d_array;
};
in use:
t_typed_NSMutableArray<Card> array([self cards]); // < note this exact constructor is not defined
Card * firstCard = array[0]; // << ok
NSString * string = array[0]; // << warning
then you also get type safety and overloading when passing the collection, so you could not pass t_typed_NSArray<Card> as an t_typed_NSArray<NSURL>.
There is an easy, effective way of doing this (I've been using it on projects for a couple of years now). Sadly, someone deleted the answer, and my attempts to get it re-instated were rejected. Here goes again:
You can re-implement a cut-down version of C++ templating within Obj-C because Obj-C encapsulates all of C (and C++ templates are C-macros with some improved compiler/debugger support):
This only needs to be done once, using a single header file. Someone has done it for you:
https://github.com/tomersh/Objective-C-Generics
You end up with 100% legal Obj-C code that looks like this:
NSArray<CustomClass> anArray= ...
CustomClass a = anArray[0]; // works perfectly, and Xcode autocomplete works too!
This all works fine in XCode, with autocomplete, etc.

Do I need to allocate NSStrings passed in as parameters to a custom initialization method?

Please consider the following two initialization methods.
The first method simply passes the value of the parameters to their respective NSString properties, but the second allocates the properties and then initializes them using the initWithString: method. Is the allocation in the latter example necessary?
Thanks in advance.
-(id)initWithTitle:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = theTitle;
muscleGroup = theMuscleGroup;
equipment = theEquipment;
}
return self;
}
-(id)initWithTitle2:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = [[NSString alloc] initWithString:theTitle];
muscleGroup = [[NSString alloc] initWithString:theMuscleGroup];
equipment = [[NSString alloc] initWithString:theEquipment];
}
return self;
}
The first example is not safe because you are not taking ownership of the strings, so your program will get all crashy if they are later released elsewhere. The second example fixes that problem and will work perfectly well, but is more concisely written thusly:
-(id)initWithTitle2:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = [theTitle copy];
muscleGroup = [theMuscleGroup copy];
equipment = [theEquipment copy];
}
return self;
}
NSString gives you a copy constructor (-initWithString:), which enables you to do what you are doing in #2, but not all classes do. copy requires the class to implement the NSCopying protocol, but is more conformant with the way a Cocoa API developer would expect to be able to copy objects.
Parameter objects don't get copied when you pass them in. So your first example may not always work, it depends how you've initialized your strings.
The following is safer (although remember to release the objects in your dealloc method):
-(id)initWithTitle:(NSString *)theTitle muscleGroup:(NSString *)theMuscleGroup equipment:(NSString *)theEquipment {
if((self = [super init])){
title = [theTitle retain];
muscleGroup = [theMuscleGroup retain];
equipment = [theEquipment retain];
}
return self;
}
Example 1 will assign the pointers. It makes no attempt to retain the objects and is vulnerable to something outside changing the content of the objects.
It could work depending on how the arguments are constructed in the first place;
Example 2 will copy the string objects and retain them. As long as you release in the dealloc then its the preferable method.
FWIW
title = [theTitle copy];
or
title = [[NSString stringWithString:theTitle] retain];
are equally good in Ex 2

Objective C Reassignment/Memory Management Crash

As a relative Objective-C beginner, I'm obviously still not grasping certain memory management rules. I can't figure out how to make this not crash:
#interface MyClass { NSArray *playerArray4th; }
- (void) viewDidLoad { playerArray4th = [self getAudioPlayersForSoundFile:#"rimshot" ofType:#"aif"]; }
- (NSArray*) getAudioPlayersForSoundFile:(NSString*)soundFileName ofType:(NSString*)soundFileType {
//code instantiating objects....
NSArray *toRet = [[NSArray alloc] initWithObjects:toRetTickPlayer,toRetTickPlayerCopy,toRetTickPlayerCopy2,toRetTickPlayerCopy3, nil];
return toRet;
}
Then later, in a different function:
NSArray *currentArray = playerArray4th;
[currentArray release];
currentArray = nil;
currentArray = [self getAudioPlayersForSoundFile:fileName ofType:ofType];
And it crashes when trying to access the array again:
- (void) playSound:(NSString*)soundType {
AVAudioPlayer *currentPlayer;
if ([soundType isEqualToString:#"4th"]) {
if (playerArray4thCounter >= [playerArray4th count]) playerArray4thCounter = 0;
NSLog(#"Playing from array: %#",playerArray4th);
currentPlayer = [playerArray4th objectAtIndex:playerArray4thCounter];
playerArray4thCounter++;
}
}
Try to learn about properties and about using getters and setters. Don't take shortcuts unless you know exactly what's going on.
So define the playerArray4th property in your header file:
#property (nonatomic,retain) NSArray *playerArray4th;
And then in your .m file create getter/setter:
#synthesize playerArray4th;
Then, always use self.playerArray4th for assigning and getting the variable. The prior objects will be released when needed.
So this will not leak:
self.playerArray4th = [NSArray arrayWithObjects:#"text",#"text",nil];
self.playerArray4th = [NSArray arrayWithObjects:#"new array",#"text",nil];
because the second assignment releases the first array.
Furthermore, read about using autorelease. In short, if you alloc, copy or new, you should either release or autorelease. There's a lot to read about this here on SO which I will not repeat here now.
Don't forget to put self.playerArray4th = nil; in your dealloc method.

Obj-c, how do I create function which will populate an NSDictionary and gain a value from the function?

I've been reading about NSArrays and NSDictionaires and I think I need the later. I'm trying to populate an object from a small database table. So I can access the string values via a record id. I have to do this several times so putting it into an object makes sense.
I have the basics...
- (void)viewDidLoad {
// WORKING START
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
[dictCategories setValue:#"Utility" forKey:#"3"];
[dictCategories setValue:#"Cash" forKey:#"5"];
NSString *result;
result = [dictCategories objectForKey:#"3"];
NSLog(#"Result=%#", result);
// WORKING END
// Can't get this bit right, current error Request for member
// 'getCategories' in something not a structure or union
NSMutableDictionary *dictCategories2 = self.getCategories;
NSLog(#"Result2=%#", [dictCategories2 objectForKey:#"5"]);
[super viewDidLoad];
}
-(NSMutableDictionary*)getCategories {
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
[dictCategories setValue:#"Utility" forKey:#"3"];
[dictCategories setValue:#"Cash" forKey:#"5"];
return dictCategories;
}
you are calling the method wrong,try [self getCategories]
You're not being clear on what isn't working, but a few things that are obviously wrong (JonLOo might be spot on though) ...
Firstly. You're using the wrong methods, or at least there's a better one -- setValue:forKey: should/could be setObject:forKey: instead. This might be one of the reasons for your issue.
Secondly. You're over-allocating and not releasing properly. dictCategories2 in your viewDidLoad will vanish into the void and bring with it the allocated memory for dictCategories defined in the getCategories method. An easy standard fix for this is to change
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
in getCategories into
NSMutableDictionary *dictCategories = [NSMutableDictionary dictionary];
It will be autoreleased using the latter method by the system.
Thirdly. You want to read up on #property. Instead of getFoo, setBar, the Ob-C standard is to use #properties to (pre)define setters and getter methods. You can then override these to populate default data into your methods when appropriate. You also (probably) want to store the dictionary in your interface as an instance variable, rather than letting it be deallocated all the time. Example of a #property implementation that does this:
#interface foo {
NSMutableDictionary *ingredients;
}
#property (nonatomic, retain) NSMutableDictionary *ingredients;
#end
// ....
#implementation foo
#synthesize ingredients;
// ...
// the #synthesize command above will create getter and setter methods for us but
// we can override them, which we need to do here
- (NSMutableDictionary *)ingredients
{
if (ingredients != nil) {
// we've already got an ingredients variable so we just return it
return ingredients;
}
// we need to create ingredients
ingredients = [[NSMutableDictionary alloc] init];
[ingredients setObject:#"foo" forKey:#"bar"]
return ingredients;
}
In the viewDidLoad method (or anywhere else where you think ingredients might not have been initialized yet), you would do e.g.
NSMutableDictionary *dict = self.ingredients;
Anywhere else you can opt to use just ingredients without self, but if it's nil, your method will never be called, and you will get nil thrown at you.
This is useful in many cases, and is necessary if we want to ever read or write the ingredients variable from outside of our class. It's outside of what you're asking about, but I brought it up because you're trying to do something similar with self.getCategories.
Hope that helps.