Strange resetting of NSMutableDictionary - iphone

I have this functions:
- (NSMutableDictionary *) getUserDataDictionary
{
[userDataDicionary removeAllObjects];
userDataDicionary = [[NSMutableDictionary alloc] initWithContentsOfFile:[self getUserDataDictionaryPath]];
return userDataDicionary;
}
- (int) getIndexOfLastVehicle
{
MyAppDelegate *app = (MyAppDelegate*)[[UIApplication sharedApplication] delegate];
NSMutableDictionary *tmpUserData = [app getUserDataDictionary];
int lastHighestIndex = -1;
for(id item in [tmpUserData allKeys]){
NSString *keyInArray = (NSString *)item;
if ([keyInArray rangeOfString:#"VEHICLE-"].location != NSNotFound) {
//f.e. "VEHICLE", "1", "TYPE"...or "VEHICLE", "1", "SPZ"...or "VEHICLE", "2", "TYPE" etc
NSArray * separatedComponents = [keyInArray componentsSeparatedByString:#"-"];
int indexOfVehicle = [(NSString *)[separatedComponents objectAtIndex:1] intValue];
if(indexOfVehicle > lastHighestIndex){
lastHighestIndex = indexOfVehicle;
}
}
}
return lastHighestIndex;
}
The problem is:
after this code:
MyAppDelegate *app = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSMutableDictionary *tmpUserData = [app getUserDataDictionary];
int lastVehicleIndex = [self getIndexOfLastVehicle];
The tmpUserData is EMPTY.
But when I changed order to this:
int lastVehicleIndex = [self getIndexOfLastVehicle];
MyAppDelegate *app = (MyAppDelegate*)[[UIApplication sharedApplication] delegate];
NSMutableDictionary *tmpUserData = [app getUserDataDictionary];
The tmpUserData is correctly filled.
Can someone explain this behavior?
Thanks

One of you problems lies in the method getUserDataDictionary. You are calling removeAllObjects which does not releases the userDataDictionary. You have to release it instead of removingAllObject. The actual release will release all it's objects for you.

I'm far from being a pro in Objective C, but the other I had a similar problem with az NSMutableArray. The problem in my case was that something got released or inited one more time than required.
I suggest putting a few NSLog()-s here and there to find out where your content goes lost. I did this, I basically put an NSLog to each and every instruction involving my object, and finally I was able to fix the stuff.
Just an idea, probably it helps.

You have problem in this method:
- (NSMutableDictionary *) getUserDataDictionary
{
[userDataDicionary release];
userDataDicionary = [[NSMutableDictionary alloc] initWithContentsOfFile:[self getUserDataDictionaryPath]];
return userDataDicionary;
}

Related

Get current location from AppDelegate

I have a method in my appDelegate which gets latitude and longitude and returns a string which I can use in any viewController.
AppDelegate :
-(void)StartUpdating
{
locManager = [[CLLocationManager alloc] init];
locManager.delegate = self;
locManager.distanceFilter = kCLDistanceFilterNone; // whenever we move
locManager.desiredAccuracy = kCLLocationAccuracyHundredMeters; // 100 m
[locManager startUpdatingLocation];
}
#pragma mark
#pragma mark locationManager delegate methods
- (void)locationManager: (CLLocationManager *)manager
didUpdateToLocation: (CLLocation *)newLocation
fromLocation: (CLLocation *)oldLocation
{
float latitude = newLocation.coordinate.latitude;
strLatitude = [NSString stringWithFormat:#"%f",latitude];
float longitude = newLocation.coordinate.longitude;
strLongitude = [NSString stringWithFormat:#"%f", longitude];
//[self returnLatLongString:strLatitude:strLongitude];
}
-(NSString*)returnLatLongString
{
NSString *str = #"lat=";
str = [str stringByAppendingString:strLatitude];
str = [str stringByAppendingString:#"&long="];
str = [str stringByAppendingString:strLongitude];
return str;
}
I am calling StartUpdating in start of application. Now in my viewController I call this method:
AppDelegate *appDelegate=[AppDelegate sharedAppDelegate];
NSString *str = [appDelegate returnLatLongString];
But I get a crash in
str = [str stringByAppendingString:strLatitude];
in returnLatLongString.
I know I am getting crash because there is no value in strLatitude at that time. But how can I fix this? How can I still have updated value of latitude and longitude?
Also, I don't want to use locationManager in viewControllers. So that I can get current location in all viewControllers, I did it in appDelegate.
The crash might be because str is an NSString, and so therefore is not mutable. Assigning it back to itself in this case is problematic. It would be simpler just to use stringWithFormat:
NSString *str = [NSString stringWithFormat: #"lat=%#&long=%#", strLatitude, strLongitude];
Import your AppDelegate.h file. Then
Use the following code:
MyAppDelegate *myAppDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *result = [myAppDelegate returnLatLongString];
i think you need to init those variables (strLatitude, strLongitude) in your
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method. if it hits this point at first time, they are not initialized so you get crash.
There are two potential problems in your code.
First of all make sure you declare strLatitude and strLongitude as strong properties in your header file:
#property (nonatomic, strong) NSString * strLatitude;
#property (nonatomic, strong) NSString * strLongitude;
Use #synthesize to automatically generate the proper getter and setter for them and assign the proper values to them as:
float latitude = newLocation.coordinate.latitude;
self.strLatitude = [NSString stringWithFormat:#"%f",latitude];
float longitude = newLocation.coordinate.longitude;
self.strLongitude = [NSString stringWithFormat:#"%f", longitude];
to make sure that they don't get released after they are assigned a value, because [NSString stringWithFormat:] returns autoreleased objects.
Secondly, after [locManager startUpdatingLocation]; it's going to take sometime before the system delivers the first location update. Therefore you need to check if the strLatitude and strLongitude have already been assigned values before you try to construct another string with them. Something similar to this should do the job:
-(NSString*)returnLatLongString
{
if (self.strLatitude == nil || self.strLongitude == nil)
return nil;
NSString *str = #"lat=";
str = [str stringByAppendingString:strLatitude];
str = [str stringByAppendingString:#"&long="];
str = [str stringByAppendingString:strLongitude];
return str;
}
Then of course the view controllers who are going to use [AppDelegate returnLatLongString] need to check that the returned value is not nil.
Hope this helps...
You are doing a mistake with creating a object of AppDelegate Class..
To create object just write this code :
AppDelegate *appDelegate=(AppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *str = [appDelegate returnLatLongString];
Its done..

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.

What does it mean when NSArray item shows up as NSObject instead of actual Class?

I have a NSMutableArray of QuoteMap objects. When I add one using the below code and navigate to another view that uses the same data, it bombs out with an EXC_BAD_ACCESS when trying to access that array.
You can see below that the last object in the array is "NSObject" instead of "QuoteMap".
Here is where I add the quoteMap:
- (void)insertQuoteMap:(QuoteMap*)qm {
// THEN TAKE THE QUOTE_MAP_ID, SUBJECT_ID AND QUOTE_ID AND INSERT INTO QUOTE_MAP TABLE
NSInteger quoteMapId = [qm.quote_map_id intValue];
NSInteger subIdInt = [qm.subject_id intValue];
NSInteger quoteIdInt = [qm.quote_id intValue];
QuotesAppDelegate *appDelegate = (QuotesAppDelegate *)[[UIApplication sharedApplication] delegate];
FMDatabase *database = [FMDatabase databaseWithPath:appDelegate.getDBPath];
if ([database open]) {
[database executeUpdate:#"insert into QUOTE_MAP(quote_map_id, quote_id, subject_id) values(?, ?, ?)",
[NSNumber numberWithInt:quoteMapId], [NSNumber numberWithInt:quoteIdInt], [NSNumber numberWithInt:subIdInt]];
[database close];
}
NSLog(#"QuoteMap inserted the quote_map_id of: %#", qm.quote_map_id);
//Add the object
[appDelegate addQuoteMap:qm];
[qm autorelease];
};
This is what calls the above method:
QuotesAppDelegate *appDelegate = (QuotesAppDelegate *)[[UIApplication sharedApplication] delegate];
QuoteMap *qm = [[QuoteMap alloc] init];
NSInteger newQuoteMapId = [appDelegate getNextQuoteMapId];
NSLog(#"quote_map_id= %d subId = %# quoteId = %#", newQuoteMapId, stringOfSubjectId, selectedQuote.quote_id);
// INSERT INTO QUOTE_MAP TABLE
NSString *stringOfId = [NSString stringWithFormat:#"%d", newQuoteMapId];
qm.quote_map_id = stringOfId;
qm.subject_id = stringOfSubjectId;
qm.quote_id = selectedQuote.quote_id;
//qm.isDirty = YES;
[qm insertQuoteMap:qm];
//Add it to the array.
[qmv.quoteMaps addObject:qm];
[qmv.tableView reloadData];
if (tableAlert.type == SBTableAlertTypeMultipleSelct) {
UITableViewCell *cell = [tableAlert.tableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryNone)
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
else
[cell setAccessoryType:UITableViewCellAccessoryNone];
[tableAlert.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
//release
[qm autorelease];
Your - (void)insertQuoteMap:(QuoteMap*)qm doesn't own the QuoteMap that's passed to it, so it shouldn't autorelease it at the end.
When classes change at runtime like that, it's frequently a result of an overrelease, where the original object no longer exists and the area it used to occupy in memory has since been filled with a another object.

Returning an object to a method in a ViewController

I am trying to call a method that returns an object and need help as I am getting a EXC_BAD_ACCESS when it is called. I am trying to get a QuoteMap object returned to the ViewController that calls it.
The method is addAndReturnQuoteMap which is as declared in the header QuoteMap.h
- (QuoteMap *)addAndReturnQuoteMap:(NSString *)subId withQuoteId:(NSString *)quoteId;
The method itself looks like this:
- (QuoteMap *)addAndReturnQuoteMap:(NSString *)subId withQuoteId:(NSString *)quoteId {
//FIRST GET A NEW QUOTE_MAP_ID
NSInteger newQuoteMapId = self.getNextQuoteMapId;
NSLog(#"newQuoteMapId = %d", newQuoteMapId);
// THEN TAKE THE QUOTE_MAP_ID, SUBJECT_ID AND QUOTE_ID AND INSERT INTO QUOTE_MAP TABLE
NSLog(#"subId = %# quoteId = %#", subId, quoteId);
QuotesAppDelegate *appDelegate = (QuotesAppDelegate *)[[UIApplication sharedApplication] delegate];
FMDatabase *database = [FMDatabase databaseWithPath:appDelegate.getDBPath];
if ([database open]) {
[database executeUpdate:#"insert into QUOTE_MAP(quote_map_id, quote_id, subject_id) values(?, ?, ?)",
newQuoteMapId, subId, quoteId];
[database close];
}
[database open];
FMResultSet *result_categories = [database executeQuery:#"select * from QUOTE where quote_id = 835"];
[result_categories next];
QuoteMap *qm = [QuoteMap alloc];
qm.quote_map_id = [result_categories stringForColumn:#"QUOTE_MAP_ID"];
qm.quote_id = [result_categories stringForColumn:#"QUOTE_ID"];
qm.subject_id = [result_categories stringForColumn:#"SUBJECT_ID"];
qm.isDirty = NO;
NSLog(#"QuoteMap inserted the quote_map_id of: %#", qm.quote_map_id);
[qm release];
return qm;
};
I call the method from my AddQuoteViewController here:
[qm addAndReturnQuoteMap:mySubjectId withQuoteId:quote.quote_id];
I am sure there is something really stupid that I am doing wrong and would appreciate any direction on what that might me.
You're not calling init on your QuoteMap. It should be:
QuoteMap *qm = [[QuoteMap alloc] init];
There is no case where you should call a bare +alloc without immediately calling some form of -init.
You have to init qm and not just allocate memory for it:
QuoteMap *qm = [[QuoteMap alloc] init];
And do not release the returning object, cause it is invalid after this. Make it autorelease.
Change your code into
QuoteMap *qm = [[QuoteMap alloc] init];
//your code
[qm autorelease];
return qm;

Custom Core Data accessors for transformable UILocalNotification

I have a transformable attribute on one of my entities, called reminder. It's a UILocalNotification.
Now, since I want to schedule it when it's added, and cancel it when removed, I would like to override the accessors to handle the scheduling and cancelling in there.
How would that look?
Thanks!
Are you actually persisting the UILocalNotification or are you using it as a transient property?
I wouldn't store it, rather UILocalNotification as a userInfo as a property. You can at a key/value pair to that dictionary with information about the owning entity. For instance:
You create a value for the key notificationID in the userInfo dictionary and set a attribute notificationID on your Core Data entity to the same value. That way, you just have to store an int or NSString in your store (which is preferable to transformable).
When you want to fetch your UILocalNotification again you can make an accessor on your Entity Class, something like:
- (void)createNotification
{
static NSUInteger kDeadlineWarningPeriod = 3600;
UILocalNotification *notification = [[UILocalNotification alloc] init];
…
self.notificationID = #"some generated ID";
[notification.userInfo setValue:self.notificationID forKey:#"notificationID"];
[[UIApplication sharedApplication] scheduleLocalNotification:notification];
[notification release];
}
- (void)cancelNotification
{
// We search for the notification.
// The entity's ID will be stored in the notification's user info.
[[[UIApplication sharedApplication] scheduledLocalNotifications] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UILocalNotification *notification = (UILocalNotification *)obj;
NSDictionary *userInfo = notification.userInfo;
NSString *notificationID = [userInfo valueForKey:#"notificationID"];
if ([notificationID isEqualToString:self.notificationID])
{
[[UIApplication sharedApplication] cancelLocalNotification:notification];
*stop = YES;
self.notificationID = nil;
}
}];
}
Of course you can make an accessor for your notification in much the same way if you actually need access to the notification object.
Hope it helps.
UPDATE
So since you have a property you call reminder on you Entity (I'm guessing that it is a BOOL) it will look something like this:
// .h
#property (nonatomic, assign) BOOL reminder;
// .m
- (void)setReminder:(BOOL)reminder {
[self willAccessValueForKey#"reminder"];
BOOL hasReminder = [[self primitiveValueForKey:#"reminder"] booleanValue];
[self didAccessValueForKey:#"reminder"];
if (hasReminder && !reminder) {
[self cancelNotification];
}
else if (!hasReminder && reminder) {
[self createNotification];
}
if (reminder != hasReminder)
{
[self willChangeValueForKey:#"reminder"];
[self setPrimitiveValue:[NSNumber numberWithBool:reminder] forKey#"reminder"];
[self didChangeValueForKey:#"reminder"];
}
}
In fact you don't really have to store the "reminder" attribute at all, you can just check if the notificationID attribute is nil or not. That was the idea from my suggestion before.
I haven't checked the code above but I do something similar in two of my projects.
Remember you can get into trouble if you create more than 64 local notifications, since you are only allowed to make that many per app. So you might want to track how many you have before creating any new ones.
If you have only one notification for each object, then you could avoid having to store a notificationID and just use the objectId of the NSManagedObject in the Persistent store.
You can serialize and deserialize the objectId with the following lines of code:
[[self.objectID URIRepresentation] absoluteString]
and
[[self persistentStoreCoordinator] managedObjectIDForURIRepresentation:[NSURL URLWithString:[localNotif.userInfo objectForKey: kYourReminderNotificationKey]
here is the code edited:
- (void)createNotification
{
Class cls = NSClassFromString(#"UILocalNotification");
if (cls != nil) {
UILocalNotification *notif = [[cls alloc] init];
notif.fireDate = self.dateDue;
notif.timeZone = [NSTimeZone defaultTimeZone];
notif.alertBody = #"Alert body";
notif.alertAction = #"Show me";
notif.soundName = UILocalNotificationDefaultSoundName;
NSDictionary *userDict = [NSDictionary dictionaryWithObject:[[self.objectID URIRepresentation] absoluteString] forKey:kRemindMeNotificationDataKey];
notif.userInfo = userDict;
[[UIApplication sharedApplication] scheduleLocalNotification:notif];
}
}
- (void)cancelNotification
{
[[[UIApplication sharedApplication] scheduledLocalNotifications] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UILocalNotification *notification = (UILocalNotification *)obj;
NSDictionary *userInfo = notification.userInfo;
NSString *notificationID = [userInfo valueForKey:kRemindMeNotificationDataKey];
if ([notificationID isEqualToString:[[self.objectID URIRepresentation] absoluteString]])
{
[[UIApplication sharedApplication] cancelLocalNotification:notification];
*stop = YES;
}
}];
}