i am working on a map application, in that i like to drop the pins (as in Zillow apps) when ever user change the map view. I am using following code code. i am try to load the xml data from server using NSAutoreleasepool to do the xml parsing in the background thread.
(void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated{
NSLog(#"inside region did changed ");
urlString =[NSString stringWithFormat: #"http://asdfasdasdf.com/asdfasdf/mapxml.php];
[stories1 release];
[mapview removeAnnotations:eventPoints1];
eventPoints1 = [[NSMutableArray array] retain];
[self performSelectorInBackground:#selector(callParsing) withObject:nil];
}
-(void)callParsing{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self parseXMLFileAtURL:urlString];
[self performSelectorOnMainThread:#selector(droppingPin) withObject:nil waitUntilDone:YES];
[pool drain];
}
The above code is working fine, but once i changed the mapview, the appllication get crashed. Anyone can help me to fix the issue?
thanks in advance.
urlString is already autoreleased when it is returned from stringWithFormat.
Since you are using urlString in callParsing which is executed on a different thread, you should pass it as an object to that method. Otherwise you risk it getting released before the callParsing method is executed and thus causing the crash:
...
[self performSelectorInBackground:#selector(callParsing:) withObject:urlString];
...
-(void)callParsing:(NSString*)urlString {
...
Related
I get leaks if I dont put it in dealloc. I get a crash EXC_BAD_ACCESS If I do. I cannot see anything wrong with this code. The bad access is pointed at [events release]. Have I made a mistake in the code below or is Instruments just having a laugh at my expense?
events is an NSArray
#interface EventsViewController : UITableViewController
{
#private
NSArray *events;
}
- (void)viewDidLoad
{
events = [[self getEvents] retain];
}
- (void)dealloc
{
[events release];
[super dealloc];
}
- (NSArray*)getEvents
{
NSMutableArray *response = [[[NSMutableArray alloc] init] autorelease];
//Some sql
while(sqlite3_step(statement) == SQLITE_ROW)
{
Event *event = [[[Event alloc] init] autorelease];
event.subject = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
[response addObject:event];
}
return response;
}
Update
A lot of you are saying the code is fine which is a plus. I dont manipulate events elsewhere - I have removed any code that does to try and single out the crash. Perhaps its in the parent view?
This is the click event that pushes the EventsViewController:
- (void)eventsClick:(id)sender
{
EventsViewController *eventsViewController = [[EventsViewController alloc] initWithNibName:#"EventsViewController" bundle:nil];
eventsViewController.anywhereConnection = anywhereConnection;
eventsViewController.contact = contact;
[[self navigationController] pushViewController:eventsViewController animated:YES];
[eventsViewController release];
}
The crash is actually happening when I return to the parent view. (I think it is considered a parent in this scenario). But perhaps the [eventsViewController release] just triggers dealloc in the EventViewController.
Have you considered just refactoring your code to use ARC? It works with iOS 4 and up and will make your life a lot easier. There are plenty of tutorials out there that will guide you how to do it, and will remove the need to manually figure out the nuances of memory management.
If your Events object has property 'subject' set as assign, then the results of stringWithUTF8String: will not be retained. (Same thing if Events is a C++ object.)
The stringWithUTF8String: method returns an auto-released object that will be released at the next turn of the event loop.
There is a huge difference when you reference a variable via "self", and when you don't.
When you use
events = [[self getEvents] retain];
the memory allocated in getEvents never gets stored in the class property and is basically a leak.
You need to use
self.events = [self getEvents]; // no need to use retain if property is correctly defined.
Then
[events release];
should work fine.
try putting
events = nil;
in dealloc.
While parsing of the twitter data, I used threads to call the main URL to download the data. It does the downloading perfectly but when I hit the back button while the data is downloading it throws performSelectorOnMainThread message deallocated. I know we can use isCancelled but its not working for me yet. Does anyone have come across this issue and have resolved it.
- (void)LoadTwitterData
{
NSString *urlString =#"http://search.twitter.com/search.json?q=tabc&result_type=recent&rpp=2500";
NSURL *url = [NSURL URLWithString:urlString];
NSString *jsonString = [NSString stringWithContentsOfURL:url];
NSDictionary *values = [jsonString JSONValue];
/**** Throws here *****/
[self performSelectorOnMainThread:#selector(didFinishLoadingResults:) withObject:values waitUntilDone:NO];
}
If you spin off a thread using a selector on self, you need to make sure that self is retained for the duration of that thread, otherwise (as in your case) self can be deallocated and your thread will try to call back into a zombie. The easiest way to do this is to pass self to the thread as an argument. If you use performSelectorInBackground:withObject: you should do something like this:
[self performSelectorInBackground:#selector(LoadTwitterData) withObject:self];
Or if you use NSThread you should pass self to the object: initializer argument.
In fact the safest way to use thread methods is to make the method static like this:
+ (void)LoadTwitterData:(id)arg
{
// ...
MyController *self = arg;
// ... do work
[self performSelectorOnMainThread:#selector(didFinishLoadingResults:)
withObject:values waitUntilDone:NO];
}
This way you are unable to access instance variables by accident which avoids various multi-threading issues. Any and all data the thread needs, including the self to callback to, should be passed in as 'arg', which can be an array or dictionary or whatever you need. This way you know that everything the thread needs will be retained for the duration of the thread, and because you aren't accessing instance variables through self, another thread can't go and change them around underneath you.
Another thing you should do in a thread method is wrap the whole thing with an autorelease pool:
- (void)LoadTwitterData
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#try {
// ...
} #finally {
[pool drain];
}
}
If LoadTwitterData: is in a background thread, you need to create an Auto release pool (If you haven't already). Surround your code with-
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
//your code
[pool drain];
I have a table view and I'd like to download an icon image (100x75) to each row asynchronously. I've followed many tutorials so far but I can't seem to figure it out. How should I do it?
Does anyone recommend just doing it using the standard NSURLConnection API or should I use one of those classes/libraries that are available online to do so? If so, what do you recommend?
Of course, I need it to be fast, efficient and does not leak. I also don't want the downloading to affect the scrolling.
Thank you!
Two options I can think of:
(1) Use ASIHTTPRequest.
(2) A custom implementation that spawns a thread in which you load the image using a combination of NSURL/NSData. Once the image is loaded, send it to a method on the main UI thread using performSelectorOnMainThread:withObject:.
NSThread *t = [[NSThread alloc] initWithTarget:self selector:#selector(loadImage:) object:nil];
[t start];
[t release];
-(void) updateImage:(id) obj {
// do whatever you need to do
}
-(void) loadImage:(id) obj {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [NSURL URLWithString:#"imageurl"];
NSData *imageData = [[NSData alloc]initWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:imageData];
[imageData release];
[self performSelectorOnMainThread:#selector(updateImage:) withObject:data waitUntilDone:YES];
[pool release];
}
I'd recommend you using ASIHTTPRequest. Its simple and pretty fast.
Here is a link to the documentation - ASIHTTPRequest
EDIT
You need to download images for visible cells only.
Heres a sample:
- (void)loadContentForVisibleCells
{
NSArray *cells = [self.tableView visibleCells];
[cells retain];
for (int i = 0; i < [cells count]; i++)
{
...
// Request should be here
...
}
[cells release];
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;
{
if (!decelerate)
{
[self loadContentForVisibleCells];
}
}
Anyway u'll need to code a lot to make things work good and fast.
I'm running some code in a background thread to get a text file from a service. That code fires a delegate at some point. It throws as SIGABRT error once the delegate is being called and well, my concept doesn't sound convincing to me either.
The code running at the background thread:
- (void)FetchStores
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Fetch from service
NSString *serviceURL = #"http://../index.html";
NSURL *myURL = [NSURL URLWithString:serviceURL];
NSData *dataRep = [NSData dataWithContentsOfURL:myURL];
storesList = [[Stores alloc] init];
storesList.storesDelegate = self;
[storesList FetchWithNSData:dataRep];
[pool release];
}
The storesList object will fire a delegate once all the stores have been extracted from the service. The delegate is getting caught by a function at the main thread.
Do you have any suggestions what am I doing wrong ?
Thank you,
f.
When calling the delegate, somewhere, you should make the switch to the main thread.
Especially if somewhere, you are updating the UI based on the data fetched.
You can use
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
to make the switch.
Maybe like this:
storesList = [[Stores alloc] init];
storesList.storesDelegate = self;
[storesList performSelectorOnMainThread:#selector(FetchWithNSData:) withObject:dataRep waitUntilDone:TRUE];
In your case, you should use waitUntilDone:TRUE so that the FetchWithNSData method gets a chance to retain the data.
It seems quite likely that FetchWithNSData: does not retain the passed dataRep and the data gets deallocated on the next line where you drain the local autorelease pool?
I have a strange issue, when it comes to parsing XML with NSXMLParser on the iPhone. When starting the app, I want to preload 4 table-views, that are populated by RSS-Feeds in the background.
When I init the table-views one-by-one, than loading, parsing and displaying all works like a charm. But when I try to init all view at once (at the same time), than it seems, that the XML-parser-instances are disturbing each other. Somehow data from one XML-Feed are "broadcasted" into other xml-parser instances, where they do not belong. Example: there is a "teammember" item, with "This is my name". When this bug occurs, there is a string from another xml-feed added, i.e. resulting in: "This is my name58", where 58 is the chart-position of something from the other view. "58" seems to miss then on the other instance.
It looks to me, that this bug occurs because of the NSXMLParser-delegate method:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
if (!currentStringValue) {
currentStringValue = [[NSMutableString alloc] initWithCapacity:50];
}
[currentStringValue appendString:string];
}
In this case "by coincidence" bytes are appended to strings, where they do not belong to.
The strange thing is, that every instance of NSXMLParser is unique, got its own unique delegates, that are attached to their own ViewController. Every parsing-requests spawns it own background-task, with its own (also also unique named) Autorelease-pool.
I am calling the NSXMLParser like this in the ViewController:
// prepare XML saving and parsing
currentStringValue = [[[NSMutableString alloc] initWithCapacity:50] retain];
charts = [[NSMutableArray alloc] init];
NSURL *url = [[NSURL alloc] initWithString:#"http://(SOME XML URL)"];
xmlParser = [[[NSXMLParser alloc] initWithContentsOfURL:url] retain];
//Set delegate
[xmlParser setDelegate:self];
//loading indicator
progressWheel = [[[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(150.0,170.0,20.0,20.0)] autorelease];
progressWheel.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
[self.view addSubview:progressWheel];
[progressWheel startAnimating];
// start loading and parsing the xml-feed in the background
//[self performSelectorInBackground:#selector(parse:) withObject:xmlParser]; -> I also tried this
[NSThread detachNewThreadSelector:#selector(parse:) toTarget:self withObject:xmlParser];
And this is one of the background-tasks, parsing the feed:
-(void)parse:(NSXMLParser*)myParser {
NSAutoreleasePool *schedulePool = [[NSAutoreleasePool alloc] init];
BOOL success = [myParser parse];
if(success) {
NSLog(#"No Errors. xmlParser got: %#", myParser);
(POST-PROCESSING DETAILS OF THE DATA RETURNED)
[self.tableView reloadData];
} else {
NSLog(#"Couldn't initalize XMLparser");
}
[progressWheel stopAnimating];
[schedulePool drain];
[myParser release];
}
What could cause this issue? Am I calling the background-task in the right way? Why is this bug approaching, since every XML-Parser got its own, unique instance?
You should not be updating UI elements (like progressWheel) from inside a background thread. UI updates should be done on the main thread.
Use -performSelectorOnMainThread:withObject:waitUntilDone: to update UI elements from within a background thread.
I've released an open source RSS/Atom Parser for iPhone and it makes reading and parsing web feeds extremely easy.
You can set it to download the data asynchronously, or you could run it in a background thread synchronously to collect the feed data.
Hope this helps!