This is my first post on stackoverflow.com so please be kind (rewind) ;)
I have a navigation based application whose purpose is to display blog posts (title) in a Table View (with JSON).
The problem I ran into occurred when a cell got out of the screen and then back in.
I was getting a EXC_BAD_ACCESS (because I sent a message to a deallocated instance), so I struggled to understand where it came from and I finally found a solution. But the fact is I don't exactly understand how the problem occurs. That's why I need someone to enlighten me, I think this is fundamental understanding !
When the connection to the JSON web service has finished loading, I parse the JSON code to obtain a list of blog posts (recentPosts), then I create a BlogArticle object for each post (blogArticle), store it in a MutableArray iVar (allEntries) and insert a row in the Table View :
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[connection release];
NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
[responseData release];
NSError *error;
SBJsonParser *json = [[SBJsonParser new] autorelease];
NSDictionary *recentPostsData = [json objectWithString:responseString error:&error];
[responseString release];
NSArray *recentPosts = [recentPostsData objectForKey:#"posts"];
int i = 0;
for (NSDictionary *post in recentPosts) {
BlogArticle *blogArticle = [[BlogArticle alloc] initWithDictionary:post];
[allEntries insertObject:blogArticle atIndex:i];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:i inSection:0]] withRowAnimation:UITableViewRowAnimationRight];
i++;
}
}
Here's the initialisation of the BlogArticle object which turned to be the origin of the problem :
- (id)initWithDictionary:(NSDictionary *)article
{
if (self = [super init])
{
// title = [[[article valueForKey:#"title"] gtm_stringByUnescapingFromHTML] copy];
// title = [[NSString alloc] initWithString:[[article valueForKey:#"title"] gtm_stringByUnescapingFromHTML]];
title = [[article valueForKey:#"title"] gtm_stringByUnescapingFromHTML];
}
return self;
}
So every Objective-C programmer who isn't as noobish as me is able to tell that title is never allocated before being assigned. If I uncomment one of the two lines above it will work. The program crashes exactly when I try to initialize a cell with that title variable, here :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
NSLog(#"indexPath.row = %i", indexPath.row);
// Configure the cell.
BlogArticle *article = [allEntries objectAtIndex:indexPath.row];
cell.textLabel.text = article.title;
return cell;
}
Now, what I need to understand is why it does compile/work without allocating the iVar and where exactly it causes trouble (or where exactly the content of title is released causing the program to crash).
Any good resource (noob friendly) about memory management in iOS environment would be much appreciated.
Thanks in advance :)
This line
title = [[article valueForKey:#"title"] gtm_stringByUnescapingFromHTML];
is allocating an autoreleased string. Essentially, think of it that an autoreleased string will get released at the end of the method (though it can last longer, it's useful to think of it that way).
You know the string is autoreleased because the name of the method gtm_stringByUnescapingFromHTML does not start with alloc, new, copy or mutableCopy.
You can add retain to this to stop it getting autoreleased:
title = [[[article valueForKey:#"title"] gtm_stringByUnescapingFromHTML] retain];
Now you own the string, and it will not get released until you say so.
The best summary I know of is Apple's own documentation here.
Well, the problem is, that you have to initialize your object, if you want to manage the memory of it on your own. Why should you manage now the memory of title?
Quite simple:
Every object reference, that is stored in an Array, Set, Dictionary etc. is managed by the Array, Dictionary and Set.
If you now just use this reference (by writing: "title = ...") in your cell, you will add the reference also to the cell. And now the cell is also responsible for the object-reference. So if the tableView wants to release your cells, which will happen from time to time to save memory, the cell will release your title-object. And this would cause the NSDitionary to be quite sad, since the NSDictionary wants to take care about the objects stored within itself.
So you could write the following in the tableView-method:
cell.textLabel.text = [article.title retain];
Or the commented lines of your own method.
That means, you will "raise" the storage-level of your object up and if it gets released, the storage level itself will be decreased by one.
If the storage-level will reach zero, it will be completely released (that should happen, if your tablecell is released AND your NSDIctionary)
I hope i could help you a bit :)
Related
I am going to quickly cut to the chase here, I am using the MailCore API to connect to email and download messages into an array, then take those messages in the array and display them into a Table View.
Here is the code (retrieved from official MailCore website) that will download ALL the messages off of the server:
messageSet = [inbox messagesFromUID:1 to:0 withFetchAttributes:CTFetchAttrEnvelope];
Basically, this will download all the messages from the server and I can confirm this by reporting the contents of the Array with an NSLog, all the messages are there.
Here is now the code that will take ONLY THE FIRST MESSAGE (index 0) and add that to my messages array:
CTCoreMessage *msg = [messageSet objectAtIndex:0];
BOOL isHTML;
isHTML = YES;
messages = [[NSMutableArray alloc] init];
sendernames = [[NSMutableArray alloc] init];
[messages addObject:[msg bodyPreferringPlainText:&isHTML]];
NSString *sender = [NSString stringWithFormat:#"%#", [msg sender]];
[sendernames addObject:sender];
[tableView reloadData];
So again, this takes the message at index 0, which is the first message and adds it to the messages array. I also have code in there that gets the senders name and adds that to the sender array, ignore that.
Here is the code in my cellForRowAtIndexPath method (just a clipping of the values I am setting for my elements on the cell):
cell.nameText.text = [sendernames objectAtIndex:0];
cell.messageText.text = [messages objectAtIndex:0];
As you can see, it displays the index 0 object, the first message, the same one we added earlier.
Lastly, here is the code that sets how many rows I have. It is based on the messagesSet array which downloads ALL of the messages.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [messageSet count];
}
My question is, how can I modify my code to do two things:
1.) Add ALL the DOWNLOADED objects to the array and not just the first one.
2.) Modify the cellForRowAtIndexPath code to display all the objects in the messages array.
Thanks so much in advance!
Well firstly you iterate through the array and fill your array as so
for (CTCoreMessage *msg in messageSet) {
[messages addObject:[msg bodyPreferringPlainText:&isHTML]];
NSString *sender = [NSString stringWithFormat:#"%#", [msg sender]];
[sendernames addObject:sender];
}
Then in the cell for row just do what you were doing, but instead of passing 0 for object at index pass: indexPath.row
Hope this helps.
The model backing your table is really messageSet. You assign that with the result of the server call. Just use that in your delegate methods:
After the download, call [self.tableView reloadData];
numberOfRowsInSection: can return messageSet.count;
cellForRowAtIndexPath: will be called sequentially for each visible indexPath. It doesn't need to deal with all messages, just the one at a time, via [messageSet objectAtIndex:indexPath.row];
I'm making an app which tries to read the information from the .plist file (put there parsed JSON).
The reading from file flows nice: got the array of dictionaries, but while trying to display it on tableview, the problems start. The initial view is loaded properly, but when I start scrolling, the app crashes.
#define DOCUMENTS [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject]
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *filePathDocArray = [DOCUMENTS stringByAppendingPathComponent:#"filters.plist"];
NSString *filePathBundleArray = [[NSBundle mainBundle] pathForResource:#"filters" ofType:#"plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:filePathDocArray]) {
[[NSFileManager defaultManager] copyItemAtPath:filePathBundleArray toPath:filePathDocArray error:nil];
NSLog(#"File saved");
} else {
NSLog(#"File already exists");
filters = [NSArray arrayWithContentsOfFile:filePathDocArray];
}
}
Here I get all the info I need into filters array (checked by looping). Then:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [filters count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *myIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:myIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:myIdentifier];
}
NSInteger crow = indexPath.row;
NSDictionary *story = [filters objectAtIndex: crow];
cell.textLabel.text = [story objectForKey:#"Name"];
cell.detailTextLabel.text = [story objectForKey:#"Description"];
return cell;
}
#end
When the app starts everething is OK: I see the normel table view, but when I start scrolling it crashes
After series of breakpoint debugs I evaluated, that after the applications starts on Simulator, the link on array filters screws, so when I try to populate the next cell, the story dictionary can't be properly created.
What sort of problem it can be?
Here the console report:
2012-09-22 13:37:43.545 JSONExample[4559:207] -[__NSCFString
objectAtIndex:]: unrecognized selector sent to instance 0x6a083c0
2012-09-22 13:37:43.547 JSONExample[4559:207] *** Terminating app due
to uncaught exception 'NSInvalidArgumentException', reason:
'-[__NSCFString objectAtIndex:]: unrecognized selector sent to
instance 0x6a083c0'
are you using ARC??
if not then autorelease the cell first!!
and if filters is not a retain property.. kindly make it one and synthesize it and again if ARC not used then release it in the dealloc block.. that should do it i suppose..
-[NSCFString objectAtIndex:]: unrecognized selector seems to be the same problem.
Your 'filters' variable is an NSString/CFString at the time you call objectAtIndex: - not an array, as you would assume. The solution given in the question linked to is to retain your filters array whenever it's set.
If you wote property for the filters like #property (nonatomic, retain) NSArray *filters; you need to write like self.filters = [NSArray arrayWithContentsOfFile:filePathDocArray];
Else you need to write like filters = [[NSArray arrayWithContentsOfFile:filePathDocArray] retain];
You need to keep in mind another thing..
if you do this step
NSDictionary *story = [[filters objectAtIndex: crow]retain];
every time your cellForRowAtIndexPath is called the retain count would increase by 1. But you will delloc it only once.
So there will be memory leaks in your applications.
I suggest you go through the memory management guide once. its a small document. at max it will take one day of yours. But will feel more confident of what to do and what not to do.
http://developer.apple.com/library/ios/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/CFMemoryMgmt.pdf
Cheers!! And Happy Coding!!
I have a signature page, were the user can sign their name.
It needs to then be saved to NSDictionary, but i want to call a List of the keys to be text in a TableView for each row or cell.
so:
"viewImage = saved as object to key:Random Number"
That parts somewhat easy, the hard part is when i call it on the other Page to the TableView.
It Exits the App with Error"SIGABRT". Now all my Delegates are in place and working...i believe.
now heres some example code:
FirstPage.m
UIImagePNGRepresentation(viewImage);
NSMutableArray *innerArray = [[NSMutableArray array]init];
[innerArray addObject:viewImage];
[SignatureSave setObject:innerArray forKey:#"5599"];
simple Enough, but doesnt give me an error.
SecondPage.m
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FirstPage *appShare = (FirstPage *)[[UIApplication sharedApplication] delegate];
NSArray *dataDuplicate = [[NSArray alloc]init ];
dataDuplicate = [appShare.SignatureSave allKeysForObject:#"innerArray"];
static NSString *CellIdentifier = #"Cell";
NSLog(#"%#",dataDuplicate);
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero]autorelease];
}
if (dataDuplicate != nil) {
cell.textLabel.text = [dataDuplicate objectAtIndex:indexPath.row];
}
else
{
UIAlertView *CellAlert = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Error Loading content, Try Again Later." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[CellAlert show];
[CellAlert release];
}
return cell;
}
#end
Now, How do i get the viewImage to save to the NSDictionary, to be able to call it on the SecondPage and display the name of the objects in the TableVIew?
I really don't understand whats your problem exactly.
first of all, is your dictionary a retained object?
//FirstPage.h
#property (nonatomic, retain) NSDictionary *SignatureSave;
//FirstPage.m
#synthesize SignatureSave;
…
UIImagePNGRepresentation(viewImage);
NSMutableArray *innerArray = [NSMutableArray array]; // using "array" is equivalent to alloc-init-autorelease
[innerArray addObject:viewImage];
[self.SignatureSave setObject:innerArray forKey:#"5599"];
// OR setting the array directly:
UIImagePNGRepresentation(viewImage);
NSArray *innerArray = [NSArray arrayWithObject:viewImage];
[self.SignatureSave setObject:innerArray forKey:#"5599"];
// OR even setting the image directly to the dictionary:
UIImagePNGRepresentation(viewImage);
[self.SignatureSave setObject:viewImage forKey:#"5599"];
now if you access the object by writing self. in front it will call the retain and your object will stay alive. Otherwise it would be autoreleased at the end of the method. This will fix the problem that your dictionary is maybe not present/available at table view creation and you don't have to use a singleton.
what are you trying to access with this code?
FirstPage *appShare = (FirstPage *)[[UIApplication sharedApplication] delegate];
with [[UIApplication sharedApplication] delegate] you get your application delegate (obviously). These are the MyAppNameAppDelegate files but you treat it as a FirstPage class.
Just NSLog() to check you get the right class, the one you expect.
NSLog(#"%#", [[[UIApplication sharedApplication] delegate] class]);
here you have a potential leak, you alloc-init but never release it:
NSArray *dataDuplicate = [[NSArray alloc]init ];
dataDuplicate = [appShare.SignatureSave allKeysForObject:#"innerArray"];
furthermore you can simplify it (will be autoreleased):
NSArray *dataDuplicate = [appShare.SignatureSave allKeysForObject:#"innerArray"];
and here you have another issue.
Why do you call all keys for the object #"innerArray"?
you don't have such an object and it's in many more cases wrong. innerArray was your previously named array in FirstPage.m but it is only for you as a developer to remember the variable better. After compilation it will have a cryptic name anyway. You could access your key #"5599" if you like but I don't think you want this. In your case you want to access all keys of the dictionary so simply call:
NSArray *dataDuplicate = [appShare.SignatureSave allKeys];
now you will have an array with all keys of your dictionary and you can access them like you do with objectAtIndex:.
NSString *keyName = [dataDuplicate objectAtIndex:indexPath.row];
cell.textLabel.text = keyName;
id theObject = [appShare.SignatureSave objectForKey:keyName]; // for example the image
Tell me if this solves your problems or tell me how I misunderstood your question.
I found the answer to this to be quit simple actually,
I ended up going with the Singleton Method instead of the Global Variable Method.
Now the Singleton Method looks terrifying but its quit simple, See here.
The main difference i noticed from the singleton method to the global method is,
Global method takes a lot of converting and re-converting.
Though the Singleton Method is working with a single object over many pages or classes.
Now i hope this will better assist people in the future also!
I have a UITableViewController with an NSMutableArray in the header like this:
NSMutableArray *someArray;
Also I have the property declared:
#property (nonatomic, retain) NSMutableArray *someArray;
In the .m file I load the array in the method:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
someArray = [[NSMutableArray alloc] init];
[self loadArrayData];
[tableview reloadData];
}
The table populates great, adding is no problem, but when I try to delete a row, the app crashes on the following line:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
[someArray removeObjectAtIndex:indexPath.row];
//update table etc..
If I use the debugger, I can see the array has some objects, and when I Log indexPath.row I get a value which is inside the array size.
I don't understand why it is crashing on this line... Who can help me?
The console outputs:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x6193890'
I had a very similar problem and I was initiating my array with CoreData like this:
self.allContacts = (NSMutableArray*)[cdc.managedObjectContext executeFetchRequest:fetchRequest error:&error];
trying to cast the result from CoreDataController fetch request to NSMutableArray.
Turns out I need the function called "mutableCopy" at the end of fetch request.
self.allContacts = (NSMutableArray*)[[cdc.managedObjectContext executeFetchRequest:fetchRequest error:&error] mutableCopy];
I don't think cast to (MutableArray *) is required but I have it just in case.
Hope this helps.
I had the same issue a week back. What I did was this:
Declared a NSMutableArray* globalArray as a global within the same tableViewController.m implementation file. Then inside
if (editingStyle == UITableViewCellEditingStyleDelete) {
globalArray = [[NSMutableArray alloc] initWithArray:someArray];
[globalArray removeObjectAtIndex:[indexPath row]];
}
And then I copied back this globalArray into someArray before the reloadData call. Works.
not sure if that cause one of your issues, but if you have property for someArray then use self.someArray instead of just someArray .
Of course that after using self, you should auto release it like this :
self.someArray = [[[NSMutableArray
alloc] init] autorelease];
(call this inside your viewController
init method)
then try to :
[self.someArray removeObjectAtIndex:indexPath.row];
2.
try to call [self loadArrayData]; inside viewDidLoad.
It should be called prior to cellForRowAtIndexPath, and that way you won't need to call reloadData
if that still won't help, then post the code of loadArrayData
When I use static data there is no problem but I use dynamic data with Web services there is problem (table view scrolling cause crash program) why? If I comment these lines add static data it works;
//tempCs is NSDictionary
tempDc = [arrHaberler objectAtIndex:indexPath.row];
cell.textLabel.text = [tempDc valueForKey:#"short_header"];
NSData *imgData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[tempDc valueForKey:#"iphone_src"]]];
UIImage *myImage = [[[UIImage alloc] initWithData:imgData] autorelease];
cell.imageView.image = myImage;
You don't release the imgData. You'd want to do that after creating the UIImage.
Other than that, from your description, maybe the numbersOfRowsInSection method has an error?
EDIT (after discussion):
(crash due to unrecognized selector (ie method from NSArray) sent to instance of NSString)
There are many ways you can come to this state, including accessing some memory that was released and reused (ie missing a retain), or overwriting an array with a string due to some parsing yielding a wrong result.
I try something with made an comment another lines and problem is on this line:
tempDc = [arrHaberler objectAtIndex:indexPath.row];
I change "indexPath.row" to "0" still cause crash... Problem about when is assign data to NSDictionary
There is no null-checking (setting NSData *imgData and setting UIImage *myImage) and there in a synchronous call to server. fmpov, problem is out there.
#VNevzatR i am just asking you that Are you are calling plenty no of images from somewhere....because this is no the problem you UITableView as you said working fine in static data,the problem is some where else, so if you are calling plenty of images at a time....Try doing this way...release that pool.
-(void) parseImages
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Fetch your images here
[self performSelectorOnMainThread:#selector(Done) withObject:nil waitUntilDone:YES];
[pool release];
}
-(void) Done {
[self.tableView reloadData];
}
Hope it will resolve your problem...Good Luck