In my ARC app for iOS 7.1, I have a singleton class that has a NSMutableDictionary (property is nonatomic, retain) where the key is a string and the value is a NSMutableArray. The class sets this dictionary in a callback from a NSOperation subclass. Everything seems to work fine until some time later (could be several minutes or several hours), the objects in the NSMutableDictionary are gone. Usually the app was in the background and brought to the foreground but it's been nearly impossible to find a reproducible test case. The problem, however, happens all the time.
How can I go about debugging this? I've seen tools for finding leaks but nothing to detect a premature release.
CODE:
#interface MyManager : NSObject
#property (nonatomic, retain) NSOperationQueue * queue;
#property (nonatomic, retain) NSMutableDictionary * allObjectsByCategory;
#property (nonatomic, retain) NSMutableDictionary * allObjectsByName;
+ (MyManager *)default;
- (void)loadWithCompletion:(void (^)(BOOL succeeded))aBlock;
#end
#implementation MyManager
#synthesize queue, allObjectsByCategory, allObjectsByName;
- (void)loadWithCompletion:(void (^)(BOOL succeeded))aBlock {
self.queue = [[NSOperationQueue alloc] init];
MyFetchObjectsOperation * op = [[MyFetchObjectsOperation alloc] init];
op.successBlock = ^(NSMutableDictionary * allByCategory, NSMutableDictionary * allByName) {
self.allObjectsByCategory = allByCategory;
self.allObjectsByName = allByName;
aBlock(YES);
};
op.failureBlock = ^(NSError * err) {
aBlock(NO);
};
[self.queue addOperation:op];
}
#end
You have to give memory allocation from your class
- (void)loadWithCompletion:(void (^)(BOOL succeeded))aBlock {
self.queue = [[NSOperationQueue alloc] init];
MyFetchObjectsOperation * op = [[MyFetchObjectsOperation alloc] init];
op.successBlock = ^(NSMutableDictionary * allByCategory, NSMutableDictionary * allByName) {
self.allObjectsByCategory = [[NSMutableDictionary alloc] initWithDictionary:allByCategory];
self.allObjectsByName = [[NSMutableDictionary alloc] initWithDictionary:allByName];
aBlock(YES);
};
op.failureBlock = ^(NSError * err) {
aBlock(NO);
};
[self.queue addOperation:op];
}
I feel enabling Zombies will be the best way to debug this and identify the reason behind premature release of the object. Here's what zombie object does (quoting from apple docs):
"Replace deallocated objects with a “zombie” object that traps any attempt to use it. When you send a message to a zombie object, the runtime logs an error and crashes. You can look at the backtrace to see the chain of calls that triggered the zombie detector."
To enable Zombies, Press CMD+Shift+,(comma) then go to diagnostics tab and tick "Enable Zombie Objects"
Click here for a Screenshot.
Hope this helps!
It seems to me that you want to use a more persistent way of storing this NSMutableDictionary. Storing it on a singleton does not guarantee that the data will be kept around forever. Just as long as the memory is allocated for your application (really your singleton itself). When the application goes into the background the OS has the ability to free this memory as needed (as #CodaFi has mentioned).
I would suggest you either store this data using Core Data or save it to a file to be read for later. There are other options as well (NSUserDefaults for example) but I'd probably have to know more about why you want to keep this data around to really know what the best approach would be.
A easy way to save this NSMutableDictionary to a file would use the following code:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *plistPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"myPlistFile.plist"];
[yourDictionary writeToFile:plistPath atomically:YES];
You could retrieve the data using the following code (assuming you use the same way of generating your plistPath from above):
NSMutableDictionary *myDictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:plistPath];
Maybe you're doing something like:
NSMutableDictionary *newDict = yourDictionary;
and then removing objects of newDict
newDict = nil;
which will also remove the values from yourDictionary.
Related
This question already has answers here:
Having trouble adding objects to NSMutableArray in Objective C
(2 answers)
Closed 8 years ago.
For some reason I can't get an array added to a nsmutabledictionary. I have it declared as a property in my .h file here:
#interface TCMExhibitFeedStore : NSObject
#property (nonatomic, strong) NSMutableDictionary *allLevels;
#property (nonatomic, strong) NSMutableDictionary *storedLevels;
+ (TCMExhibitFeedStore *)sharedStore;
- (void)fetchExhibtFeedWithCompletion:(void (^)(NSMutableDictionary *obj, NSError *err))block;
- (TCMLevel *)createLevel:(TCMLevelRemote *)l;
- (TCMExhibit *)createExhibit:(TCMExhibitRemote *)e;
- (BOOL)saveChanges;
#end
Then, I'm trying to add an empty array to it in a function in the .m file like this
[_storedLevels setObject:[[NSMutableArray alloc] init] forKey:#"levels"];
However, when I step through the application this never gets added. Looking in the debugger shows it as
_storedLevels NSMutableDictionary * 0x00000000
NSMutableDictionaries are nothing new to me. I'm just going a bit crazy trying to find my error because this all looks normal to me.
The following lines confuses me...
[_storedLevels setObject:[[NSMutableArray alloc] init] forKey:#"levels"];
Instead of above Use:
self.storedLevels = [[NSMutableDictionary alloc] init];
[self.storedLevels setObject:... forKey:#"levels"];
NOTE: Whenever you see 0x00000000
This means your object is not alloc-ated.
Therefore you need to alloc+init them, before setting any array/value to them.
0x00000000 means nil. All properties and instance variables of Objective C objects are initialized to nil. Hence, we can tell that you have never initialized your properties.
You can to initialize the dictionaries in the designated initializer for TCMExhibitFeedStore. (Or before you access them to add elements)
self.storedLevels = [NSMutableDictionary dictionary];
self.allLevels = [NSMutableDictionary dictionary];
arrayOfElements = [NSMutableArray arrayWithArray:[someObj getArray]];
and
arrayOfElements = [[NSMutableArray alloc] init];
arrayOfElements = [someObj getArray];
What's the difference?
The first arrayOfElements does not seem to lose its objects when it returns count in numberOfRowsInSection:(NSInteger)section, but the second one does. I get EXC_BAD_ACCESS when I do it the second way.
EDIT:
Can I suppose now that this is the best way,
arrayOfElements = [[NSMutableArray alloc] initWithArray:[someObj getArray]];
because I am initializing an array with the contents of whatever will be autorelease'd, and I now have a fully independent array in the current class, that is viewDidLoad, oops sorry, ViewController.
This line creates an NSMutableArray from an existing array
arrayOfElements = [NSMutableArray arrayWithArray:[someObj getArray]];
This combination first creates an NSMutableArray and then instantly discards it replacing it with what is returned by [someObj getArray]
arrayOfElements = [[NSMutableArray alloc] init]; // Create new NSMutableArray
arrayOfElements = [someObj getArray]; // Throw away the newly created array and replace with the result of [someObj getArray]
If you are not using ARC then it is purely by luck that either would work.
In both cases arrayOfElements is being assigned an autorelease'd object - which will be cleared soon (most likely the next runloop). It is only by chance that nothing else has been written over this point of memory which allows one of your implementations to still work.
If you are not using ARC then really you should update your project to be using it will handle a lot of cases like this for you.
You should definitely be using properties (not bare ivars) as this will help reduce memory issues (for non-ARC) and give a more consistent interface to your code.
In your header (or class extension) declare the property like this
#property (nonatomic, strong) NSMutableArray *arrayOfElements;
Now for ARC you can simple do
[self setArrayOfElements:[[someObj getArray] mutableCopy];
for non-ARC you can do
NSMutableArray *array = [[someObj getArray] mutableCopy];
[self setArrayOfElements:array];
[array release]; array = nil;
Also note that getArray is a bad method name.
The use of “get” is unnecessary, unless one or more values are returned indirectly.
Coding Guidelines
When you are adding objects to mutable array from another array, try this:
[arrayOfElements addObjectsFromArray: [someObj getArray]];
If you're not using ARC, you need to make sure its retained.
if (arrayOfElements) {
[arrayOfElements release];
}
arrayOfElements = [[NSMutableArray alloc] initWithArray:[someObj getArray]];
I just noticed a surprising behavior of NSArray, that's why I'm posting this question.
I just added a method like:
- (IBAction) crashOrNot
{
NSArray *array = [[NSArray alloc] init];
array = [[NSArray alloc] init];
[array release];
[array release];
}
Theoretically this code will crash. But In my case it never crashed !!!
I changed the NSArray with NSMutableArray but this time the app crashed.
Why this happens, why NSArray not crashing and NSMutableArray crashes ?
In general, when you deallocate an object the memory is not zeroed out, it’s just free to be reclaimed by whoever needs it. Therefore if you keep a pointer to the deallocated object, you can usually still use the object for some time (like you do with your second -release message). Sample code:
#import <Foundation/Foundation.h>
#interface Foo : NSObject
#property(assign) NSUInteger canary;
#end
#implementation Foo
#synthesize canary;
#end
int main(int argc, const char * argv[])
{
#autoreleasepool {
Foo *foo = [[Foo alloc] init];
[foo setCanary:42];
[foo release];
NSLog(#"%li", [foo canary]); // 42, no problem
}
return 0;
}
There are no checks against this by default, the behaviour is simply undefined. If you set the NSZombieEnabled environment value, the messaging code starts checking for deallocated objects and should throw an exception in your case, just as you probably expected:
*** -[Foo canary]: message sent to deallocated instance 0x100108250
By the way, the default, unchecked case is one of the reasons why memory errors are so hard to debug, because the behaviour might be highly non-deterministic (it depends on memory usage patterns). You might get strange errors here and there around the code, while the bug is an over-released object somewhere else. Continuing in the previous example:
Foo *foo = [[Foo alloc] init];
[foo setCanary:42];
[foo release];
Foo *bar = [[Foo alloc] init];
[bar setCanary:11];
NSLog(#"%li", [foo canary]); // 11, magic! (Not guaranteed.)
As for why is NSArray different from NSMutableArray, an empty array looks like a special beast indeed:
NSArray *foo = [[NSArray alloc] init];
NSArray *bar = [[NSArray alloc] init];
NSLog(#"%i", foo == bar); // yes, they point to the same object
So that might have something to do with it. But in general case, working with deallocated objects might do anything. It might work, it might not, it might spill your coffee or start a nuclear war. Don’t do it.
The simplest thing I can think of is that an empty NSArray is some kind of "constant" in the Foundation framework - e.g. an object similar to a NSString literal, which would have a retainCount (if you were to invoke it) of -1, and it could never be -dealloc'd.
Short Version:
I define a property with (nonatomic, retain) and assumed that the property would be retained. But unless I call retain when assigning a dictionary to the property, The app crashes with an EXEC BAD ACCESS error.
Long Version:
I have a singleton which has a dictionary. The header is defined like this
#interface BRManager : NSObject {
}
#property (nonatomic, retain) NSMutableDictionary *gameState;
+ (id)sharedManager;
- (void) saveGameState;
#end
In the implementation file, I have a method that's called in the init. This method loads a plist form the bundle and makes a copy of it in the users documents folder on the device.
- (void) loadGameState
{
NSFileManager *fileManger=[NSFileManager defaultManager];
NSError *error;
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *destinationPath= [doumentDirectoryPath stringByAppendingPathComponent:#"gameState.plist"];
NSLog(#"plist path %#",destinationPath);
if (![fileManger fileExistsAtPath:destinationPath]){
NSString *sourcePath=[[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:#"gameStateTemplate.plist"];
[fileManger copyItemAtPath:sourcePath toPath:destinationPath error:&error];
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
}else{
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:destinationPath];
}
}
Now here's how I thought this should work. In the header I define the gameState property with (nonatomic, retain). I assumed (probably incorrectly) that 'retain' meant that the gameState dictionary would be retained. However, I have another method in my singleton (saveGameState) that get's called when the AppDelegate -> 'applicationWillResignActive'.
- (void) saveGameState
{
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *plistPath = [doumentDirectoryPath stringByAppendingPathComponent:#"gameState.plist"];
[gameState writeToFile:plistPath atomically:YES];
}
This throws an EXEC BAD ACCESS error on gameState. If I modify loadGameState to retain the gameState dictionary, everything works as it should. eg:
gameState = [[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath] retain];
I'm guessing this is the correct behaviour, but why? Does (nonatomic, retain) not mean what I think it means, or is something else at play here?
I've not really grok'd memory management yet, so I stumble on this stuff all the time.
You must use the accessor:
self.gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
or (is equivalent to):
[self setGameState:[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]];
instead of
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
which only sets the ivar without any notion of property.
Where do you declare gameState as an ivar? I'm presuming you do so in the implementation.
The real problem is that in your implementation, you access gameState directly and don't actually invoke the property you've declared. To do so you must send self the appropriate message:
[self gameState]; // invokes the synthesized getter
[self setGameState:[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]]; // invokes the synthesized setter -- solves your problem
or
whatever = self.gameState; // invokes the getter
self.gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]; // invokes the synthesized setter -- solves your problem
Make sure you get around to groking that memory management literature... this is a very basic question which, according to the strict rules of StackOverflow, I shouldn't be answering. Good luck!
I have looked at numerous posts which state various ways in which to remove an object from an array correctly, but I am not sure which method is best to use in my instance. I am loading a dictionary from a plist, this dictionary contains numerous arrays, and these arrays contain another dictionary. So I have 3 storage devices setup, 1 to hold the overall dictionary, another for an array, and finally a dictionary to hold the object from the array:
Header:
NSMutableDictionary *questionsDictionary;
NSMutableArray *questionsArray;
NSDictionary *currentQuestion;
#property (nonatomic, retain) NSMutableDictionary *questionsDictionary;
#property (nonatomic, retain) NSMutableArray *questionsArray;
#property (nonatomic, retain) NSDictionary *currentQuestion;
So my first question is to do with the above, are (nonatomic, retain) the right things to use for the following code.
Next I load in my dictionary from the plist:
.m:
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"MultipleChoice.plist"];
self.questionsDictionary = [[NSDictionary dictionaryWithContentsOfFile:finalPath] retain];
I then setup my question array based upon type based upon my question type:
- (void)setupQuestionType : (NSString *)qType
{
if ([self.questionsDictionary objectForKey:qType])
{
self.questionsArray = [self.questionsDictionary objectForKey:qType];
[self pickRandomQuestion];
}
}
Finally (this is where I get the error), I want to grab the a question at random from this question category:
// Pick a random question number based upon amount of questions
int randomQuestionNum = [[NSNumber numberWithInt:(arc4random() % [self.questionsArray count])] intValue];
// Grab the dictionary entry for that question
currentQuestion = [self.questionsArray objectAtIndex:randomQuestionNum];
// Remove the question from the available questions
[self.questionsArray removeObjectAtIndex:randomQuestionNum]; (Error here)
// Set the question text
self.question.text = [currentQuestion objectForKey:kQuestionkey];
Now if I comment out the removeObjectAtIndex line then the code runs fine, and my question is displayed on the screen. This leads me to believe that it isn't a null pointer. So the logical answer points to the fact that self.questionsArray isn't a NSMutableArray. So I tried the following when setting the array:
- (void)setupQuestionType : (NSString *)qType
{
if ([self.questionsDictionary objectForKey:qType])
{
NSMutableArray *temp = (NSMutableArray *)[self.questionsDictionary objectForKey:qType];
self.questionsArray = (NSMutableArray *)temp;
[self pickRandomQuestion];
}
}
Purely to see if I could type_cast it but the error still occurs. Can anyone shed some light on what I'm doing wrong, or the best approach to take?
Don't typecast NSArray to NSMutableArray. Instead:
NSArray *temp = [self.questionsDictionary objectForKey:qType];
self.questionsArray = [NSMutableArray arrayWithArray:temp];
// code not tested.