How to manage memory load of static, lazily loaded dictionaries and arrays - iphone

Generally, I use static arrays and dictionaries for containing lookup tables in my classes. However, with the number of classes creeping quickly into the hundreds, I'm hesitant to continue using this pattern. Even if these static collections are initialized lazily, I've essentially got a bounded memory leak going on as someone uses my app.
Most of these are arrays of strings so I can convert strings into NSInteger constants that can be used with switch statements, etc.
I could just recreate the array/dictionary on every call, but many of these functions are used heavily and/or in tight loops.
So I'm trying to come up with a pattern that is both performant and not persistent.
If I store the information in a plist, does the iphoneOS do anything intelligent about caching those when loaded?
Do you have another method that might be related?
EDIT - ANSWER EXAMPLE
Based on a solution proposed below, here's what I'm going to work with...
First, add a method to NSObject via category.
- (void)autoreleaseOnLowMemory;
Now, whenever I want to create lazy-loading static array or dictionary in a helper function, I can just use the following pattern...
- (id)someHelperFunction:(id)lookupKey {
static NSDictionary *someLookupDictionary = nil;
if (!someLookupDictionary) {
someLookupDictionary = [[NSDictionary dictionaryWithObjects:X, Y, Z, nil] autoreleaseOnLowMemory];
}
return [someLookupDictionary objectForKey:lookupKey];
}
Now, instead of that static dictionary living until the end of the program, if we're running out of memory it will be released, and only re-instantiated when needed again. And yes, in a large project running on an iphone, this can be important!
PS - The implementation of autoreleaseOnLowMemory is trivial. Just create a singleton class with a method that takes an object and retains it in a set. Have that singleton listen for low memory warnings, and if it gets one, release all the objects in that set. May want to add a manual release function as well.

I generally prefer plists for this just because they're easy to maintain and reuse in different sections of code. If the speed of loading them into an NSDictionary from file is a concern (and check the profiler to be sure) you can always put them into an instance variable which you can release when you get a memory warning.

