Objective-c singleton object does not working as expected - iphone

I have a problem. There is class to store game progress:
struct GameData {
PackData & packDataById(LEVEL_PACK packId);
int gameVersion;
AudioData audio;
PackData sunrise;
PackData monochrome;
PackData nature;
};
//singleton
#interface GameDataObject : NSObject <NSCoding>
{
GameData data_;
}
+(GameDataObject*) sharedObject;
-(id) initForFirstLaunch;
-(GameData*) data;
-(void) save;
#end
and implementation:
#implementation GameDataObject
static GameDataObject *_sharedDataObject = nil;
+ (GameDataObject*) sharedObject
{
if (!_sharedDataObject) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *encodedObject = [defaults objectForKey:Key];
if (!encodedObject) {
_sharedDataObject = [[GameDataObject alloc] initForFirstLaunch];
}
else {
_sharedDataObject = (GameDataObject*)[NSKeyedUnarchiver unarchiveObjectWithData: encodedObject];
}
}
return _sharedDataObject;
}
-(GameData*) data
{
return &data_;
}
-(id) initForFirstLaunch
{
self = [super init];
if (self) {
data_.audio.reset();
data_.sunrise.reset();
data_.monochrome.reset();
data_.nature.reset();
data_.gameVersion = 1;
data_.sunrise.levelData[0].state = LEVEL_OPENED;
}
return self;
}
-(void) save
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[NSKeyedArchiver archivedDataWithRootObject:self] forKey:Key];
[defaults synchronize];
}
-(void) encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeInt:data_.gameVersion forKey:#"game-version"];
[encoder encodeBytes:(uint8_t*)&data_.audio length:sizeof(AudioData) forKey:#"audio-data"];
[encoder encodeBytes:(uint8_t*)&data_.sunrise length:sizeof(PackData) forKey:#"sunrise-pack"];
[encoder encodeBytes:(uint8_t*)&data_.monochrome length:sizeof(PackData) forKey:#"monochrome-pack"];
[encoder encodeBytes:(uint8_t*)&data_.nature length:sizeof(PackData) forKey:#"nature-pack"];
}
-(id) initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self) {
data_.gameVersion = [decoder decodeIntForKey:#"game-version"];
NSUInteger length = 0;
{
const uint8_t *buffer = [decoder decodeBytesForKey:#"audio-data" returnedLength:&length];
assert(length);
memcpy(&data_.audio, buffer, length);
}
{
const uint8_t *buffer = [decoder decodeBytesForKey:#"sunrise-pack" returnedLength:&length];
assert(length);
memcpy(&data_.sunrise, buffer, length);
}
{
const uint8_t *buffer = [decoder decodeBytesForKey:#"monochrome-pack" returnedLength:&length];
assert(length);
memcpy(&data_.monochrome, buffer, length);
}
{
const uint8_t *buffer = [decoder decodeBytesForKey:#"nature-pack" returnedLength:&length];
assert(length);
memcpy(&data_.nature, buffer, length);
}
}
return self;
}
#end
It loads and saves itself correctly when save is called directly after initialization and nothing more is done.
But when I try a simple thing. I write in appDidFinishLaunching
GameDataObject *obj = [GameDataObject sharedObject];
Then everything is done - just one simple menu is loaded, and I minimize the application so
-(void) applicationDidEnterBackground:(UIApplication*)application
{
[[CCDirector sharedDirector] stopAnimation];
[[GameDataObject sharedObject] save];
}
is executed. And in this method obj is totally corrupted (before saving), sometimes it's even seen with debugger as another class object.
What am I doing wrong?
EDIT
Just launching the app and minimizing it causes the same problem.

As I already mentioned in the comment, you have a memory bug when unarchiving the data using NSKeyedUnarchiver.
The method +[NSKeyedUnarchiver unarchiveObjectWithData:] returns an autoreleased object (you can tell from the naming convention: it doesn't contain either new, alloc or copy) so you'd have to take ownership of the object by sending it a retain message. Now the object won't be released by the autorelease pool at the end of the runloop.

While you have an answer, your overall app architecture could use a bit of refinement.
Notably, it is generally quite fragile to have some massive amount of persistent logic associated with an arbitrary singleton's instantiation. It introduces all kinds of weird ordering dependencies or other mechanisms via which a seemingly minor change can cause your code to break.
A far less fragile pattern is to associate reconstruction of state with known points in an application's lifespan. I.e. if the state is required for the app to work, load the state in applcationDidFinishLaunching:. If the state is only required by a subsystem, load it when the subsystem is loaded.
Doing so reduces complexity and, by implication, reduces the maintenance costs of your code. Any indeterminism you can eliminate is a future bug removed.

When reading an archived shared object, you must retain it when assigning to your singleton variable. NSCoder methods for unarchiving always return autoreleased objects.

Related

encodeWithCoder is not called in derived class of the NSMutableDictionary

Basically I am using some open source code called OrderedDictionary that is derived from NSMutableDictionary. Then I want to save the ordered dictionary data to NSUserDefaults by adding encode and decode method to the OrderedDictionary class. However, I realized the encode and decode methods are not being called, as a result, the decoded dictionary is no longer ordered. Below is my code:
#interface OrderedDictionary : NSMutableDictionary <NSCopying, NSCoding>
{
NSMutableDictionary *dictionary;
NSMutableArray *array;
}
In the implementation file:
/**
* encode the object
**/
- (void)encodeWithCoder:(NSCoder *)coder
{
[super encodeWithCoder:coder];
[coder encodeObject:dictionary forKey:#"dictionary"];
[coder encodeObject:array forKey:#"array"];
}
/**
* decode the object
*/
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self != nil)
{
dictionary = [coder decodeObjectForKey:#"dictionary"];
array = [coder decodeObjectForKey:#"array"];
}
return self;
}
Quick example code for using this:
dictionary = [[OrderedDictionary alloc] init];
[dictionary setObject:#"one" forKey:#"two"];
[dictionary setObject:#"what" forKey:#"what"];
[dictionary setObject:#"7" forKey:#"7"];
NSLog(#"Final contents of the dictionary: %#", dictionary);
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"myDictionary"] == nil)
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:dictionary]
forKey:#"myDictionary"];
}
else
{
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *savedDictionary = [currentDefaults objectForKey:#"myDictionary"];
if (savedDictionary != nil)
{
OrderedDictionary *oldData = [NSKeyedUnarchiver unarchiveObjectWithData:savedDictionary];
if (oldData != nil)
{
NSLog(#"Final contents of the retrieved: %#", oldData);
}
}
}
The thing is, the final retrievedDictionary does NOT have the original data order and the encode and decode methods are not called at all.
Thanks for any help in advance! :)
There's an NSObject method called -classForCoder that you need to override in OrderedDictionary. From the docs:
classForCoder
Overridden by subclasses to substitute a class other than its own during coding.
-(Class)classForCoder
Return Value
The class to substitute for the receiver's own class during coding.
Discussion
This method is invoked by NSCoder. NSObject’s
implementation returns the receiver’s class. The private subclasses of
a class cluster substitute the name of their public superclass when
being archived.
That last line is the key. So, in OrderedDictionary.m:
- (Class)classForCoder
{
return [self class]; // Instead of NSMutableDictionary
}
Also, if you're not using ARC, make sure you retain the objects coming back from -decodeObjectForKey. As rob mayoff mentions below, you shouldn't call [super initWithCoder:] (or [super encodeWithCoder:'). I also changed the key strings to avoid collisions.
- (id)initWithCoder:(NSCoder *)coder
{
if (self != nil)
{
dictionary = [[coder decodeObjectForKey:#"OrderedDictionary_dictionary"] retain];
array = [[coder decodeObjectForKey:#"OrderedDictionary_array"] retain];
}
return self;
}
You are possibly creating a new OrderedDictionary with the wrong initializer, initWithDictionary:. Try this instead:
OrderedDictionary *oldData = [NSKeyedUnarchiver unarchiveObjectWithData: savedDictionary];
if (oldData != nil)
{
NSLog(#"Final contents of the retrieved: %#", oldData);
}
Make sure initWithDictionary: expects OrderedDictionary as an argument. My guess is that it expects an NSDictionary.
Edit: the code to save the defaults should include something like this:
OrderedDictionary *myDict = ...;
NSData* data = [NSKeyedArchiver archivedDataWithRootObject: myDict];
[[NSUserDefaults standardDefaults] setObject: data forKey: #"myDictionary"];

Problem with saving custom object into NSUserDefaults

I have a custom object class with the following .m:
#implementation FolderObject
#synthesize folderTitle, folderContents; //title is NSString, contents is array
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:foldersContents forKey:#"foldersContents"];
[coder encodeObject:folderTitle forKey:#"folderTitle"];
}
- (id)initWithCoder:(NSCoder *)coder {
self = [[FolderObject alloc] init];
if (self != nil)
{
foldersContents = [coder decodeObjectForKey:#"foldersContents"];
folderTitle = [coder decodeObjectForKey:#"folderTitle"];
}
return self;
}
#end
Here is how I use the folder (in some other class):
FolderObject *newFolder=[FolderObject alloc];
newFolder.folderTitle=[textView text];
newFolder.folderContents=[[NSMutableArray alloc]init];
[folders addObject:newFolder];
Here is how I save and retrieve the custom object from NSUserDefaults:
-(void)viewDidDisappear:(BOOL)animated
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:folders] forKey:#"folders"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
-(void)viewWillAppear:(BOOL)animated
{
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArrayFolders = [currentDefaults objectForKey:#"folders"];
if (dataRepresentingSavedArrayFolders != nil)
{
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArrayFolders];
if (oldSavedArray != nil) {
folders = [[NSMutableArray alloc] initWithArray:oldSavedArray];
}
else {
folders = [[NSMutableArray alloc] init];
}
}
else folders=[[NSMutableArray alloc] init];
And finally here is where the issue is:
FolderObject *newFolder=[folders objectAtIndex:indexPath.row];
[folderTitleLabel setText:newFolder.folderTitle];
On the second line, I get an error during runtime, but only after I exit the app and come back. When I add objects to the folders array and call the above, no problems. But if I exit the app and come back, then problems:
-[__NSArrayM isEqualToString:]: unrecognized selector sent to instance 0x4b9e400
2011-09-02 08:09:24.290 MyApp[43504:b303] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayM isEqualToString:]: unrecognized selector sent to instance 0x4b9e400'
Inside of initWithCoder: for your FolderObject you are not properly retaining the values which will result in your crash. Make sure you use the property like this.
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self != nil)
{
//dot notation (self.) will properly retain or copy your string
//as long as it is declared as retain or copy (I recommend copy)
self.foldersContents = [coder decodeObjectForKey:#"foldersContents"];
self.folderTitle = [coder decodeObjectForKey:#"folderTitle"];
}
return self;
}
The problem seems to be in your initWithCoder: method:
foldersContents = [coder decodeObjectForKey:#"foldersContents"];
folderTitle = [coder decodeObjectForKey:#"folderTitle"];
The values returned by decodeObjectForKey: are not retained for you, and as you are assigning them to ivars directly instead of via the (presumably) retain-declared properties, they are not being retained there either. So they get auto-released the next time the autorelease pool is drained. By the time you get around to trying to use it, it just so happens that the memory location formerly used for the title is now occupied by an __NSArrayM object; were things to work out slightly differently, you'd get a more straightforward EXC_BAD_ACCESS crash.
#Joe is right about needing to retain the objects but why are you using
self = [[FolderObject alloc] init];
instead of
self = [super init];
Your code will leak an chunk of memory each time it's called?
I don't know if it's causing your crash (I suspect not) but it's certainly interesting.

class variables in objective-c and memory management

#implementation ProductController
NSString *areaName = nil;
+ (void)setAreaName:(NSString *)areaName_ {
areaName = areaName_;
}
#end
and
#implementation ProductController
NSString *areaName = nil;
+ (void)setAreaName:(NSString *)areaName_ {
if(areaName_ != areaName) {
[areaName release];
areaName = [areaName_ copy];
}
}
- (void)dealloc {
[areaName release];
}
#end
Now which one is correct?and why?
As you seem to understand, there are no "class variables" in Obj-C. The workaround is just a C-style (global, or file-scoped) variable that you set up similarly to how you've shown above. First off, you should use file scope for these variables by marking them with the static keyword:
static NSString *areaName = nil;
You might also consider using a convention like FirstLetterUppercase to indicate the scope difference.
As for memory management, you can treat it exactly like an instance variable, but one that never goes away forever:
static NSString *AreaName = nil;
+ (void)setAreaName:(NSString *)name {
if (![name isEqualToString:AreaName]) {
[AreaName release];
AreaName = [name copy];
}
}
Note that in your second example, you should NOT release the "class" variable from an instance's -dealloc method. If you have more than one instance of the object, this leaves a bad dangling pointer, and defeats the purpose of the "class" variable anyways. Generally, when you use this pattern, you'll "leak" (for some definition of leak) the class variable value, and that's OK.
class variables are generally bad style.
nevertheless, an alternative to other answers would be to create a static dictionary for your lib/app's class variables. a very primitive implementation would take this form:
// MONLibraryClassVariable.h
extern id MONLibraryClassVariableGetObjectForKey(NSString * key);
extern void MONLibraryClassVariableSetObjectForKey(id<NSObject> object, NSString * key);
// MONLibraryClassVariable.m
/* #todo make all this thread safe */
static NSMutableDictionary * MONLibraryClassVariables_ = nil;
id MONLibraryClassVariableGetObjectForKey(NSString * key) {
return [MONLibraryClassVariables_ objectForKey:key];
}
void MONLibraryClassVariableSetObjectForKey(id<NSObject> object, NSString * key) {
if (nil == MONLibraryClassVariables_) {
MONLibraryClassVariables_ = [NSMutableDictionary new];
}
[MONLibraryClassVariables_ setObject:object forKey:key];
}
// ProductController.m
static NSString * const ProductController_KEY_areaName = #"ProductController.areaName";
#implementation ProductController
+ (void)setAreaName:(NSString *)inAreaName {
MONLibraryClassVariableSetObjectForKey([[inAreaName copy] autorelease], ProductController_KEY_areaName);
}
- (void)dealloc {
// nope [areaName release];
[super dealloc];
}
#end

#dynamic properties and its usage?

Can anyone give me clear picture about dynamic property and its usage? y not use the usual #property everywhere?
Dynamic properties are used when you don't provide an implementation at compile time, but ensure that one exists at runtime. Being a dynamic language, Objective-C can respond to messages at runtime, even if the class doesn't have an implementation at compile time.
Here's a contrived example: Let's say you have a Book class, backed by an NSMutableDictionary that contains the keys title and author. However, you want Book to respond to title and author as well, and have them as properties; title and author will grab the appropriate value from the dictionary, and setTitle: and setAuthor: will change the value stored in the dictionary. You could do so with this code:
#import <Foundation/Foundation.h>
#interface Book : NSObject
{
NSMutableDictionary *data;
}
#property (retain) NSString *title;
#property (retain) NSString *author;
#end
#implementation Book
#dynamic title, author;
- (id)init
{
if ((self = [super init])) {
data = [[NSMutableDictionary alloc] init];
[data setObject:#"Tom Sawyer" forKey:#"title"];
[data setObject:#"Mark Twain" forKey:#"author"];
}
return self;
}
- (void)dealloc
{
[data release];
[super dealloc];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
NSString *sel = NSStringFromSelector(selector);
if ([sel rangeOfString:#"set"].location == 0) {
return [NSMethodSignature signatureWithObjCTypes:"v#:#"];
} else {
return [NSMethodSignature signatureWithObjCTypes:"##:"];
}
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
NSString *key = NSStringFromSelector([invocation selector]);
if ([key rangeOfString:#"set"].location == 0) {
key = [[key substringWithRange:NSMakeRange(3, [key length]-4)] lowercaseString];
NSString *obj;
[invocation getArgument:&obj atIndex:2];
[data setObject:obj forKey:key];
} else {
NSString *obj = [data objectForKey:key];
[invocation setReturnValue:&obj];
}
}
#end
int main(int argc, char **argv)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Book *book = [[Book alloc] init];
printf("%s is written by %s\n", [book.title UTF8String], [book.author UTF8String]);
book.title = #"1984";
book.author = #"George Orwell";
printf("%s is written by %s\n", [book.title UTF8String], [book.author UTF8String]);
[book release];
[pool release];
return 0;
}
Note the the methods are "created" at runtime via forwardInvocation:; hence, title and author are dynamic properties.
(This isn't the best example, but I think it gets the point across.)
#dynamic thing; is merely a way to inform the system not to generate getters/setters for the thing, that you (or someone else) will provide them for you—As in, they'll be there at runtime.
This is in contrast to #synthesize which tells the compiler to generate the getter/setter (as appropriate) for you.
#dynamic is (in my experience) used primarily in conjunction with Core Data and subclasses of NSManagedObject. To quote Marcus Zarra's Core Data,
By declaring them
[attributes/relationships], we are
telling the compiler to ignore any
warnings associated with there
properties because we "promise" to
generate them at runtime. Naturally,
if the turn up missing at runtime,
then our application is going to
crash.

How to define a global variable of type NSUserDefaults and initialize a value?

which is the cleanest way to use something like a global variable? Normally, using a global variable is forbidden, but I don't know a better solution for accessing NSUserDefaults from different classes.
I read a bit and come up with this. I define a Contants.h and a Constants.m file and include them everywhere I need to.
//Constants.h
#import <Foundation/Foundation.h>
#interface Constants : NSObject {
extern NSUserDefaults *settings;
}
#end
.
//Constants.m
#implementation Constants
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"plist"];
NSDictionary *settingsDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
[[NSUserDefaults standardUserDefaults] registerDefaults:settingsDict];
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
#end
The problem here is that I want to initialize a value to my constant. I have no method in Constants.m. So my helper variables would also be globals?
One thing to mention: I think the global variable also has to be released?
Thanks for your help!
Edit:
#hotpaw2:
AppBundleSingleton.h:
#import <Foundation/Foundation.h>
#interface AppBundleSingleton : NSObject {
}
+ (AppBundleSingleton *)sharedAppBundleSingleton;
#end
AppBundleSingleton.m:
#import "AppBundleSingleton.h"
static AppBundleSingleton *sharedAppBundleSingleton = nil;
#implementation AppBundleSingleton
#pragma mark -
#pragma mark Singleton methods
+ (AppBundleSingleton *)sharedAppBundleSingleton {
#synchronized(self) {
if (sharedAppBundleSingleton == nil) {
sharedAppBundleSingleton = [[self alloc] init];
}
}
return sharedAppBundleSingleton;
}
+ (id)allocWithZone:(NSZone *)zone {
#synchronized(self) {
if (sharedAppBundleSingleton == nil) {
sharedAppBundleSingleton = [super allocWithZone:zone];
return sharedAppBundleSingleton; // assignment and return on first allocation
}
}
return nil; // on subsequent allocation attempts return nil
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (NSUInteger)retainCount {
return NSUIntegerMax; //denotes an object that cannot be released
}
- (void)release {
//do nothing
}
- (id)autorelease {
return self;
}
-(id)init {
self = [super init];
sharedAppBundleSingleton = self;
// Initialization code here
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"plist"];
NSDictionary *settingsDict = [NSDictionary dictionaryWithContentsOfFile:filePath];
[[NSUserDefaults standardUserDefaults] registerDefaults:settingsDict];
return self;
}
#end
In my AppDelegate.m I have the following:
// ...
#include "AppBundleSingleton.h"
#implementation MyAppDelegate
// ...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Add the navigation controller's view to the window and display.
[window addSubview:navigationController.view];
[window makeKeyAndVisible];
[AppBundleSingleton sharedAppBundleSingleton];
return YES;
}
// ...
#end
In my ViewController I query the values:
NSString *myString = [[NSUserDefaults standardUserDefaults] stringForKey:#"myKeyforString"];
Would that be a solution?
You don't need to use global variables - you can access the userdefaults from any class or object within your project like this:
BOOL foo = [[NSUserDefaults standardUserDefaults] boolForKey:#"bar"];
As long as you synchronize the userdefaults after every change you make, the data fetched in this way is consistent.
Global variables are not only NOT forbidden, but a required part of the ANSI C specification, which is a subset of Obj C. Both gcc and llvm fully support globals. They are often the smallest and fastest way to pass unprotected values around.
That said, explicit use of global variables are most probably not the solution to your problem. You have a problem quite well suited to the MVC paradigm. Place all your NSDefault code into a singleton model class (an M of the MVC), which can self-initialize on first access (the implementation may use a hidden global), and attach that object to the appDelegate where it can easily be obtained from anywhere. Then encapsulate all you default value read/writes as properties of that object.