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!
Related
With the new xcode7 Apple introduced generics and nullability to Objective-C ( Developer guide )
But it seems to be very different from what we have on swift.
Nullability:
- (nonnull NSString *)something {
return nil;
}
This should raise a warning! And you can even assign the return value of this method to a nonnull variable like:
//#property (copy, nonnull) NSString *name
obj.name = [obj something];
Generics:
Looking this example:
#property (nonatomic, strong, nonnull) NSMutableArray <UIView *> *someViews;
a warning is raised when something different from a UIView is inserted on the array
[self.someViews addObject:#"foobar"]; //<- this raises an error
but not in this case:
self.someViews = [#[#"foobar"] mutableCopy];
nor in this case:
NSString *str = [self.someViews firstObject];
So the question is, I'm using generics and nullability in a wrong way or they are far away from the Swift implementation?
self.someViews = [#[#"foobar"] mutableCopy];
mutableCopy is inherited from NSObject, where it is declared to return id. It is not declared by NSArray specifically and NSArray des not decide the return type.
NSString *str = [self.someViews firstObject];
This does give a warning for me.
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.
This is a mystery:
I'm invoking setPrimitiveValue:forKey: on an NSManagedObject. The key is a legit, persistent, modeled attribute of the object. However, setPrimitiveValue:forKey: fails, often setting the value for a different, arbitrary attribute. The docs say this behavior is expected when invoking setPrimitiveValue:forKey: for an unmodeled key. So it seems Core Data thinks the key is unmodeled.
The strange part:
When the key is hardcoded as a string literal, the primitive value is indeed set successfully. It only fails when the key is a variable. The variable I'm using happens to be passed from the keyPath argument of observeValueForKeyPath:ofObject:change:context:
The keyPath variable is the same as the string literal. isEqual: returns true and the hash values are equal. The keyPath variable is of type __NSCFString. Does anyone know why setPrimitiveValue:forKey: would behave any differently? (This behavior is on OS X 10.9.1)
An update with better information:
The misbehaving key traced back to a string loaded from a file on disk. The example below is an isolated case. If the attribute string "mainAttr" is written to disk and read back in, then setPrimitiveValue:forKey: sets the value for the wrong attribute, not "mainAttr".
Core data object:
#interface Boo : NSManagedObject
#property (nonatomic, retain) NSNumber * mainAttr;
#property (nonatomic, retain) NSNumber * attr1;
#property (nonatomic, retain) NSNumber * attr2;
#property (nonatomic, retain) NSNumber * attr3;
#end
-
#import "Boo.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSManagedObjectContext *context = managedObjectContext();
NSString *key = #"mainAttr";
// write to disk, read back in
NSString *path = [#"~/Desktop/test.txt" stringByExpandingTildeInPath];
[key writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:NULL];
key = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
Boo *boo = [NSEntityDescription insertNewObjectForEntityForName:#"Boo" inManagedObjectContext:context];
[boo setPrimitiveValue:#(5) forKey:key];
NSLog(#"Boo: %#", boo);
}
return 0;
}
You need the below 3 statements to set the value. Try it.
[self willChangeValueForKey:key];
[boo setPrimitiveValue:#(5) forKey:key];
[self didChangeValueForKey:key];
I can pass basic data between classes, but when I try to pass a NSString* from my UIApplicationDelegate I get an EXC_BAD_ACCESS / NSZombie.
Is there something special I have to do to return an NSObject? Does this have to do with threading? (I thought the atomic setting on the property would take care of that?)
AppDelegate.h:
#interface AppDelegate : NSObject <UIApplicationDelegate> {
NSString * currentNoteName;
}
#property (atomic, assign) NSString *currentNoteName;
#end
AppDelegate.m:
- (void)timerCallback:(NSTimer *)timer {
currentNoteName = [NSString stringWithCString:(tone->freq).c_str() encoding:NSUTF8StringEncoding];
// This works:
NSLog(#"Current Note Name in timerCallback: %#", currentNoteName);
OtherObject.m:
// Returns a Zombie object & EXC_BAD_ACCESS:
NSString *currentNoteName = [appDelegate currentNoteName];
If not using ARC, you must using retain property:
#property (atomic, retain) NSString *currentNoteName;
and assign a value for it, using setter:
self.currentNoteName = [NSString stringWithCString: ...];
and don't forget to release instance of this ivar in your dealloc implementation of AppDelegate:
- (void) dealloc {
[currentNoteName release], currentNoteName = nil;
[super dealloc];
}
you are assigning a value and autoreleasing the NSString instance. Use retain instead.
The probleis is "assign", because the string from " [NSString stringWithCString" is auto-released.
Maybe u can change it to "copy" or "retain". (i think copy is better).
I archive an array (NSMutableArray) of custom objects that implement the .
Once i load it froma file to a retaining property
#property (nonatomic, retain) NSMutableArray *buddies;
the release count of the object is 2 (correct, it's 1of autorelease + 1 of retain of the property) but then noone releases it and the retain count becames 1, so when i release it i get
-[__NSArrayM retainCount]: message sent to deallocated instance
(i think because the 1 retain count is the autorelease)
Here's the full code:
BuddieListViewController.h
#import <UIKit/UIKit.h>
#import "Buddie.h"
#interface BuddieListViewController : UITableViewController {
IBOutlet NSMutableArray *buddies;
[...]
}
[...]
#property (nonatomic, retain) NSMutableArray *buddies;
[...]
#end
BuddieListViewController.m
#import "BuddieListViewController.h"
#import "Buddie.h"
#import "PreviewViewController.h"
#implementation BuddieListViewController
#synthesize buddies;
[...]
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
[self loadFromDisk];
}
return self;
}
- (void)loadFromDisk {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *appFile = [documentsPath stringByAppendingPathComponent:#"BuddieArchive.ark"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:appFile]) {
self.buddies = [NSKeyedUnarchiver unarchiveObjectWithFile:appFile];
NSLog(#"1- buddies retain count %d (should be 2, 1 + 1autorelease)", [buddies retainCount]);
} else {
self.buddies = [NSMutableArray arrayWithCapacity:1];
}
}
[...]
- (IBAction)cancelledBuddie:(NSNotification *)notification
{
[editingBuddie release];
NSLog(#"2- buddies retain count %d (should be 2, 1 + 1autorelease)", [buddies retainCount]);
[buddies release];
[self loadFromDisk];
[self.tableView reloadData];
}
Has anyone some idea of why this happens?
I can't say it better than this:
The number returned by retainCount is
useless.
Don't rely on it for anything. Use the Leaks tool in Instruments to determine if you're leaking objects.
If you're crashing, it's most likely that you have a zombie. See this video to find out how to use Instruments to find zombies.
If you need to nullify the array, use the property accessor to set it to nil:
self.buddies = nil;
The synthesized implementation takes care of the memory management issues. Try to avoid sending -retain/-release messages directly to instance variables wherever possible and instead allow the property accessors to take care of things for you. It'll save you a lot of trouble.
Rather than releasing buddies why not just do a [self.buddies removeAllObjects] at the beginning of loadFromDisk.