If you are just doing strings, you could use C arrays.
id keys[] = { #"a" , #"b" , #"c" };
id values[] = { #"1" , #"2" , #"3" };
And if you occasionally need a true NSArray or NSDictionary from that:
[NSArray arrayWithObjects:values count:3];
[NSDictionary dictionaryWithObjects:values forKeys:keys count:3];
A plist will involve a disk hit and xml parsing for each collection. As far as I know only NSUserDefaults are cached.

Related

NSDictionary vs. custom object

The question is quite simple: When I create a new API or Service Class should I create a custom class for the objects that are being passed around or should I just stick to an NSDictionary which simply holds the data in a key-value-style format.
Obviously there are pros and cons but where do you guys think is the threshold of using one over the other?
NSDictionary:
+ No dependencies
+ Very Flexible
+ Commonly used
+ Build-in support for NSCoding
- Structure not defined -> Runtime errors
A custom Object:
+ Structure defined
+ Property-style accessors: myObject.someProperty
- Can result in a rel. big number of classes for nested objects
Update: Included remarks from jbat100
I usually have a set of domain models, which fit better with the MVC approach of iPhone development. Having specific objects also enables you to enforce type-safety a lot easier and also reduces complexity in the long run. If you have NSDictionaries containing NSArrays and more NSDictionaries etc etc to represent your object graph, it can very quickly become unmanageable.
It really depends how much you expect your data model to change. Dealing with changes once you have a custom class based data model can be tricky especially when you have archives (NSCoding stuff) with different versions of the model (in already shipped versions of your app), you must be very careful to ensure backwards compatibility and avoid nasty run time surprises. In that respect NSDictionary based models are, as you say more flexible. However they do not allow all the customized checks and behaviour that custom classes do. Also custom classes make the data model more explicit for coders unfamiliar with the code, from my experience, developers often get sloppy (especially when inexperienced) when dealing with NSDictionary based models which can quickly result in an incomprehensible mess, so if you go down that route, document well and be disciplined!
If you need readonly access and do not need methods, you can do following:
#interface NSDictionary (MyClassCategory)
-(NSString*) someField;
#end
#implementation NSDictionary (MyClassCategory)
-(NSString*) someField {
return [self objectForKey:#"someField"];
}
#end
typedef NSDictionary MyClass;
And use it:
MyClass* myClass = ...;
NSString* value = [myClass someField];

Do I need to initialize an iOS empty nested array that's part of a plist import?

the code below is working, but I want to make sure it's correct. I'm nervous about having an empty Array inside my dictionary that I create from the plist, since typically it seems that if you don't, say, initWithCapacity:1 then you often get memory errors once you start trying to add items.
At least, that's been my experience with NSMutableDictionary. However, this is the first time I'm trying to implement nested data objects, so perhaps the reason this code works is that the nested array is automatically initialized when it's imported as part of its parent dictionary?
Any and all comments appreciated. Thanks.
First, here's what the plist looks like that I'm using to create my dictionary:
Next, here's my code where I'm using the plist to create a dictionary, then adding an item to dataArray
// Create a pointer to a dictionary
NSMutableDictionary *dictionary;
// Read "SomeData.plist" from application bundle
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"SomeData.plist"];
dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:finalPath];
// Now let's see if we can successfully add an item to the end of this empty nested array. How 'bout the number 23
NSNumber *yetAnotherNumber = [NSNumber numberWithInt:23];
[[dictionary objectForKey:#"dataArray"] addObject:yetAnotherNumber];
// Dump the contents of the dictionary to the console
NSLog(#"%#", dictionary);
Okay, fine, simple, good. When I Log the dictionary contents it shows that "23" has been added as an array value to dataArray. So the code works. But again, I want to confirm that I'm not "getting lucky" here, with my code just happening to work even though I'm not properly initializing that nested array. If so, then I could run into unanticipated errors later on.
So to sum up, dataArray is an empty array inside the .plist, so do I need to initialize it somehow (using, for example initWithCapacity: or something else) before I can properly populate it, or is the way I'm coding here just fine?
Thanks again.
EDIT
Hey all. I've been doing continued research on this, in the interests of finding a satisfying answer. I think I may have stumbled upon something, via this link on deep copying. His previous posts on deep copying had presented some code to do essentially what I was looking for above: create a mutable copy of a dictionary or array, from a plist, that also has mutable sub-structures.
However, as mentioned in the link above, it looks like these methods were superfluous, due to the CFPropertyListCreateDeepCopy method, which can be invoked with a call such as
testData = CFPropertyListCreateDeepCopy(kCFAllocatorDefault, [NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);
So, my question is, can I properly use CFPropertyListCreateDeepCopy, in the way shown, to achieve what I've been asking about here? In other words, can I use this method to import my dictionary from a plist with fully mutable, nested data objects?
As I mentioned in one of the comments, I know I can create a nested, mutable dictionary manually, but for complex data that's just not practical, and it seems unlikely that built-in methods to import a mutable plist don't exist. So, based on the above, it looks like I've possibly found the solution, but I'm still too new to this to be able to say for sure. Please advise.
(Side note: I would simply test the code, but as we've established, the current SDK is buggy with regard to allow you to edit immutable nested dictionaries, contrary to the documented behavior. So as before, I'm not just interested in whether this works, but whether it's correct)
Thanks in advance.
init... methods should only be called once, immediately after a call to alloc or allocWithZone:. When framework code creates and returns an object or graph of objects, their init... methods have already been called, so sending another init... message would have undefined results. Don't do that.
Interestingly, in spite of what the documentation appears to say (and admittedly I probably missed a key sentence or paragraph somewhere), when you create an instance of a mutable collection by reading a plist, any nested collections are also mutable. I ran the following little experiment in a test harness just to be sure:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSMutableArray *cats = [pets objectForKey:#"cats"];
[cats addObject:#"Foo"]; // EDIT: Added line I accidentally omitted earlier
NSLog(#"%#", cats);
So again, the nested collections created when you read in the plist are fully initialized, and mutable to boot, so you can simply use them, as you've been doing.
EDIT
However, after doing some further reading of the docs, I think the OP is right to feel uneasy about relying on what is apparently an undocumented feature of the current version of the SDK. For example, the Property List Programming Guide states:
If you load the property list with
this call:
NSMutableArray * ma = [NSMutableArray arrayWithContentsOfFile:xmlFile];
ma is a mutable array with immutable
dictionaries in each element. Each key
and each value in each dictionary are
immutable.
So, to be on the safe side, if you need a nested collection to be mutable, you should create it yourself. For example, I'd recommend rewriting the code in the example above as follows:
NSMutableDictionary *pets = [NSMutableDictionary dictionaryWithContentsOfFile:#"/tmp/Pets.plist"];
NSArray *cats = [pets objectForKey:#"cats"];
NSMutableArray *mutableCats = [cats mutableCopy];
[pets setObject:mutableCats forKey:cats];
[mutableCats release];
You can then safely make changes to the nested mutable collection:
[mutableCats addObject:#"Foo"];
Any object in a dictionary which is created by reading from disk will be properly initialized. You will not have to do it on your own. However, as pointed out by jlehr, contents of the dictionary should be immutable. If you want the contents of the dictionary to be mutable, you will need to change them on your own. I have no idea why your program is not throwing an exception.
I do not know why you are getting memory errors while not using initWithCapacity:1 in other situations. The following code is perfectly valid:
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:#"object1"];
[array addObject:#"object2"];
NSLog(#"%#",array);
[array release];
If you don't specify a capacity, the array won't pre-allocate any memory, but it will allocate memory as required later.
Edit:
It is perfectly acceptable to use NSDictionary with CFPropertyListCreateDeepCopy. In Core Foundation, a CFPropertyList can be a CFDictionary, CFArray, CFNumber, CFString, or CFData. Since NSDictionary is toll-free bridged to CFDictionary, you can use it wherever a CFDictionary is asked for by casting, and vice-versa. Your code as is will give a warning, but you can suppress it by casting the dictionary and return values.
NSDictionary *testData = (NSDictionary*)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)[NSDictionary dictionaryWithContentsOfFile:path], kCFPropertyListMutableContainersAndLeaves);

Memory about lazy loading

I am reading tutorial book now. When I see an example, I get some confused.
The following codes show a way for lazy loading. Does this motivations array be released after instance deallocated ? or will it occupy this memory block until application terminates.
+ (NSArray *)motivations
{
static NSArray *motivations = nil;
if (!motivations)
{
motivations = [[NSArray alloc] initWithObjects:#"Greed",#"Revenge",#"Bloodlust",#"Nihilism",#"Insanity",nil];
}
return motivations;
}
Edit 1
Thank Georg for a bug.
The example you show has a bug - +arrayWithObjects: returns an autoreleased instance which will be destroyed later. The code was probably intended to be:
motivations = [[NSArray alloc] initWithObjects:#"Greed",#"Revenge",#"Bloodlust",#"Nihilism",#"Insanity",nil];
With that, the array will live until the application terminates.
Because it is a static object, so the system will store the object pointer until the application is terminated. You can use this way to cache by let your pointer point to an object that is not release or autorelease
I recommend to use this approach when you really want to cache some data in memory (usually small or big images data) that requires a lot of CPU or IO processing time to generate. For small data like NSString, you can create new array and return every time you need.
Edit for the comment:
There are 2 things about imageNamed:
1/ You cannot control what is cached and what is not cached by imageNamed:. You may not want to cache an image with big size and only used once, for example.
2/ imageNamed: cannot be used for getting image from Network or folders in the system. It will only load from your bundle
Since this is a class method (indicated by a + instead of a - at the declaration), there is no instance that will be released. (Technically there is an instance of an object of class isa I think (? comment if I'm wrong please, I don't know the inner workings very well) but don't worry about that)
So the class, which exists in memory the whole time the program is running, owns that array. Think of static as about as close as you can get to a class variable, rather than an instance variable. Since the class exists the whole time, the array exists the whole time.
Lazy loading keeps it from being created until the first time that class method is called though, so it isn't wasting memory until you need it.

Why are these class methods needed when there are perfectly fine parent class methods?

Why are class methods such as
+ (NSMutableArray *)array;
needed when there are perfectly fine parent class methods such as
+ arrayWithObjects:(id)firstObj, ...;
which could be set to
[array arrayWithObjects:nil]
and have the same effect.
Are they actually not equivalent things?
They're convenience methods, to make it easier to just get an autoreleased object fast instead of having to write a lot more to get the same result.
BTW, NSArray does not have an instance method called arrayWithObjects, they are only class methods.
Jacob's answer is right (so accept that one) but I'll add that the Cocoa framework has a spiffy thing under the hood called class clusters. The idea is that, although you may call [NSArray array], the object you get back is actually a private subclass of NSArray that's optimized for your specific situation. These convenience methods can give the NSArray class a "hint" as to which class to use.
For example, if you call [NSArray array], you get an empty, immutable array. Well, how many different values can an empty immutable array have? Just one. So behind the scenes, Cocoa can return the same empty NSArray to every call to [NSArray array] so that it only ever has to allocate one of these. This is a nifty optimization that saves some memory.
Now, this is really an implementation detail you don't need to concern yourself with, but the takeaway is that you should use the constructor that most closely matches the result you want, especially with collection classes like arrays, sets, and dictionaries, because Apple's implemented a boatload of optimizations that make your application work better.

There's got to be an easier way of dealing with arrays!

To optimize a bottleneck, I converted the creation of a large NSArray to a c-style array. (The resulting creation was 1/8 the time of the original NSArray version. Yeah!) But once it's created, speed is no longer an issue, so I'd rather benefit from it being an NSArray again.
However, it seems ridiculously involved to convert a c-style array to an NSArray (unless I'm missing some magic initWithArrayWrapElementsInObjects method.)
As I understand the process now, I first have to create an NSMutableArray, iterate through the c-style array converting each element (floats in my case) to objects, adding each object to the NSMutableArray, then creating the NSArray with the NSMutableArray.
Is that right? There's got to be a better way.
And help would be appreciated.
Thanks!
There's no direct way to take a blob of memory that you own and "convert" it cheaply into an NSArray-- after all, the framework would need to then own that memory, and it doesn't know where you got it from (malloc, stack, etc). If there were a convenience method for initWithArrayWrapElementsInObjects, it would itself need to do internally what you surmise: iterate over your provided memory and add items to itself (it's possibly the framework could do this as quickly as a memcpy, but who knows).
One way you could tackle this (and probably a fun learning exercise) is by actually creating your own subclass of NSArray that manages memory exactly as you want (ie, lets you create and init with whatever semantics you want), but that behaves to the outside world as an NSArray would. You can do this by inheriting from NSArray and implementing the methods count: and objectAtIndex: to operate on whatever memory you're holding on to. Obviously, you'd need to implement the management of your own memory in the init/dealloc, etc methods as well. See this page http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/NSArray.html
under "Subclassing Notes".
The design discussion here hinges on what your data looks like. NSArray, of course, expects its items to be Obj-C references (of type id), and not just arbitrary chunks of data. If your C-style array is holding structures or some other primitive values that aren't object references, then this technique won't really work for you-- NSArray's interface will never be happy with non-reference items.
One final note: you mention taking an NSMutableArray and "creating" an NSArray with it. You should be aware that an NSMutableArray is already an NSArray, since it's a subclass. You can use an instance of NSMutableArray anywhere you'd want an NSArray, without creating some new copy of it.
UPDATE: Missed the note about your array containing floats. Yeah, you're a little bit screwed here. NSArrays want objects. If the capacity doubling was the expensive part (as another poster notes), then try initWithCapacity:. If it's the boxing/unboxing of the floats into object types, there's nothing you can do.
I have created (but don't have handy) a pair of very simple classes (called like MYArray and MYMutableArray) that are intended to wrap just this kind of data with NSArray-like methods on them. But they're not interchangeable with NSArrays. You must use them intentionally.
UPDATE #2. I know it's been ages since this question was live, but I just revisited it and realized there actually is a sort of clever way around this in this specific case. (You want a non-mutable NSArray from a C-style float array). You can create a custom subclass of NSArray that wraps the float values and only converts them to objects when they're accessed via the primitives. This may have performance pitfalls in some corners (?), but it does neatly meet your requirements:
#interface FloatProxyArray : NSArray
{
float * values;
NSUInteger count;
}
- (id)initWithCArray:(float *)arrayOfFloats count:(int)numberOfValues;
#end
.
#implementation FloatProxyArray
- (id)initWithCArray:(float *)arrayOfFloats count:(int)numberOfValues
{
if ((self = [super init])) {
values = (float *)malloc(numberOfValues * sizeof(float));
if (!values) {
[self release]; return nil;
}
memcpy(values, arrayOfFloats, numberOfValues * sizeof(float));
count = numberOfValues;
}
return self;
}
- (void)dealloc
{
free(values);
[super dealloc]
}
- (NSUInteger)count
{
return count;
}
- (id)objectAtIndex:(NSUInteger)index
{
if (index >= count) {
[NSException raise:NSRangeException format:#""];
return nil;
}
float val = values[index];
return [NSNumber numberWithFloat:val];
}
#end
(N.B. Written in the editor without compiling/testing.)
One optimization that you can do with the NSMutableArray is initWithCapacity which will prevent the doubling of your array which is the expensive operation in the addition.
Outside of that, since NSArrays and NSMutableArrays expect objects, so it's difficult to get around this.
What benefits of it being an NSArray are you looking to get?
It seems like you may be better off with a custom wrapper object around the C array that responds to whatever NSArray messages you are looking to call. Otherwise you are right back at the point of array creation... You could try manually creating a call to initWithObjects, but at the very least every float has to be wrapped in an NSNumber which would bring down your speed again.
If you really need an NSArray because something else you want to use takes NSArray objects, then you are probably better off subclassing NSArray (following the guidelines posted by Ben).
The “best” optimisation (for speed) would almost certainly be to avoid using NSArray altogether, if possible.