selector and PerformSelector - ios

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.

Related

Accessing an instance throws exception - NSDictionary NSArray

I am a beginner in Objective-C and do struggle a bit with dictionaries and arrays.
My goal is to create a table view where every cell contains a picture from a server. The url for the pic comes from a JSON call. I downloaded the example from Apple called "lazy table view" and tried to merge it with the one I found inside a Standford iOS Class. So far everything works, besides the picture. Xcode throws the following exception, once I try to access the icon:
-[__NSCFDictionary Icon]: unrecognized selector sent to instance 0x10b845ff0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFDictionary Icon]: unrecognized selector sent to instance 0x10b845ff0'
The code in question looks like this:
#interface FlickrPhotosTVC : UITableViewController
#property (nonatomic, strong) NSArray *photos;
#end
#interface ClRecord : NSObject
#property (nonatomic, strong) UIImage *Icon;
#define FLICKR_PHOTO_TITLE #"title"
ClRecord *clRecord = self.photos[indexPath.row];
cell.textLabel.text = [clRecord valueForKeyPath:FLICKR_PHOTO_TITLE]; // works
if (clRecord.Icon) NSLog(#"test"); // throws exeption
Somehow this seems to be releated with the types. Trying to access an array, while accesing a dictionory or similar. I found some releated posts on stack, but could not solve it so far.
Thank you for any help!
The objects stored in self.photos seems to be dictionaries objects and not ClRecord objects. The error you get say that you are calling Icon method on a dictionary object.
Check where you fill your photos array to make sure you put ClRecord objects in it and not dictionaries objects.
You could do NSLog(#"My photos array is ---%#",self.photos);
This will give you what exactly you have in your array.
Case 1: If you have a dictionary objects then :- You should do NSDictionary *dict = self.photos[indexPath.row]; ClRecord *clRecord = [dict objectForKey:#"YourKeyForImage"];
Case2: If you have images as you expect, then make sure you import ClRecord header file, you have non nil object at your specified [indexPath.row] index and other necessary initializations.
Hope this helps. Link1

Embedded Table View in UIView container

So here's the case:
I wish to fill the prototype cells with the names of the friends selected in the UIPickerView over there. I have programatically filled the picker with the correct data, and set its properties using the delegate functions.
The "New Game Friends View" you see here has its own viewcontroller subclass, as has the table view, which I attempt to embed into a UIView on the "New Game Friends View".
I have in many ways tried to add data to the prototype cells, but with no luck. Here's my current addBtnClicked function:
- (IBAction)addBtnClicked:(id)sender {
WHGFriendTableViewController* tabView = (WHGFriendTableViewController*) [[self childViewControllers] objectAtIndex:0];
NSInteger row = [friendPicker selectedRowInComponent:0];
[[tabView selectedFriends] addObject:[[self friendList] objectAtIndex:row]];
}
This pretty much crashes my app. Whenever I hit the Add Friend button, the iPhone sends an abort signal, and gives back this message:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIViewController
selectedFriends]: unrecognized selector sent to instance 0x978d530'
Any help with this problem is very appreciated. Thanks in advance!
Do you have a property called selectedFriends on your New Friends view controller?
If the answer is yes try:
- (IBAction)addBtnClicked:(id)sender {
WHGFriendTableViewController* tabView = (WHGFriendTableViewController*) [[self childViewControllers] objectAtIndex:0];
NSInteger row = [friendPicker selectedRowInComponent:0];
[[self selectedFriends] addObject:[[self friendList] objectAtIndex:row]];
}
If selectedFriends is a property of WHGFriendTableViewController, you should create a public method that adds a friend to the Mutable Array and call it from addBtnClicked IBAction.

Unrecognized Selector on BOOL Property

I've been racking my brain over this seemingly simple issue. I have a XYZObject class where I declare:
#property BOOL checked;
In my View Controller, I import the object and whenever I use 'checked', the app compiles fine but breaks at runtime wherever 'checked' is used, for example:
XYZObject *tableitem = [myDictionary[currentCategory] objectAtIndex:indexPath.row];
if (tableitem.checked) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
This was working fine until I deleted and re-added the XYZObject class, so I've been debugging under the assumption that something in the file path is what's screwing things up. But I can click on 'checked' in my VC and under Quick Help it shows the proper reference to XYZObject. This is the exact error:
[__NSCFString checked]: unrecognized selector sent to instance
EDIT/UPDATE:
With some help I've realized the issue is that when I changed my datasource from manual declaration in the ViewController, to importing a Plist, I completely scrapped my XYZObject and didn't account for it. Here is the original way I declared my dictionary:
XYZCategory *category1 = [[XYZCategory alloc]init]; category1.categoryArray = #"First Category"; [categoryArray addObject:category1];
XYZObject *object1 = [[XYZObject alloc]init]; object1.objectName = #"My String"; [objectArray addObject:object1];
myDictionary[category1.categoryArray] = objectArray;
When I switched to the Plist, the code changed to:
NSString *path = [[NSBundle mainBundle] pathForResource:#"myDictionaryPlist" ofType:#"plist"];
NSMutableDictionary *plistDictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:path];
objectArray = plistDictionary[#"First Category"];
myDictionary[category1.categoryArray] = objectArray;
And then for reference, XYZObject makes the following declarations:
#property NSString *objectName;
#property BOOL checked;
So the dictionary problem would be that I'm just pulling the direct strings for the objectArray, instead of a set of XYZObjects. I'm going to keep testing but I'm pretty sure I just have to re-define objectArray to be a set of objects based on what's pulled from the Plist.
But I also think that since I'm using the Plist now to create a dictionary (that is popped into a table where the Keys are sections and Values are rows), I can simplify things by removing the XYZCategory and XYZObject all together. Not sure if that's possible but I'm going to work towards it.
As the error message is suggesting, tableitem is actually a NSString, contrary to what you expect.
You are probably populating the dictionary in the wrong way.

heightForRowAtIndexPath occasionally crashes when loading, NSCoding - iOS

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.

Proper way of creating new objects which are copies of NSDictionary and NSArray objects defined in app delegate

I am wondering what the correct way is to make a copy of an object defined in the app delegate or a singleton object. In short, I am making an app which requires a user to login. This login view is just a modal view controller on top of the 'real' app, which consists of a tabbarcontroller, plus some tableview controllers. After a successful login, there is send a data request to a remote server, and the modal view controller is dismissed, revealing the tabbar controller and table views holding the XML data. To parse the incoming data, I have created a singleton object named DataParser, which has interface
...
#interface DataParser : NSObject {
// Data objects that hold the data obtained from XML files
NSMutableDictionary *personnel;
NSMutableDictionary *schedule;
NSMutableDictionary *today;
}
#property (nonatomic, retain) NSMutableDictionary *personnel;
#property (nonatomic, retain) NSMutableDictionary *schedule;
#property (nonatomic, retain) NSMutableDictionary *today;
...
Now in these dictionaries I store (mutable) dictionaries and arrays holding NSString objects with the parsed XML data. Since I do not want to modify these original objects holding the parsed data (that is to say, I only want to modify them at the login stage, but not in any of the tableview controllers), I am creating a new dictionary object which holds a copy of the content of one of the dictionaries above in each tableview controller. So for instance, in the loadView of a view controller called ScheduleViewController I have
...
#interface ScheduleViewController : UITableViewController {
NSDictionary *copyOfSchedule;
}
#property (nonatomic, retain) NSDictionary *copyOfSchedule;
...
#end
#implementation ScheduleViewController
#synthesize copyOfSchedule;
- (void)loadView {
[super loadView];
DataParser *sharedSingleton = [DataParser sharedInstance];
self.copyOfSchedule = [NSDictionary dictionaryWithDictionary:sharedSingleton.schedule];
}
...
Now this seems to work fine. The only difficulty arises however, when the user 'logs out', which entails popping the login modal view controller back on the stack. When the user presses the login button again, then a new XML data request is send to the server and the dictionaries in the singleton object get refreshed with the (new) data (I check if they contain any data, if so I call removeAllObjects before filling them up again with newly parsed data). At this point the dictionaries in all view controllers should be updated too, however I am not quite sure how to go about this the right way. I have noticed that loadView is not always called again in this case and so to this end I have added the same code as above in loadView to every viewWillAppear method. After navigating back and forth between the different views or navigating back and forth between child views of a tableview a couple of times, I receive an EXC_BAD_ACCESS error however. I suspect this has to do with not properly retaining the copies of the original dictionaries, but I don't seem to be able to find a solution around this. Instead of using dictionaryWithDictionary, which I suspect is not the right way to go anyway, I also tried a different approach, where instead of using objects of type NSDictionary in ScheduleViewController I use NSMutableDictionary. So:
...
#interface ScheduleViewController : UITableViewController {
NSMutableDictionary *copyOfSchedule;
}
#property (nonatomic, retain) NSMutableDictionary *copyOfSchedule;
...
#end
#implementation ScheduleViewController
#synthesize copyOfSchedule;
- (void)loadView {
[super loadView];
DataParser *sharedSingleton = [DataParser sharedInstance];
self.copyOfSchedule = [[NSMutableDictionary alloc] initWithDictionary:sharedSingleton.schedule];
}
- (void)viewWillAppear {
DataParser *sharedSingleton = [DataParser sharedInstance];
[self.copyOfSchedule removeAllObjects];
[self.copyOfSchedule addEntriesFromDictionary:sharedSingleton.schedule];
[self.tableView reloadData];
}
...
But this doesn't get rid of the EXC_BAD_ACCESS errors. To make a very long story short: what would be the best way to go about making independent copies of objects defined in a singleton object or app delegate and which can be dynamically updated at request? Since I am already rather into the project and lots is going on, I realize that my question may be a bit vague. Nonetheless I hope there is somebody who could enlighten me somehow.
Deep copies are often made recursively. One way to do it would be to add -deepCopy methods to NSDictionary and NSArray. The dictionary version might go like this:
- (NSDictionary*)deepCopy
{
NSMutableDictionary *temp = [self mutableCopy];
for (id key in temp) {
id item = [temp objectForKey:key];
if ([item respondsToSelector:#sel(deepCopy)] {
// handle deep-copyable items, i.e. dictionaries and arrays
[temp setObject:[item deepCopy] forKey:key]
}
else if ([item respondsToSelector:#(copy)]) {
// most data objects implement NSCopyable, so will be handled here
[temp setObject:[item copy] forKey:key];
}
else {
// handle un-copyable items here, maybe throw an exception
}
}
NSDictionary *newDict = [[temp copy] autorelease];
[temp release]
return newDict;
}
I haven't tested that, so be a little careful. You'll want to do something similar for NSArray.
Note that views are not copyable.
It is quite a typical pattern that you build an array or dictionary with some code, so clearly it must be mutable while you add bits to it, and when you're done you don't want it ever to change. To do this:
Have a property like
#property (...) NSArray* myArray;
When you calculate the contents of myArray, use a mutable array to build it, like
NSMutableArray* myMutableArray = [NSMutableArray array];
When you're done building the array, just use
self.myArray = [NSArray arrayWithArry:myMutableArray];

Resources