Some information about the way I am saving my data: I have an array of View Controllers that are added and deleted by the user (this is basically a note taking app and the View Controllers are folders). The View Controllers have several dynamic properties that the app needs to save as well as the notes array within them, and then the Note objects themselves have a few properties that need to be saved. The View Controllers and the Notes both of course have the proper NSCoding stuff, this is the one on the View Controller for example:
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.folderName forKey:#"lvcTitle"];
[encoder encodeObject:[NSNumber numberWithInt:self.myPosition] forKey:#"myPosition"];
[encoder encodeObject:self.notes forKey:#"notes"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self.folderName = [decoder decodeObjectForKey:#"lvcTitle"];
NSNumber *gottenPosition = [decoder decodeObjectForKey:#"myPosition"];
int gottenPositionInt = [gottenPosition intValue];
self.myPosition = gottenPositionInt;
self.notes = [decoder decodeObjectForKey:#"notes"];
return self; }
The array of Controllers belongs to a Singleton class. NSCoding is pretty confusing to me even though it's considered to be simple stuff, but so far I've had success with only telling the Singleton to save the Controllers array - which then (successfully) saves all of the contained properties of the View Controllers, their properties and all of the Notes' properties as well. Here is the code in the Singleton:
- (void) saveDataToDisk:(id)object key:(NSString *)key {
NSString *path = [self pathForDataFile];
NSMutableDictionary *rootObject;
rootObject = [NSMutableDictionary dictionary];
[rootObject setValue:object forKey:key];
[NSKeyedArchiver archiveRootObject:rootObject toFile:path]; }
- (void) loadDataFromDisk {
NSString *path = [self pathForDataFile];
NSDictionary *rootObject;
rootObject = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if ([rootObject valueForKey:#"controllers"] != nil) {
self.controllers = [NSMutableArray arrayWithArray:[rootObject valueForKey:#"controllers"]];
firstRun = false;
LabeledViewController *lastOneThere = [self.controllers objectAtIndex:self.controllers.count-1];
lastOneThere.isFolderAddView = TRUE;
}else{
firstRun = true;
}
}
I then call the save method several times in the Folder View Controllers:
[singleton saveDataToDisk];
And this will work well several times, until I randomly get a crash right when the app is loading up. The culprit is heightForRowAtIndexPath:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
Note *currentNote = [self.notes objectAtIndex:indexPath.row];
if (currentNote.associatedCellIsSelected) {
return currentNote.myHeight + NOTE_BUTTON_VIEW_HEIGHT;
}
return NORMAL_CELL_FINISHING_HEIGHT; }
I get the following error:
2012-06-07 08:28:33.694 ViewTry[1415:207] -[__NSCFString associatedCellIsSelected]: unrecognized selector sent to instance 0x8904710
2012-06-07 08:28:33.696 ViewTry[1415:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFString associatedCellIsSelected]: unrecognized selector sent to instance 0x8904710'
*** First throw call stack:
I understand that "__NSCFString" and "unrecognized selector sent to instance" means that there is a string somewhere there shouldn't be, as associatedCellIsSelected is a bool. However, if I only return "currentNote.myHeight" in heightForRow, I also get the same __NSCF error with myHeight, which is a float. If I take out heightForRow all together, everything works except for the appropriate height definitions.
BTW, the table view that heightForRowAtIndexPath is referencing is made in loadView AFTER the notes array is made and populated. I just don't understand why this error would only pop up every once in a while (like 5-10 opens, savings, closings and reopenings of app), seemingly random - I cannot find the pattern that causes this behavior. Any pointers?
Sorry for the mess, I'm new to iOS programming and I'm sure I'm doing a lot of things wrong here.
Edit - Also, once the app has crashed, it stays crashed every time I reopen it (unless I disable heightForRow) until I uninstall and reinstall it.
When you see an "unrecognized selector" error and the receiver type is not the kind of object that you coded (in this case __NSCFString instead of Note), the odds are that you have a problem where the object you intended to use has been prematurely released and its address space is being reused to allocate the new object.
The fix depends on tracking down where the extra release is happening (or retain is not happening). If you can show the #property declaration for notes it might shed more light on the situation.
One quick thing to do is choose Product->Analyze from the menu and fix anything it flags. It won't catch everything but it's a good sanity check to start.
Related
#interface ViewController
{
NSMutableArray * GetPrices;
}
-(void)viewWillAppear:(BOOL)animated
{
GetPrices=[[NSMutableArray alloc]init];
// here I’m adding objects to the array..
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
#try
{
if([Getprices count]>0)
{
// dealing with array values
}
}
#catch (NSException *exception) {
// here Im using some mail service to get crash description
}
}
So I got following info to my mail
Stack Trace: -[__NSCFString count]: unrecognized selector sent to instance 0x157153180
stackoverflow.com/questions/5152651/… from this accepted answer Im thinking that array was released at some point.
Now my doubt is, Is there any chance of my array become released… (Let us say my app is in background for a long time, will my array gets released).
What are possible reasons for that crash ?
Thank You..
ARC will retain this array, so there is no way it becomes released until you do it programatically.
I am working on challenges from iOS Big Nerd Ranch book of 12th chapter and there is a problem of saving an array of items to the disk. I have BNRDrawView UIView that has an array finishedLines that holds items BNRLine defined by me. I want to use NSCoder for the purpose. So, I implemented inside BNRDrawView two methods:
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:_finishedLines forKey:finishedLinesKey];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
_finishedLines = [decoder decodeObjectForKey:finishedLinesKey];
}
return self;
}
and trying to save finishedLines array whenever it is changed like this:
[NSKeyedArchiver archiveRootObject:self.finishedLines
toFile:self.finishedLinesPath];
Loading I am trying to do inside initWithFrame BNRDrawView method:
- (instancetype)initWithFrame:(CGRect)r
{
...
self.finishedLinesPath = #"/Users/nikitavlasenko/Desktop/XCodeProjects/MyFirstApp/TouchTracker/savedLines/finishedLines";
BOOL fileExists = [[NSFileManager defaultManager]
fileExistsAtPath:self.finishedLinesPath];
if (fileExists) {
self.finishedLines = [NSKeyedUnarchiver
unarchiveObjectWithFile:self.finishedLinesPath];
}
...
It gives me the error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BNRLine encodeWithCoder:]: unrecognized selector sent to instance 0x7fbd32510c40'
From here I have two questions:
Do I need to implement encodeWithCoder: and initWithCoder: for ALL of the classes - let's suppose that I have a plenty of different ones, defined by me - that are inside my array that I am trying to save?
If you look above to my initWithCoder: method, you can see that I need to call [super initWithCoder:decoder], but do I also need to call BNRDrawView's initializer method initWithFrame: there?
Yes, you need to implement NSCoder in all objects that are stored inside the array.
No, you don't need to call initWithFrame - initWithCoder is sufficient.
I have a UITableView with several sections. Each section contains a different set of data: phoneNumbers, addresses....
For each of those sets I have a model: PhoneNumber, Address. They're completely different but have some methods in common.
In my UITableView I have an array containing those models/classnames:
NSMutableArray *classNames;
In the viewDidLoad of my UITableView I do some initializations for all those sections:
//section 1: PhoneNumbers
phoneNumbers = [PhoneNumbers getAllIDs];
if (phoneNumbers && (phoneNumbers.count >0)) {
[classNames addObject:#"PhoneNumber"];
[dataIDs addObject:phoneNumbers];
}
I do this again for all the other sections/models:
//section 2: Addresses
addresses = [Address getAllIDs];
if (addresses && (addresses.count >0)) {
[classNames addObject:#"Address"];
[dataIDs addObject:addresses];
}
// section 3: .....
Ok so far for initialization. This looks good and works fine.
Then later on in my cellForRowAtIndexPath I'm retrieving the actual data via those ID's
NSInteger section = [indexPath section];
NSInteger row = [indexPath row];
NSArray *rows = [dataIDs objectAtIndex:section];
NSNumber *recordID = [rows objectAtIndex:row];
I then figure out in what class we have to fetch the actual data:
Class displayedDataClass = NSClassFromString ([classNames objectAtIndex:section]);
and get the data to populate the cell.
id displayedRecord = [[displayedDataClass alloc] init];
[displayedRecord getByID:recordID];
I can then set the labels in my cell using :
[cell.someLabel setText:[displayRecord fullDesciption]];
So far so good, I succesfully abstracted everything, the cellForRowAtIndexPathdoesn't need to know where things come from, as long as those classes respond to the methods for retrieving the data for the labels (in the case above fullDesciption)
Now I need an actionButton in every Cell performing some kind of action
To make sure I understood the concept of selectors and performSelection I just quick and dirty made in action in my TableView Class:
- (void) buttonTarget {
NSLog (#"yes");
}
And in my cellForRowAtIndexPath method created a button with the following target:
button addTarget:self action:#selector(buttonTarget) forControlEvents:UIControlEventTouchUpInside];
Ok, so far so good, things work like expected. But this is not what I really wanted. The action should not be performed here, but in the actual class (PhoneNumber,Address,...).
To keep things clean I made a model Action, containing the icon for the button, a description and the selector:
#interface Action : NSObject
#property (nonatomic, strong) NSString *description;
#property (nonatomic, strong) UIImage *icon;
#property (nonatomic ) SEL selector;
#end
In my PhoneNumber class (and similar classes) the action is set to the correct selector:
Action *phoneAction = [[Action alloc] init];
phoneAction.description = NSLocalizedString(#"Call", #"Call button description");
phoneAction.icon = [UIImage imageNamed:#"phone"];
phoneAction.selector = #selector(callPhone);
Of course callPhone is implemented in the PhoneNumber class.
In my TableView I then get the actions for that cell
action = [displayedRecord action];
I then try to use that selector in my Button:
[button addTarget:displayedRecord action:[action selector] forControlEvents:UIControlEventTouchUpInside];
But here things go wrong: we never arrive in that method and I get the following error:
[UIDeviceWhiteColor callPhone]: unrecognized selector sent to instance
0x874af90 2013-12-29 23:23:03.629 thinx[27242:907] * Terminating app
due to uncaught exception 'NSInvalidArgumentException', reason:
'-[UIDeviceWhiteColor callPhone]: unrecognized selector sent to
instance 0x874af90'
Sounds like you have a zombie. When you get an action being sent to an object that makes no sense, it usually means that your object is being deallocated before you can send a message to it.
In your case, you're adding "displayedRecord" as the target for your button.
In order for that to work, you need to keep a strong reference to displayedRecord call for the lifetime of your button object. What owns your displayedRecord object?
If you can't debug this from looking at your code you can use the zombies instrument to try to figure it out.
In your unrecognized selector error you sent the message to an object called UIDeviceWhiteColor. Does that class have a method called callPhone? It seems to me that displayedRecord is not pointing to the object you think it is.
I use core data to store some arrays as strings. The strings are prefixed with STRINGFROMARRAY and delimited by &,&.
I thought it would be neat to override the setters and getters so I wouldn't have to provide code to convert them every time I needed access. I have created a managed object subclass called TestEntity with only one attribute, "memberIds" (string). I have verified that my custom setter and getter seem to work fine:
TestEntity.h:
#interface TestEntity (CoreDataGeneratedAccessors)
- (NSArray*)memberIds;
- (void)setMemberIds:(NSArray *)memberIds;
#end
TestEntity.m:
#implementation TestEntity
#dynamic memberIds;
- (NSArray *)memberIds
{
[self willAccessValueForKey:#"memberIds"];
NSArray *memberIdsArray = [NSArray arrayWithArray:[[[self primitiveValueForKey:#"memberIds"] substringFromIndex:15] componentsSeparatedByString:#"&,&"]];
[self didAccessValueForKey:#"memberIds"];
return memberIdsArray;
}
- (void)setMemberIds:(NSArray *)memberIds
{
NSString *stringFromArray = [#"STRINGFROMARRAY" stringByAppendingString:[memberIds componentsJoinedByString:#"&,&"]];
[self willChangeValueForKey:#"memberIds"];
[self setPrimitiveValue:stringFromArray forKey:#"memberIds"];
[self didChangeValueForKey:#"memberIds"];
}
#end
I can set an array value and it properly stores as a string. I can also use valueForKey to log the value after it's set, and it properly prints as an array.
However, when I attempt to log the entire managed object (without valueForKey:), I get a [NSArray length] unrecognized selector error.
Here's the code I'm using to test this:
NSManagedObject *test = [NSEntityDescription insertNewObjectForEntityForName:#"TestEntity" inManagedObjectContext:[SyncEngine sharedEngine].managedObjectContext];
[test setValue:[NSArray arrayWithObjects:#"1", #"2", #"3", nil] forKey:#"memberIds"];
NSLog(#"%#", [test valueForKey:#"memberIds"]);
NSLog(#"%#", test);
And here's the output:
2013-08-24 13:59:27.820 0.1[1440:19d03] (
1,
2,
3
)
2013-08-24 13:59:27.820 0.1[1440:19d03] -[__NSArrayI length]: unrecognized selector sent to instance 0xa533800
Why is the length message getting sent to my array? And how can I prevent this crash? Thanks!
I think the problem is that you have overriden memberIds methods.
In your model, memberIds should return a string, and you've overriden it to return an NSArray.
So, when your managedobjet is trying to generate its description, it is assuming that memberIds is a string.
I think the best way to do that it to name your custom getter / setter differently than your model attribute.
I have the following problem: In a certain view controller I have a NSDictionary, which itself is an entree in an NSArray object. This view controller has a child view which displays some of the key value pairs that are in this dictionary. Since I need only some key value pairs, I construct a new dictionary object from which I then remove the key value pair I do not want to have in it. To be able to access this dictionary in the child view, I though it would be possible to just set the dictionary via a property, which seems to work fine. To illustrate with some code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
...
// today is an instance of NSArray holding a number of NSDictionary objects
NSDictionary *completeData = [self.today objectAtIndex:row];
NSDictionary *data = [[NSDictionary alloc] initWithDictionary:completeData];
[data removeObjectForKey:#"name"];
SomeViewController *childController = [[SomeViewController alloc] init];
childController.data = data;
[self.navigationController pushViewController:childController animated:YES];
[childController release];
// This results in a EXC_BAD_ACCESS error when navigating back to the parent
// view and calling didSelectRowAtIndexPath a second time. When commenting this
// line out, the error dissapears, but now the object leaks
[data release];
}
The problem arises when, after returning to the parent view, I try to replace the NSArray object (today) by an updated version of itself by calling
- (void)refreshDataNotification:(NSNotification *)notification {
if (notification) {
self.today = [NSArray arrayWithArray:[[[MyAppDelegate getAppDelegate] todaySchedule]
objectForKey:#"data"]];
[self.tableView reloadData];
}
}
Note that as long as I do not release 'data' in didSelectRowAtIndexPath I get no error, but then the object leaks. When I do release it, I receive an EXC_BAD_ACCESS when refreshDataNotification is executed.
If someone has any clue as to what I might be doing wrong, then please do share with me.
Set the environment variable NSZombieEnabled to YES to get more helpful error messages about over releasing objects. (Set the environment variable by viewing details under 'Executables')
Also, it would be helpful to see how you've defined your properties. (e.g. what is the #property for data in SomeViewController?)
ps - I know you haven't pasted actual code, but data is a terrible instance name for an NSDictionary. dict is better - but something more descriptive would make your code easier to understand.