I am trying to load an image in the background from a url. The code works great if all I pass is the NSUrl. If I try to pass an NSArray with additional variables, it never gets called:
This code works great, LoadImage2 is called which in turn nicely calls ImageLoaded2.
- (void)LoadBackgroundImage2: (char*)pImageURL
{
NSString* pImageURLString = [NSString stringWithFormat:#"%s", pImageURL];
NSLog( #"LoadBackgroundImage2: %#", pImageURLString );
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(LoadImage2:)
object:pImageURLString];
[queue addOperation:operation];
[operation release];
}
- (void)LoadImage2: (NSString*)pImageURL
{
NSLog( #"LoadImage2: %#", pImageURL );
NSData* imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:pImageURL]];
UIImage* image = [[[UIImage alloc] initWithData:imageData] autorelease];
[imageData release];
[self performSelectorOnMainThread:#selector(ImageLoaded2:) withObject:image waitUntilDone:NO];
}
This code does not work. LoadImage never gets called:
- (void)LoadBackgroundImage: (char*)pImageURL :(int)textureID :(int)textureType
{
printf( "LoadBackgroundImage( %s, %d, %d)\n", pImageURL, textureID, textureType );
NSString* pImageURLString = [NSString stringWithFormat:#"%s", pImageURL];
NSArray* pUrlAndReferences = [[[NSArray alloc] initWithObjects: pImageURLString, textureID, textureType, nil] autorelease];
NSOperationQueue *queue = [[NSOperationQueue new] autorelease];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(LoadImage:)
object:pUrlAndReferences];
[queue addOperation:operation];
[operation release];
}
- (void)LoadImage: (NSArray*)pUrlAndReferences
{
NSString* pImageUrl = [pUrlAndReferences objectAtIndex: 0];
int textureId = [ [ pUrlAndReferences objectAtIndex: 1 ] intValue ];
int textureType = [ [ pUrlAndReferences objectAtIndex: 2 ] intValue ];
NSLog( #"\n\nLoadImage: %#, %d, %d\n", pImageUrl, textureId, textureType );
NSData* pImageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:pImageUrl]];
UIImage* pImage = [[[UIImage alloc] initWithData:pImageData] autorelease];
NSArray* pImageAndReferences = [[[NSArray alloc] initWithObjects: pImage, textureId, textureType, nil] autorelease];
[pImageData release];
[self performSelectorOnMainThread:#selector(ImageLoaded:) withObject:pImageAndReferences waitUntilDone:NO];
}
Anyone have any ideas why LoadImage doesn't get called?
Thanks.
My guess is your not retaining your queue. Here's what's happening
Your array (autoreleased) goes into the NSInvocationOperation and it's being retained (no problem)
Your NSInvocationOperation goes into the queue (retained) and then it's released. No problem here since the retain count is still one: 1 (alloc) + 1 (retain) - 1 (release) = 1 = not dealloced.
Your queue is alloced (new = alloc+init) and then autoreleased, but it's not being retained elsewhere. Here comes the problem: since you have autoreleased the queue, once the method LoadBackgroundImage is finished the queue has a retain count of 0 and it's automatically released, therefore, your Invocation won't be executed.
You can try if that's the problem by just removing the autorelease call from the queue. If I'm correct your code should work. But be aware that that is not a good solution because you're loosing memory. It's just to see if it works.
You should definitely make a class, a singleton, an instance variable or whatever you like to retain that instance of the queue. Also, it's a good idea to only have one queue for all the LoadBackgroundImage calls instead of creating a new queue every time.
Related
I dont know why this NSXMLParser parse method is leaking.
I looked at the other similar SO question, but couldn't resolved it.
Here is my code.
- (void)parseXMLFileAtURL {
self.results = [[NSMutableArray alloc] init];
NSURL *xmlURL = [NSURL URLWithString:#"http://www.dukascopy.com/swiss/video/rss/"];
NSData *dataXml = [[NSData alloc] initWithContentsOfURL:xmlURL];
NSXMLParser *MyrssParser = [[NSXMLParser alloc] initWithData:dataXml];
[dataXml release];
[MyrssParser setDelegate:self];
[MyrssParser setShouldProcessNamespaces:NO];
[MyrssParser setShouldReportNamespacePrefixes:NO];
[MyrssParser setShouldResolveExternalEntities:NO];
[MyrssParser parse]; // memory leak here
MyrssParser.delegate=nil;
[MyrssParser release];
if(!imagesArray)
{
imagesArray = [[NSMutableArray alloc] initWithCapacity:[self.results count]];
for(int i=0;i<[results count];i++)
{
UIImage *image = [UIImage imageNamed:#"nophoto.png"];
[imagesArray addObject:image];
bImgExist[i] = NO;
}
}
}
Even After releasing my NSXMLParser object instrument still shows memory leak.
What I am missing here..
self.results = [[NSMutableArray alloc] init];
Properties take ownership (according to their declarations) of their assigned values. So the array you set this property to is retained by self (I'm assuming the property is either retain or copy here), but already has a retain count of +1 from its initialization.
Change the line to:
self.results = [NSMutableArray array];
And the memory leak should clear up.
I have a bunch of UIButton in a UIScrollView and each UIButton takes an image from a URL. What is the easiest way so that the image will be loaded asynchronously?
For example, the usual way is:
[button setImage:[UIImage imageWithData:data] forState:UIControlStateNormal];
but this blocks the UI, I don't want it to
You can try this
[self performSelectorInBackground:#selector(loadImag) withObject:nil];
In loadImage function load the image from url and then assign it to the button.
I am not sure this will work for you...As I am a beginner in objective C development
Try with an NSInvocationOperation, make an synchronous request for each image button... Pass the button as parameter, something like this i mean...
Init the operation queue (maybe on init):
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
start the operation invocation for each button...
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(getImageFromURL:)
object:[NSDictionary dictionaryWithObjectsAndKeys:#"http://getMyImage.com/resource.jpg", #"url", button, #"button", nil]];
[queue addOperation:operation];
[operation release];
this can be your getImageFromURL: selector
- (void) getImageFromURL:(NSDictionary*)dict
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [NSURL URLFromString:[dict objectForKey:#"url"]];
UIButton *button = [dict objectForKey:#"button"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error];
UIImage *image = [[UIImage alloc] initWithData:data];
// Finally set the button image and release image ...
[pool drain];
}
Don't forget release queue on dealloc...
Hope this helps! :)
I guess it is alright, if you block while loading one single image? The problem is that you have many of them? If so, then I would do like this (no threads needed):
#define DELAY 0.1 // you may set it to 0 as well
...
[self performSelector:#selector(setupButton:)
withObject:[NSNumber numberWithInt:0]
afterDelay:DELAY];
...
-(void)setupButton:(NSNumber*)count
{
UIButton *button = [self buttonFromMyScrollViewWithCount:count.intValue];
[button setImage:...];
if (count.intValue < self.numberOfButtonsInMyScrollView)
[self performSelector:#selector(setupButton:)
withObject:[NSNumber numberWithInt:count.intValue + 1]
afterDelay:DELAY];
}
I have some images in the array in the form of NSDATA.and i am showing them on page in page controller example 6 at a time.
But they are taking time to get convert in to UIImage that's why scrolling get slow dowwn is we have any other option?
i am using following code to convert them into NSDATA.
[UImage imageWithData:data];
From http://www.switchonthecode.com/tutorials/loading-images-asynchronously-on-iphone-using-nsinvocationoperation :
In your main thread:
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:#selector(loadImage)
object:nil];
[queue addOperation:operation];
[operation release];
Other methods required:
- (void)loadImage {
NSData* imageData = //however you're getting your NSData
UIImage* image = [[[UIImage alloc] initWithData:imageData] autorelease];
[imageData release];
[self performSelectorOnMainThread:#selector(displayImage:) withObject:image waitUntilDone:NO];
}
- (void)displayImage:(UIImage *)image {
[imageView setImage:image]; //UIImageView
}
I have a few apps which are largely data driven, so most screens are basically composed of:
Open the screen
Download the data via an NSOperation
Display data in a UITableView
Make a selection from the UITableView
Go to new screen, and start over from step 1
I am finding that everything works in normal usage, but if the user goes away from the app for a while and then comes back, I'm getting an EXC_BAD_ACCESS error when the next NSOperation runs. This doesn't seem to matter if the user sends the app into the background or not, and it only seems to occur if there's been at least a few mins since the previous data connection was made.
I realise this must be some form of over-releasing, but I'm pretty good with my memory management and I can't see anything wrong. My data calls generally look like this:
-(void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue* tmpQueue = [[NSOperationQueue alloc] init];
self.queue = tmpQueue;
[tmpQueue release];
}
-(void)loadHistory {
GetHistoryOperation* operation = [[GetHistoryOperation alloc] init];
[operation addObserver:self forKeyPath:#"isFinished" options:0 context:NULL];
[self.queue addOperation:operation];
[operation release];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqual:#"isFinished"] && [object isKindOfClass:[GetHistoryOperation class]]) {
GetHistoryOperation* operation = (GetHistoryOperation*)object;
if(operation.success) {
[self performSelectorOnMainThread:#selector(loadHistorySuceeded:) withObject:operation waitUntilDone:YES];
} else {
[self performSelectorOnMainThread:#selector(loadHistoryFailed:) withObject:operation waitUntilDone:YES];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
-(void)loadHistorySuceeded:(GetHistoryOperation*)operation {
if([operation.historyItems count] > 0) {
//display data here
} else {
//display no data alert
}
}
-(void)loadHistoryFailed:(GetHistoryOperation*)operation {
//show failure alert
}
And my operations generally looks something like this:
-(void)main {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSError* error = nil;
NSString* postData = [self postData];
NSDictionary *dictionary = [RequestHelper performPostRequest:kGetUserWalkHistoryUrl:postData:&error];
if(dictionary) {
NSNumber* isValid = [dictionary objectForKey:#"IsValid"];
if([isValid boolValue]) {
NSMutableArray* tmpDays = [[NSMutableArray alloc] init];
NSMutableDictionary* tmpWalksDictionary = [[NSMutableDictionary alloc] init];
NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:#"yyyyMMdd"];
NSArray* walksArray = [dictionary objectForKey:#"WalkHistories"];
for(NSDictionary* walkDictionary in walksArray) {
Walk* walk = [[Walk alloc] init];
walk.name = [walkDictionary objectForKey:#"WalkName"];
NSNumber* seconds = [walkDictionary objectForKey:#"TimeTaken"];
walk.seconds = [seconds longLongValue];
NSString* dateStart = [walkDictionary objectForKey:#"DateStart"];
NSString* dateEnd = [walkDictionary objectForKey:#"DateEnd"];
walk.startDate = [JSONHelper convertJSONDate:dateStart];
walk.endDate = [JSONHelper convertJSONDate:dateEnd];
NSString* dayKey = [dateFormatter stringFromDate:walk.startDate];
NSMutableArray* dayWalks = [tmpWalksDictionary objectForKey:dayKey];
if(!dayWalks) {
[tmpDays addObject:dayKey];
NSMutableArray* dayArray = [[NSMutableArray alloc] init];
[tmpWalksDictionary setObject:dayArray forKey:dayKey];
[dayArray release];
dayWalks = [tmpWalksDictionary objectForKey:dayKey];
}
[dayWalks addObject:walk];
[walk release];
}
for(NSString* dayKey in tmpDays) {
NSMutableArray* dayArray = [tmpWalksDictionary objectForKey:dayKey];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"startDate" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray* sortedDayArray = [dayArray sortedArrayUsingDescriptors:sortDescriptors];
[sortDescriptor release];
[tmpWalksDictionary setObject:sortedDayArray forKey:dayKey];
}
NSSortDescriptor* sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:nil ascending:NO selector:#selector(localizedCompare:)];
self.days = [tmpDays sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
self.walks = [NSDictionary dictionaryWithDictionary:tmpWalksDictionary];
[tmpDays release];
[tmpWalksDictionary release];
[dateFormatter release];
self.success = YES;
} else {
self.success = NO;
self.errorString = [dictionary objectForKey:#"Error"];
}
if([dictionary objectForKey:#"Key"]) {
self.key = [dictionary objectForKey:#"Key"];
}
} else {
self.errorString = [error localizedDescription];
if(!self.errorString) {
self.errorString = #"Unknown Error";
}
self.success = NO;
}
[pool release];
}
-(NSString*)postData {
NSMutableString* postData = [[[NSMutableString alloc] init] autorelease];
[postData appendFormat:#"%#=%#", #"LoginKey", self.key];
return [NSString stringWithString:postData];
}
----
#implementation RequestHelper
+(NSDictionary*)performPostRequest:(NSString*)urlString:(NSString*)postData:(NSError**)error {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL* url = [NSURL URLWithString:[NSString stringWithFormat:#"%#/%#", kHostName, urlString]];
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
[urlRequest setHTTPMethod:#"POST"];
if(postData && ![postData isEqualToString:#""]) {
NSString *postLength = [NSString stringWithFormat:#"%d", [postData length]];
[urlRequest setHTTPBody:[postData dataUsingEncoding:NSASCIIStringEncoding]];
[urlRequest setValue:postLength forHTTPHeaderField:#"Content-Length"];
[urlRequest setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
}
NSURLResponse *response = nil;
error = nil;
NSData *jsonData = [NSURLConnection sendSynchronousRequest:(NSURLRequest *)urlRequest returningResponse:(NSURLResponse **)&response error:(NSError **)&error];
NSString *jsonString = [[NSString alloc] initWithBytes: [jsonData bytes] length:[jsonData length] encoding:NSUTF8StringEncoding];
NSLog(#"JSON: %#",jsonString);
//parse JSON
NSDictionary *dictionary = nil;
if([jsonData length] > 0) {
dictionary = [[CJSONDeserializer deserializer] deserializeAsDictionary:jsonData error:error];
}
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
return dictionary;
}
If I have the autorelease pool in place, the crash occurs on [pool release]. If I don't, then the crash just looks to appear in the main.m method, and I don't seem to get any useful information. It's difficult to track down when I have to wait 10 mins in between every test!
If anyone can offer any clues or directions to go, that'd be much appreciated.
It's almost certain you're overreleasing something in your code, seeing that the crash is occurring during a [pool release] (There's a autorelease pool in the main method as well).
You can find it using Xcode - use build and analyze to have the static analyser pinpoint potential problems. Run it and post the results.
try this:
http://cocoadev.com/index.pl?NSZombieEnabled
also, you should avoid:
1) calling UIKit methods from secondary threads
2) making (synchronous) url requests from the main thread.
you must be doing one in any case in RequestHelper's performPostRequest method.
My guess is this section
GetHistoryOperation* operation = (GetHistoryOperation*)object;
if(operation.success) {
[self performSelectorOnMainThread:#selector(loadHistorySuceeded:) withObject:operation waitUntilDone:YES];
} else {
[self performSelectorOnMainThread:#selector(loadHistoryFailed:) withObject:operation waitUntilDone:YES];
}
If the sleep happens at a bad point here, you have an object being passed to another thread. I'd find a way around having to pass the operation as the object.
This is a really old question, so sorry for the dredge, but there is no accepted answer.
I was also getting a EXC_BAD_ACCESS on NSOperationQueue -addOperation for seemingly no reason, and after a few days of hunting down memory leaks, and turning on all the debugger options i could find (malloc guard, zombies) and getting nothing, I found an NSLog warning that said: "[NSoperation subclass] set to IsFinished before being started by the queue."
When I modified my base operation subclass, so that its -cancel function only set (IsRunning = NO) and (IsFinished = YES) IF AND ONLY IF (IsRunning == YES), NSOperationQueue stopped crashing.
So if you're ever calling NSOperationQueue -cancelAllOperations, or you're doing that manually (i.e. for (NSOperation *op in queue.allOperations) ) double check to make sure that you don't set IsFinished on those operations in your subclass implementation.
Updates code
- (void) searchBarSearchButtonClicked:(UISearchBar *)theSearchBar {
mytimer3=[NSTimer scheduledTimerWithTimeInterval:.03 target:self selector:#selector(show) userInfo:nil repeats:NO];
NSLog(#" search is down");
//////////// rnd to hold keyboard
//ovController.view.backgroundColor = [UIColor grayColor];
self.tableView.scrollEnabled = NO;
UserText=[self.searchDisplayController.searchBar text];
myInt= UserText.length;
//int myInt= [save length];
// NSString *myStringPrt1 = [[NSString alloc] init];
// NSString *myStringPrt2 = [[NSString alloc] init];
if(UserText.length>3)
{
//[ovController.view removeFromSuperview];
//[tableView reloadData];
url=nil;
// myStringPrt1=#"http://find.php?keyword=";
NSString * myStringPrt2=UserText;
// myStringPrt1=[myStringPrt1 stringByAppendingString:myStringPrt2];
// myStringPrt1 = [myStringPrt1 stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
// loadingView.hidden=NO;
NSString *outputString = [[NSString stringWithString:#"http://find.php?keyword="] stringByAppendingString: UserText];
outputString = [outputString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(#"My string is now = %#", outputString);
url= [NSURL URLWithString:outputString];
NSXMLParser *xmlParser = [[[NSXMLParser alloc] initWithContentsOfURL:url] autorelease];
//Initialize the delegate.
XMLParser *parser = [[[XMLParser alloc] initXMLParser]autorelease];
//Set delegate
[xmlParser setDelegate:parser];
//Start parsing the XML file.
BOOL success = [xmlParser parse];
if(success)
{
[super viewDidLoad];
[self searchTableView];
mytimer4=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(wipe) userInfo:nil repeats:NO];
}
}
}
Right, you have memory leaks all over the place:
NSString *myStringPrt1 = [[NSString alloc] init];
NSString *myStringPrt2 = [[NSString alloc] init];
url=nil;
myStringPrt1=#"http://wap?keyword="; //MEMORY LEAK, this will assign a new string to myStringPrt1, without releasing the first one
myStringPrt2=UserText; //MEMORY LEAK, this will assign a new string to myStringPrt2, without releasing the first one
myStringPrt1=[myStringPrt1 stringByAppendingString:myStringPrt2]; //MEMORY LEAK, this will assing an autoreleased string the myStringPrt1, without releasing the old one first.
myStringPrt1 = [myStringPrt1 stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //MEMORY LEAK, this will assing an autoreleased string the myStringPrt1, without releasing the old one first.
I suggest you first read the memory management guidelines as indicated by Kubi. Or, if you are feeling lazy, use this:
NSString *outputString = [[NSString stringWithString:#"http://wap?keyword="] stringByAppendingString: UserText];
outputString = [outputString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
Or this:
NSString *outputString = [NSString stringWithFormat:#"http://wap?keyword=%#",UserText];
outputString = [outputString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
Don't pay any attention to your retain count. That number will fluctuate for reasons entirely out of your control and trying to determine why the number is what it is will drive you crazy (and not do you any good).
Follow proper Obj-C memory management guidelines, use the leaks detector in Instruments, and run the static analyzer when you build. If you do all that, you'll be fine.
No depending on your requirement and object allocation, your retain count will definitely increase. But the main thing you need to keep in mind that release all the allocated object at proper place and proper time.