Wierd behaviour of a singleton object - iphone

I'm having really weird situation. I create a singletone object of a class named "Profile like this:
static Profile *currentProfile;
+ (Profile *)currentProfile
{
#synchronized(self)
{
if (currentProfile == nil)
currentProfile = [[Profile alloc] init];
}
return currentProfile;
}
- (id)init
{
self = [super init];
if (self)
{
// Initialization code here.
isChecked = [[[NSUserDefaults standardUserDefaults] objectForKey:#"isChecked"] boolValue];
if (isChecked)
{
NSLog(#"isChecked block is called");
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"myEncodedObjectKey"];
self = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
[self retain];
for (int i = 0; i < self.avatar.count; i++)
[self.avatar replaceObjectAtIndex:i withObject:[UIImage imageWithData:[self.avatar objectAtIndex:i]]];
}
else
{
password = #"";
pfefferID = #"";
email = #"";
avatar = [[NSMutableArray alloc] initWithObjects:
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],
[UIImage imageNamed:#"empty_image.png"],nil];
isBoy = YES;
friends = [[NSMutableDictionary alloc] init];
rating = 0;
}
}
return self;
}
In init method i check a certain condition stored in NSUserDefaults by using BOOL variable named "isChecked". isChecked is equal to YES and everything works fine. But... i create this Profile object in AppDelegate file like this
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
users = [[NSMutableDictionary alloc] init];
usersForLeaderboardFromServer = [[NSMutableDictionary alloc] init];
listOfFriendsFromServer = [[NSMutableDictionary alloc] init];
currentProfile = [Profile currentProfile];
sessionID = 0;
if (!currentProfile.isChecked)//why????
NSLog(#"not checked");
if (currentProfile.isChecked)
{
[self getUsersForLeaderboardFromServer];
MainMenuView *mainMenu = [[[MainMenuView alloc] init] autorelease];
[self.navigationController pushViewController:mainMenu animated:YES];
}
}
So the same isChecked variable which a moment (far less than a moment actually) ago was equal to YES gets equal to NO when being used in application didFinishLaunchingWithOptions method by accessing it via dot. What's going on? I'm able to handle it but i'm just curious about this situation. Do you know what's wrong with it?

You're reassigning self in init, so you're returning the new object rather than the one you set isChecked on. See this code:
self = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
[self retain];
It's slightly awkward to do things like you've got - I would certainly not recommend replacing it in the way you have. For a start, the value you set to the static currentProfile is not being updated when you reassign self so that's still the old one. And also you're not releasing the old self when you reassign.
To fix it you could do something like this:
id newSelf = (Profile *) [NSKeyedUnarchiver unarchiveObjectWithData:data];
newSelf.isChecked = isChecked;
[self release];
self = [newSelf retain];
But I don't really advocate that personally. I suggest you load in the object from your archive and then proceed to update yourself with it rather than reassigning self.

Related

Populating NSDictionary and NSArrays for Model data

I'm trying to create an NSDictionary full of arrays in the implementation file of my model but my code hasn't worked yet. I want to create arrays that are lists of types of dogs and cats and then add those arrays to a dictionary with keys called DOG and CAT. Here is my code:
#implementation wordDictionary
#synthesize catList = _catList;
#synthesize dogList = _dogList;
#synthesize standardDictionary =_standardDictionary;
- (void)setCatList:(NSMutableArray *)catList
{
self.catList = [NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil];
}
- (void)setDogList:(NSMutableArray *)dogList
{
self.dogList = [NSMutableArray arrayWithObjects:#"pit bull", #"pug", #"chihuahua", nil];
}
-(void)setStandardDictionary:(NSMutableDictionary *)standardDictionary
{
[self.standardDictionary setObject: _catList forKey:#"CAT"];
[self.standardDictionary setObject: _dogList forKey:#"DOG"];
}
- (NSString*)selectKey
{
NSInteger keyCount = [[self.standardDictionary allKeys] count];
NSInteger randomKeyIndex = arc4random() % keyCount;
NSString *randomKey = [[self.standardDictionary allKeys] objectAtIndex:randomKeyIndex];
return randomKey;
}
#end
This code is the model. The model is hooked up to my view controller such that when a user taps a button, the NSString returned from randomKey is displayed in a label on the screen. So the text will read either CAT or DOG. Here's the code for that:
- (IBAction)changeGreeting:(UIButton*)sender {
NSString *chosenKey = [self.dictionary selectKey];
NSString *labelText = [[NSString alloc] initWithFormat:#"%#", chosenKey];
self.label.text = labelText;
}
Unfortunately when I tap the button on the simulator I get an error message saying: Thread 1:EXC_ARITHMETIC (code=EXC_1386_DIV, subcode=0x0) at NSInteger randomKeyIndex = arc4random() % keyCount; and it appears that I'm getting it because neither my NSArray nor my NSDictionary have any objects inside of them.
Does anyone have any idea why my NSArray and NSDictionary haven't been populated?
Thanks very much.
The simple answer is that there isn't any code here that calls the methods to set the arrays or dictionary.
But the real underlying issue is that there are a couple of bad 'patterns' going on here that you should fix:
In your setter methods (setCatList:, setDogList:, setStandardDictionary:) you're not setting the properties in question to the values that are passed in. For example, you should be setting catList to the passed in "catList" variable.
- (void)setCatList:(NSMutableArray *)catList
{
if (_catList != catList) {
[_catList release];
_catList = [catList retain];
}
}
Then you should have some kind of "setup" happening, usually in a method in the view controller like viewDidLoad:
[wordDictionary setCatList:[NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil]];
// and more for the other two setters
Alternately, you can set these default values in the init for the wordDictionary class:
- (id)init {
self = [super init];
if (self) {
[self setCatList:[NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil]];
}
return self;
}
The former is better in most cases, but you may have a good reason to pre-populate your model for all instances of the class.
Assuming you called setCatList:, setDogList: and setStandardDictionary: before. Probably that causing is this :
NSString *chosenKey = [self.dictionary selectKey];
change into this :
NSString *chosenKey = [self selectKey];
UPDATE
I'm trying to make your life easier. no need to create your object if you don't need the most.
- (NSMutableArray*)getCatList
{
return [NSMutableArray arrayWithObjects:#"lion", #"puma", #"snow leopard", nil];
}
- (NSMutableArray*)getDogList
{
return [NSMutableArray arrayWithObjects:#"pit bull", #"pug", #"chihuahua", nil];
}
-(NSMutableDictionary*)getStandardDictionary
{
NSMutableDictionary *standardDictionary = [NSMutableDictionary new];
[standardDictionary setObject:[self getCatList] forKey:#"CAT"];
[standardDictionary setObject:[self getDogList] forKey:#"DOG"];
return [standardDictionary autorelease];
}
- (NSString*)selectKey
{
NSMutableDictionary *standardDictionary = [self getStandardDictionary];
NSInteger keyCount = [[standardDictionary allKeys] count];
NSInteger randomKeyIndex = arc4random() % keyCount;
NSString *randomKey = [[standardDictionary allKeys] objectAtIndex:randomKeyIndex];
return randomKey;
}
- (IBAction)changeGreeting:(UIButton*)sender {
// NSString *chosenKey = [self selectKey];
//NSString *labelText = [[NSString alloc] initWithFormat:#"%#", chosenKey];
self.label.text = [self selectKey]; //no need to convert it to NSString again
}
Two things to consider:
I don't see you calling these:
setCatList:(NSMutableArray*)catList;
setDogList:(NSMutableArray*)dogList;
You use self.catList and self.dogList, but neither of those are synthesized, instead you have beatList and meList synthesized
Change the synthesizes to the catList and dogList, and make sure you call the set list methods, and then you should make some progress.

Adding custom defined objects to NSMutableArray overwrite the whole array

-(id) initWithData:(NSString *)lastName: (NSString *)firstName{
self->firstname = firstName;
self->lastname = lastName;
return self;
}
-(void) printData {
NSLog(#"firstname: %#", firstname);
NSLog(#"lastname: %#", lastname);
}
so whenever I create a new object using the above init function. And Add objects to a NSMutableArray, using the addObject function.
NSMutableArray *objectArray = [[NSMutableArray alloc] init];
CustomObject *tempObject = [[CustomObject alloc] initWithData: #"smith": #"john"];
CustomObject *tempObjectb = [[CustomObject alloc] initWithData: #"brown": #"william"];
[objectArray addObject:tempObject];
[objectArray addObject:tempObjectb];
[[objectArray objectAtIndex:0] printData];
[[objectArray objectAtIndex:1] printData];
objects at index 1, and 0 always equal the whichever object was added to the array last.
This also happens if I use a for loop, or have more than 2 objects, all values when printed, turn to the values of the last added object to the objectArray. Let me know if there is any information that I am missing.
Is there something that I am missing?
Fix your initWithData:lastName: implementation as following:
-(id) initWithData:(NSString *)lastName: (NSString *)firstName
{
self = [super init];
if ( nil != self ) {
self->firstname = [firstName retain];
self->lastname = [lastName retain];
}
return self;
}

How to solve Memory leaks for following Sqlite code?

I am getting memory leaks in Instruments in the following Sqlite Code.
NSArray *result = [self executeQuery:sql arguments:argsArray];
It calls following method.
- (NSArray *)executeQuery:(NSString *)sql arguments:(NSArray *)args {
sqlite3_stmt *sqlStmt;
if (![self prepareSql:sql inStatament:(&sqlStmt)])
return nil;
int i = 0;
int queryParamCount = sqlite3_bind_parameter_count(sqlStmt);
while (i++ < queryParamCount)
[self bindObject:[args objectAtIndex:(i - 1)] toColumn:i inStatament:sqlStmt];
NSMutableArray *arrayList = [[NSMutableArray alloc] init];
int columnCount = sqlite3_column_count(sqlStmt);
while ([self hasData:sqlStmt]) {
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
for (i = 0; i < columnCount; ++i) {
id columnName = [self columnName:sqlStmt columnIndex:i];
id columnData = [self columnData:sqlStmt columnIndex:i];
[dictionary setObject:columnData forKey:columnName];
}
[arrayList addObject:[dictionary autorelease]];
}
sqlite3_finalize(sqlStmt);
return arrayList;
}
How do I solve it ?
We'd need to see the code of your executeQuery method - it should be returning an auto-released result, but perhaps it isn't.
You could try ;
NSArray *result = [[self executeQuery:sql arguments:argsArray] autorelease];
But I'd be wary of just blindly trying that without actually seeing what executeQuery does in detail.
EDIT:
OK, here's your problem;
NSMutableArray *arrayList = [[NSMutableArray alloc] init];
Either create it as an auto-released array, or finish the method with;
return [arrayList autorelease];

Setting dictionary in singleton causing EXC_BAD_ACCESS

I'm having issues with a singleton I've created. It contains two NSMutableDictionary's, which are read and used in three views (and some modal views) throughout the app.
I've added an MKMapView t plot some of the venues inside the dictionaries on a map. When I use the exact same method/function used in every other view to access the data, I receive an EXC_BAD_ACCESS error pertaining to a deallocated dictionary. This comes from NSZombieEnabled:
CFDictionary retain: message sent to deallocated instance
In a dsym'ed trace, it is the replacement of one dictionary with another that is causing grief. The code I'm using to call the function comes from a MKAnnotationView click:
UIControl *tempButton = sender;
NSString *selectedEventsString = [self.eventsArray objectAtIndex:tempButton.tag];
NSLog(#"eventString: %#", selectedEventsString);
[[EventsManager eventsManager] changeSelectedEventsDictionaryTo:selectedEventsString];
[tempButton release];
[selectedEventsString release];
"selectedEventsString" is coming out to a perfectly corresponding event.
The corresponding code in EventsManager:
-(void)changeSelectedEventsDictionaryTo:(NSString *)eventName {
NSLog(#"singleton: %#", eventName);
self.eventString = eventName;
self.selectedEventsDictionary = [self.eventsDictionary objectForKey:eventName];
}
Both selectedEventsDictionary and eventsDictionary are set as #property (nonatomic, retain) in the .H file, and this is the init function:
+ (EventsManager*)eventsManager {
if (eventsManager == nil) {
eventsManager = [[super allocWithZone:NULL] init];
eventsManager.eventsDictionary = [[NSMutableDictionary alloc] init];
eventsManager.selectedEventsDictionary = [[NSMutableDictionary alloc] init];
eventsManager.eventString = [[NSString alloc] init];
eventsManager.mode = [[NSString alloc] init];
}
return eventsManager;
}
This is an example of code used in other views that works fine:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
NSString *eventString = [self.eventsArray objectAtIndex:row];
[[EventsManager eventsManager] changeSelectedEventsDictionaryTo:eventString];
//Modal display code here
}
Any help would be greatly appreciated! I think I've provided all relevant code but let me know if more is needed.
Cheers!
Where to start! I will point out some things that I do see wrong.
First Example. Do not release tempButton and selectedEventString as you never explicitly called retain/copy or alloc and init on them.
UIControl *tempButton = sender;
NSString *selectedEventsString = [self.eventsArray objectAtIndex:tempButton.tag];
NSLog(#"eventString: %#", selectedEventsString);
[[EventsManager eventsManager] changeSelectedEventsDictionaryTo:selectedEventsString];
//DO NOT RELEASE THESE YOU NEVER RETAINED THEM!
[tempButton release];
[selectedEventsString release];
Your static eventsManager is not thread safe which may not be a issue for you but should definitely be looked into.
Read the comments for the following code example
+ (EventsManager*)eventsManager {
if (eventsManager == nil) { //<-- Not thread safe
//DO NOT CALL SUPER USE self
//eventsManager = [[self alloc] init];
eventsManager = [[super allocWithZone:NULL] init];
//You need to autorelease these values or use an autoreleased static method
//eventsManager.eventsDictionary = [NSMutableDictionary dictionary];
//eventsManager.selectedEventsDictionary = [NSMutableDictionary dictionary];
eventsManager.eventsDictionary = [[NSMutableDictionary alloc] init];
eventsManager.selectedEventsDictionary = [[NSMutableDictionary alloc] init];
//Do not bother setting these at all or just set them to nil
eventsManager.eventString = [[NSString alloc] init];
eventsManager.mode = [[NSString alloc] init];
}
return eventsManager;
}
Make sure all of those properties are set to retain or copy and that may fix your problem. If you still have an issue after these fixes you can update your question and I will update my answer.

IPhone Repository for data

I'm a totally noob in IPhone development. Just start a week ago. I'm trying to have this tableview working.
I have a class I created called CustomerRepository with a method like this
- (CustomerRepository *) init {
self = [super init];
if (self) {
customerArray = [[NSMutableArray alloc] init];
Customer *cust1 = [[Customer alloc] init];
cust1.name = #"cust1";
[customerArray addObject: cust1];
[cust1 release];
}
return self;
}
- (NSMutableArray *) GetAll {
NSMutableArray *returnCustomerArray = [[[NSMutableArray alloc] init] autorelease];
for(Customer *cust in customerArray)
{
Customer *copy = [[Customer alloc]init];
copy.name = cust.name;
[returnCustomerArray addObject:copy];
[copy release];
}
return returnCustomerArray;
}
Now In my Controller
#synthezise customerArray;
viewDidLoad {
CustomerRepository *custRepo = [[CustomerRepository alloc] init];
customerArray = [custRepo GetAll];
[custRepo release];
}
- (NSInteger)tableView:(UITableView *)tv numberOfRowsInSection:(NSInteger)section {
// It always throw an exception here about -[UITableViewRowData count]: unrecognized selector sent to instance
return [customerArray count];
}
There is definitely something wrong with my code, can you guys help me point out what is wrong. Most probably an access to an instance that is already release ....
You need to retain the array returned by GetAll. Try this:
viewDidLoad {
CustomerRepository *custRepo = [[CustomerRepository alloc] init];
customerArray = [[custRepo GetAll] retain];
[custRepo release];
}
If you don't retain it, then your autorelease in -GetAll means that the returned array will eventually be released. When your -numberOfRowsInSection method fires, it's talking to a dealloc'd instance.