I have been searching top and bottom for info on how to do this. I landed on a great tutorial! So I am still quite newbie to this. Basically I have been trying to store Map View annotations to an array. The annotations are a separate class which basically overrides / acts as a MKAnnotation for a pin annotation. It has three properties:
Annotation Coordinate
Annotation Title
Annotation Subtitle
This array needs to store into the NSUserDefaults. I faced a problem, here is the log:
[UIMutableIndexPath setObject:forKey:]: unrecognized selector sent to
instance 0x1187b0
My Annotation class objects stored inside the array could not be saved to the user defaults. So I had to turn this array into NSData and then save it, right?
I have a lot of code setup, just not working. Here is how I attempt all of this:
View Controller Class.m:
- (void)syncMap { // this method is called in viewWillDissapear (for running tests) and applicationDidEnterBackground in App Delegate
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject: localOverlays]; // this is the array I was talking about
[defaults setObject:data forKey:#"overlays"];
[defaults synchronize];
}
- (void)initCircles { // called in AppDelegate UIApplicationDelegate method: applicationDidFinishLaunchingWithOptions
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey: #"overlays"];
localOverlays = [NSKeyedUnarchiver unarchiveObjectWithData: data];
if (!localOverlays) {
// Either there is a problem or it is the first time opening the app
localOverlays = [[NSMutableArray alloc] init];
}
}
NOTE: I AM TESTING WITH TWO ANNOTATIONS IN THE ARRAY (localOverlays)
So, my localOverlays can be encoded / archived(using NSCoder) since it is an NSArray. However, I had to add some further setup in my Annotation class. In its .h it uses to NSCoding and MKAnnotation: like the following< NSCoding, MKAnnotation>. Sorry if I am not using the correct term. Here is my .m:
- (void)encodeWithCoder:(NSCoder *)aCoder { // should only be called when app enters background state, but since that cannot log in the console, like before I set it up so it should also be called in viewWillDissapear
NSLog(#"encodeCoder called in Annotation"); // gets called twice when the view will disappear... GOOD!
[aCoder encodeObject: title forKey: #"title"];
[aCoder encodeObject: subtitle forKey: #"subtitle"];
[aCoder encodeDouble: coordinate.latitude forKey: #"latitude"];
[aCoder encodeDouble: coordinate.longitude forKey: #"longitude"];
}
- (id)initWithCoder:(NSCoder *)aDecoder { // Should be called only at startup of app (not the first time you startup the app though... because data will be NULL)
NSLog(#"In the Annotation class, initWithCoder is called"); // Does get called at appropriate times twice... NICE!
self = [super init];
if (self) {
title = [aDecoder decodeObjectForKey: #"title"];
subtitle = [aDecoder decodeObjectForKey: #"subtitle"];
double lat = [aDecoder decodeDoubleForKey: #"latitude"];
double lon = [aDecoder decodeDoubleForKey: #"longitude"];
coordinate = CLLocationCoordinate2DMake(lat, lon);
}
return self;
}
So as you can see, I have everything setup for archiving, right? Well it seems not... because now in the .m of the ViewController, I also have this code in viewDidLoad:
for (Annotation *pin in localOverlays) {
if (pin) {
NSLog(#"valid pin: _CMD updateCircles");
[mapView addAnnotation: pin];
}
}
This code works nicely and fine the first time I open my app and add the pins. Okay, so now I have exited the view and quit the app, and deleted from multitasking bar. When I open it back up, I get a crash at the fast enumeration line:
-[NSURL countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance 0xcd31140
So, there is something wrong with all my archiving and encoding setup. What is wrong here... I know this was a lengthy question but I tried to structure it well. Am I setting up my code totally incorrect, is there a typo/bug in my code. Thanks all!
UPDATE:
I have got the coordinate variable encoded, therefore when I startup the app after the pin appear at the right coordinate, but when I try to press it to see the title and subtitle, I get the following crash:
objc_msgSend
So something is released right... just a guess... bad memory management? What would cause this crash in my code?
UPDATE:
I have looked deeper and further into my code and changed a few release statements around, just improved my memory management and done a bit of optimization. Now I get a more specific crash:
*** -[CFString length]: message sent to deallocated instance 0x147490
So my title or subtitle IS deallocated... why? I have checked my code it should be absolutely fine, especially since the coordinates are fine...
UPDATE:
I have solved this problem! I came to the realization, the coordinate two variables, latitude and longitude are doubles, data types... NOT objects! Therefore they are sticking on and working only BECAUSE they are copied... unlike objects which are references. Long story short I needed to retain. Just like this:
title = [[aDecoder decodeObjectForKey: #"titler"] retain];
subtitle = [[aDecoder decodeObjectForKey: #"subtitler"] retain];
I'm not sure what exactly is the reason for the crashes. But I noticed some mistakes in your encode/decode methods:
EDIT: Only if your super class conforms to NSCoding:
In encodeWithCoder you should at first call:
[super encodeWithCoder: aCoder];
In initWithCoder replace
self = [super init];
by
self = [super initWithCoder:aDecoder]
The following returns an immutable object (not NSMutableArray):
localOverlays = [NSKeyedUnarchiver unarchiveObjectWithData: data];
Replace this line by:
localOverlays = [[NSKeyedUnarchiver unarchiveObjectWithData: data] mutableCopy];
I hope that helps!
I have solved this problem! I came to the realization, the coordinate two variables, latitude and longitude are doubles, data types... NOT objects! Therefore they are sticking on and working only BECAUSE they are copied... unlike objects which are references. Long story short I needed to retain. Just like this:
title = [[aDecoder decodeObjectForKey: #"titler"] retain];
subtitle = [[aDecoder decodeObjectForKey: #"subtitler"] retain];
Related
I am developing an iphone application which has some data stored in a sqllite database. When my view loads i would like to load the data from the database on a background thread. The problem is the application keeps crashing and i dont know why.
The code:
-(id) init
{
if((self=[super init]))
{
[self performSelectorInBackground:#selector(loadList) withObject:nil];
}
}
-(void) loadList
{
#autoreleasepool
{
Loader * loader = [[Loader alloc] init];
NSMutableArray * array = [loader getItemList];
[array retain];
NSLog(#"Got %d items",[array count]);
[self performSelectorOnMainThread:#selector(createList:) withObject:array waitUntilDone:false];
[loader release];
}
}
-(void) createList: (NSMutableArray*) array
{
items = array;
int i;
Item * it;
for(i = 0; i < [items count]; i++)
{
it = [items objectAtIndex: i];
[it getName]; // crashes
// populate the list
}
}
Loader returns a NSMutableArray with Item objects. The application crashes when i call the item getName (which returns a NSString*). From what i understand it crashes because the item name properties is being released. What am i doing wrong?
Thanks!
It's likely to be a problem with whatever type of object you're using to populate array.
I'm unable to find finger-on-paper proof but I'm confident that performSelectorOnMainThread:withObject:waitUntilDone: retains its object. However if each of the items in array keeps a reference to loader then they need to take responsibility for retaining that object. It looks like you're attempting to keep it alive manually but — as Chuck alludes to — your call to performSelector... will return instantly and not wait for the call you've made to complete.
This particular bug appears to be that you're passing waitUntilDone:NO, so the array is being released immediately and consequently so are its items.
But in general, UIKit is not thread-safe, so this is just a touchy design. I would probably put the loading of this stuff in another class that handles the task for you instead of right in the view.
I'd put a breakpoint on the line:
it = [items objectAtIndex: i];
Then type
po it
in the debugger, and see what's in the name field. As a guess, I'd say one of two things: 1) the field that getName returns isn't initialized with an object (i.e. isn't a real NSString *) or that you're getting a C string from SQLite (which is what it usually returns) and you're trying to treat it as an NSString *. If it's the latter you can use [myCString stringWithUTF8String] to convert the C string into an NSString *
i am storing my "custom button" in NSUserdefaults using the following code.But i am getting an error "[UIImage encodeWithCoder:]: unrecognized selector sent to instance" while converting the object to NSdata..here "custom button" is UIButton class. Anyone know why...? please help me.
Custom_button *lock11 = (Custom_button*)[menu1 viewWithTag:100];
NSLog(#"opened lock1 ========= %#",lock11);
lock11.is_menu_lock_opened = YES;
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:lock11]; //[NSKeyedArchiver archivedDataWithRootObject:lock11];
[prefs setObject:myEncodedObject forKey:#"set1lock"];
The NSUserDefaults class provides convenience methods for accessing
common types such as floats, doubles, integers, Booleans, and URLs. A
default object must be a property list, that is, an instance of (or
for collections a combination of instances of): NSData, NSString,
NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any
other type of object, you should typically archive it to create an
instance of NSData.
Objects that don't map directly to property list objects are turned into NSData by being sent a coder and encoding their contents. [UIImage encodeWithCoder:]. They need to conform to the NSCoding protocol for this to work. You will find that UIImage does not confirm to the NSCoding protocol before iOS 5. If you want to deploy before iOS 5 you will have to work something out yourself, by implementing NSCoding in your Custom Button class and storing that image in a different way.
Implement this method into your Custom_button Class
initWithCoder and encodeWithCoder for all object
-(id) initWithCoder: (NSCoder *)coder {
self = [[CastInnerListData alloc] init];
if (self != nil) {
self.object1 = [coder decodeObjectForKey:#"object1"];
self.object2 = [coder decodeObjectForKey:#"object2"];
}
return self;
}
-(void) encodeWithCoder: (NSCoder *)coder{
[coder encodeObject:object1 forKey:#"object1"];
[coder encodeObject:object2 forKey:#"object2"];
}
For more detail click here
Well a little searching would have given you the answer fast:
UIImage encodeWithCoder urecognised selector?
I'm working on an iPhone app and facing some troubles with my shared singleton class.
I'm using a shared singleton to store two variables
int gameRuns and int totalScore
'gamRuns' just increments every time the user loads the app, and 'totalScore' is obvious :D
the issue is as follows, I load the singleton and init using my own method when the app loads using this code:
+ (SingletonLevelState*)sharedLevelStateInstance {
static SingletonLevelState *sharedLevelStateInstance;
#synchronized(self) {
if(!sharedLevelStateInstance) {
//Init a singleton
sharedLevelStateInstance = [[SingletonLevelState alloc] init];
sharedLevelStateInstance->gameRuns = 1;
sharedLevelStateInstance->totalScore = 0;
}
}
return sharedLevelStateInstance;
}
This is working great as I can reference this class from any other class and always get a pointer to the same object, so this works fine from other objects:
sharedLevelState = [SingletonLevelState sharedLevelStateInstance];
sharedLevelStateInstance.gameRuns++;
Now I added the NSCoder protocol, and added the two methods initWithCoder and encodeWithCoder as follows :
- (void) encodeWithCoder: (NSCoder *)coder
{
//encode level data
[coder encodeInt:self->gameRuns forKey:#"gameRuns"];
[coder encodeInt:self->totalScore forKey:#"totalScore"];
}
- (id) initWithCoder: (NSCoder *) coder
{
if(self = [super init]){
self->gameRuns = [coder decodeIntForKey:#"gameRuns"];
self->totalScore = [coder decodeIntForKey:#"totalScore"];
}
return self;
}
Now when the app loads, I check to see if we already have a saved sate, if it exists, I just unarchive the class with that file, if not, I init that class using my custom method above, then set its defaults, encode it to file so we have a saved state, here's the code:
//Load Level state
sharedLevelStateInstance = [SingletonLevelState sharedLevelStateInstance];
//Check if file is saved
NSFileManager *fm = [[NSFileManager alloc] init];
NSString *gameStatePath = [NSString stringWithString:[self getSavePath]];
if([fm fileExistsAtPath:gameStatePath]){
[self loadState];
sharedLevelStateInstance.gameRuns = sharedLevelStateInstance.gameRuns+1;
NSLog(#"Loaded %d times", [sharedLevelStateInstance gameRuns]);
}
[fm release];
Now the last line in the if statement works perfectly, it increments every time I load the app as expected and I feel really happy lol.
However, the problem arises when I try to get a reference of the singleton in another class by doing the following:
sharedLevelStateInstance = [SingletonLevelState sharedLevelStateInstance];
NSLog(#"Played: %d times", sharedLevelStateInstance.gameRuns);
It always counts back to 1, I know what happens but I'm not sue what's the best way to solve it, when I initWithCoder the singleton, It's not returning a static object, it creates a new one, when I init my sharedLevelStateInstance, it calls my first custom method, initializing it to the defaults hardcoded.
So StackOverflow, can you please help me ?!
I just need to know what's the best way to get a reference to the same object without allocating a new one every time I initWithCoder !
Thanks :)
So, you code should probably look like this:
if(self = [[SingletonLevelState sharedLevelStateInstance] retain])
Which sets the variables of the singleton, and returns the singleton. Be sure to retain the singleton, so that when the NSCoder releases this instance, it doesn't fully deallocate your singleton.
I am getting the EXC_BAD_ACCESS error when trying to set a value inside a subclass of NSManagedObject for the second time.
I am using zombies but nothing is showing up in the console. Printing out the object using GDB I see that the object has the same memory address both times I try to set the value - not sure why though.
Situation:
I have a view (A) that, when a QR code is scanned, adds a subview (B) which in turn downloads XML that is then saved into a subclassed NSManagedObject.
Inside the subview (B) I navigate back (removeFromSuperView is called)
Back in the original view (A)
Next time, when the same QR code is scanned, it (A) finds the NSManagedObject from the database and attaches that to an instance variable on a new view (same type as B) that it then adds as a subview to the original (A).
In view B's viewDidLoad i always try to set the current date in order to track when a user "saw" that object. This is where I get the EXC_BAD_ACCESS error:
self.currentPiece.piece_last_viewed = [[NSNumber alloc] initWithDouble:[[NSDate date] timeIntervalSince1970]];
Where self.currentPiece is the instance of a subclassed NSManagedObject that was attached in A when that object existed in the database.
I know that it is being released somewhere but I don't know where since managed objects take care of much of that on their own. The error only occurs the second time around that I try to set the value.
I have tried to make this clear. Please tell me if you want me to clarify it even more.
Thanks for the help (have worked on this for some hours now)
UPDATE:
Declaring the piece_last_viewed in HubPiece.h:
#interface HubPiece : NSManagedObject {
}
// ...
#property (nonatomic, retain) NSNumber *piece_last_viewed;
HubPiece.m:
#dynamic piece_last_viewed;
//...inside init method:
self.piece_last_viewed = [[NSNumber alloc] initWithDouble:[[NSDate date] timeIntervalSince1970]];
UPDATE 2:
It is not due to the switching of subviews, that is ruled out. I then realized that I didn't save my changes either, so I introduced save: inside the subclassed NSManagedObject. I then got an earlier error the first time I try to save the entity instance (which saved during an app session, but the data vanishes if I quit the app entirely and then open it up again). So I thought using [context save:&error] would be a good idea :) ...but now that doesn't work and give me a EXC_BAD_ACCESS error.
The HubPiece itself is initialized from another class HubPieceView.m :
self.currentPiece = [[HubPiece alloc] initWithXML:pieceXML];
self.currentPiece is a class variable of type HubPiece and it first declared in .h file and then synthesized in .m file.
Then inside HubPiece.m the initializer looks like this:
-(id)initWithXML:(TBXMLElement *)pieceXML
{
// Setup the environment for dealing with Core Data and managed objects
HenryHubAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityHubPiece = [NSEntityDescription entityForName:#"HubPiece"
inManagedObjectContext:context];
// STORING values
self = [[HubPiece alloc] initWithEntity:entityHubPiece insertIntoManagedObjectContext:context];
// ...setting variables with normal assignment: self.var = value;
NSError *error;
// Save fails
if (![context save:&error] ){
NSLog(#" ERROR: %#", [error localizedDescription]);
}
return self;
}
I just realized my problem. I had been assigning values to the entity through with normal '=' assignment:
self.currentPiece.piece_last_viewed = [[NSNumber alloc] initWithDouble:[[NSDate date] timeIntervalSince1970]];
When it should have been done:
[self setCurrentPiece.piece_last_viewed:[[NSNumber alloc] initWithDouble:[[NSDate date] timeIntervalSince1970]] ];
This is because it is a managed object which creates it's own accessors at runtime through the #dynamic compiler instruction.
Serious Problem here... i'm getting ECX_BAD_ACCESS if i try to NSLog an instance variable of my custom object. Following Function is called in my ViewController, payload holds String Data which is pulled from a url.
- (void) initVcardWithData:(NSString *)payload {
NSLog(#"1. initVcardWithData");
aVCard = [[vcardItem alloc] initWithPayload:payload];
VCardViewController *aVCardViewController = [[VCardViewController alloc] initWithVCard:aVCard];
[self presentModalViewController:aVCardViewController animated:YES];
[aVCard release];
}
So far so good. The initWithWithVCard function is as follows, theVCard and theVCardN are defined in #implementation and also set as a #property (nonatomic, retain) in (.h).:
-(id)initWithVCard:(vcardItem *)aVCard {
if(self = [super init]) {
theVCard = [aVCard retain];
theVCardN = [theVCard.PersonName retain];
}
NSLog(#"---- vCardViewController :: initWithVcard :: FirstName: %#", theVCard.PersonName.FirstName);
return self;
}
If i access the theVCardN object in my ViewController aVCardViewController within ViewDidLoad everything works like a charm. I set some labels with data from that object.
If i then try to access the instance variables from theVCardN within a function which is called from an IBAction which is connected to a button in View, i get an EXC_BAD_ACCESS error at the debugger console. The Function which tries to pull data from the instance variables is as follows:
-(IBAction)addressbookButtonTapped {
NSLog(#"RETAIN COUNT FOR theVCard: %i", [theVCard retainCount]);
NSLog(#"RETAIN COUNT FOR theVCardN: %i", [theVCardN retainCount]);
NSLog(#"Save to Adressbook: %#", theVCardN.FirstName);
//[self dismissModalViewControllerAnimated:YES];
}
The RetainCounter for theVCardN right before calling NSLog outputs "1". The NSLog Line then returns EXC_BAD_ACCESS in Debugger Console.
Any idea ?
Do not call -retainCount. Absolute retain counts are useless.
retainCount returns the absolute retain count of an object. The actual value will be an implementation detail that is very often completely out of your control as the system frameworks may do any number of things internally to cause the retain count to be modified in ways you don't expect.
It is useless for debugging and their are a wealth of tools that are specifically focused on tracking down these kinds of issues.
First, if there is a crash, there is a backtrace. Post it. Probably not that interesting in this case, but, still, always look to the backtrace to at least confirm that it is crashing where/how you think it is.
From the evidence posted, it sounds like theVCardN.FirstName is either set to garbage or the underlying string has been over-released. Turn on zombie detection mode and see if that is the case. Since it is crashing on FirstName, then show the code related to creating/storing the FirstName.
Also, instance variables and methods should always start with a lowercase letter; PersonName should be personName & FirstName should be firstName.
Maybe i'm reading the code wrong or misunderstanding your class structure, but it looks like you logging:
NSLog(#"Save to Adressbook: %#", theVCardN.FirstName);
Above, where you say it is still working, you are logging:
theVCard.PersonName.FirstName
Are you missing the "PersonName"? Meaning you should be logging:
NSLog(#"Save to Adressbook: %#", theVCardN.PersonName.FirstName);