UIWebView memory issues - iphone

I am playing youtube videos on a UIWebView which appears as a modalViewController subview (flip transition). Everything works fine, even though the UIWebView is released, I still receive memory warnings after a few repeated selection of this modalViewController.
I have added my UIWebView programmatically inside ViewDidLoad. Inside viewDidDisappear I check for [UIWebView retainCount] and if greater than 1, perform the following steps:
[[NSURLCache sharedURLCache] removeAllCachedResponses];
[self.webView removeFromSuperview];
self.webView.delegate = nil;
self.webView = nil;
NSLog(#"[self.webView retainCount] %d", [self.webView retainCount]);
I am running my code on xCode 3.2.5, iOS 4.2.
Appreciate all you help.

I think you are approaching the memory management problem in the wrong way. Checking the retainCount is a valid debugging technique if you know what you are doing. It is not, however, a memory management tool. In your particular case, if the UIWebView is being displayed it will always have retain count > 1. The superview will have a retain on it thus making the "if" useless.
If the webView property is well defined (i.e. noatomic, retain) the statement:
self.webView = nil;
should release the webView. A common mistake is to initialize the property with:
self.webView = [[UIWebView alloc] init];
This is likely to introduce a leak if the webView is defined as "retain". The correct way is
self.webView = [[[UIWebView alloc] init] autorelease];
If you can't display your controller several times without running out of memory you have a memory leak. Use Instruments (Leaks in particular) to find hte objects what are note being released properly. This is a good tutorial.
Be careful in keeping your retains and releases balanced and check for leaks.

Your problem will be related to this:
Is it possible to prevent an NSURLRequest from caching data or remove cached data following a request?
Scroll down to my answer for an extension of the accepted answer - i had this problem for days and it's now resolved!

Related

UIWebView EXC_BAD Access

Hy
I've got a UIView. In one method, I alloc a WebView, and set it's to a retain property.
self->webView= [[UIWebView alloc] initWithFrame:self.frame];
Then I start to load a HTML string. After it's loaded, I resize the view and start a callBack to the superview to resize. It's working.
My problem is that, if the user go back before the view has been loaded, the view's are released. Then my WebView throw a BAD_ACCESS.
Here is the dealloc method:
-(void)dealloc{
[self.webView setDelegate:nil];
[self.webView stopLoading];
[self setWebView:nil];
[htmlStr release];
[super dealloc];}
The callback trace is shown in the screenshot. The interesting is that, if I don't release the WebView it is work's like the charm. If I release the WebView, then when it's deallocated, I get an error message in the log:
![bool _WebTryThreadLock(bool), 0x4e05150: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...][1]
EDIT: It has been fixed. It turns out, that my image loading method was guilty. I've start a new thread (NSOperationQueue and NSInvocationOperation in that) to load an image, and make a performSelectorOnMainThred: when it's finished. Whit that, I've loaded more than hundred small (1-2 KB) image, on every page switch, which was a really big overhead. When I wrote a method which download the images in one thread one by one, this bug has never came again
You are modifying UI from other tread than main. This is forbidden as UIKit is not thread-safe (hence the crash...). If you want to modify the UI from another thread, you must use:
performSelectorOnMainThread:withObject:waitUntilDone:
Other thing I've notice in your code is that you incorrectly release your properties in -dealloc. You should not use synthesized setters like this:
[self setWebView:nil]; // same as self.webView = nil;
You should not, because it can bring you lots of problems if you start using KVO (Key-Value Observing) on you properties... Instead just write:
[webView release];
or, if you want to avoid "The Heisenbug":
[webView release], webView = nil;
EDIT: you can also benefit from answer to this SO question: How to safely shut down a loading UIWebView in viewWillDisappear?
Dont call it with self.webView in dealloc, just use webView:
- (void)dealloc {
if ([webView isLoading])
[webView stopLoading];
[webView setDelegate:nil];
[webView release], webView = nil;
[super dealloc];
}
It has been fixed. It turns out, that my image loading method was guilty. I've start a new thread (NSOperationQueue and NSInvocationOperation in that) to load an image, and make a performSelectorOnMainThred: when it's finished.
Whit that, I've loaded more than hundred small (1-2 KB) image, on every page switch, which was a really big overhead. When I wrote a method which download the images in one thread one by one, this bug has never came again.

UIImageView got weird retain count and i need to release it twice to avoid leaks

I got a UIImageView that i set in the nib file. I download an image from internet and sets the image to the UIImageView. When i'm releasing it has retain count 2? If i'm using only 1 release it won't show any memory leak but i can see in "Instrument Allocations" that it never gets released. When i release the UIImageView twice like below then it works good. But i should never release it twice?!?!
in Header:
IBOutlet UIImageView *background;
in the .m loading the image:
/* Load Image code */
id path = [NSString stringWithFormat:#"http://www.image.com/aImage.jpg"];
NSURL *url = [NSURL URLWithString:path];
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSData* urlData = [[NSData alloc] initWithContentsOfURL:url];
[background setImage:[UIImage imageWithData:urlData]];
[urlData release];
[pool release];
in dealloc function:
- (void)dealloc {
NSLog(#"Backgroud count: %i",[background retainCount]); // Prints 2
[background release];
[background release]; // Does not "leak" if i have 2x release
[super dealloc];
}
This is the only code that is useing the UIImageView Background.
EDIT:
something i forgot to mention is that i run this code inside a for loop like this. but this for loop will only execute once! But it shouldn't matter?
for (id theKey in dictionary) {
/* Load Image code above is here */
}
I believe that I've figured out what the trouble is. Apple recommends that you retain the objects you connect to through IBOutlets (your image view, in this case). You said that you haven't done so, but you should be following Apple's recommendation. The reason that you should is outlined in a iphonedevsdk.com forum post about this problem, which links to a Big Nerd Ranch blog post that lays it all out.
On iOS, the nib loading mechanism uses the setter if your outlet has one, but it uses key-value coding if not; specifically, it uses setValue:forKey:, which retains the value (this is documented, but somewhat unexpected). Your image view, being the subview of your view controller's top view, is retained by that view. It's also retained by this key-value setting procedure. So, unbeknownst to you, your objects have two references to the image view. Apple makes the retaining property suggestion so that it becomes knownst to you that the view is being retained.
You still shouldn't be worrying about the retain count as such, but you should do one of two things: make this IBOutlet a retained property and release it in both viewDidUnload and dealloc (just once each, though!), or follow BNR's suggestion and make the property explicitly assigned:
#property (assign, nonatomic) IBOutlet UIImageView *background;
in which case you do not have to release it yourself. In both cases, make sure you #synthesize the property accessors.
Previously:
Don't look at retain count, and if there's no leak being detected, then don't worry about it. The UIKit framework is likely retaining the view for reasons that you aren't privy to.
Additionally, if background isn't a retained property:
#property (retain) IBOutlet UIImageView *background;
and you're creating it in the xib, you shouldn't be releasing it at all, because you don't own it. That is, you aren't responsible for its memory; the actions that give you that responsibility are: calling retain on the object, or creating it using a method whose name begins with alloc, copy, mutableCopy, or new.
I don't know much about nib file, i used to follow like below
background=[[UIImageView alloc] initWithFrame:CGRectMake(0,0,320,480)];
//Now the reatin count is 1
[background setImage:[UIImage imageWithData:urlData]];
[someView addSubview:background];
//Now the ratainCount will be 2 since we added the imageview to the superview
[background release];
//i will release immediately so the retain count drops to 1
. . .
//in dealloc or viewDidDisaaper i will remove the imageview from its superview
//then the retainCount will become 0 and the instance will be deallocated.
//it works for we without any memory leakage
[background removeFromSuperview];

UIWebView acts differnetly in app store version than dev version

I've got a problem with an app that works perfectly in the simulator as well as a physical iPhone 4 and an iPhone 3GS. The app was approved and is now in the App Store, but the distribution build downloaded from the App Store exhibits a bug not seen in the dev/release build.
This is a free app, but is supported by local advertising. When the app launches (or returns from background), the AppDelegate attempts to download some HTML from our ad server, and if successful, presents a modal view controller with a UIWebView and passes an NSData variable containing the HTML. In development/release builds, this works PERFECTLY; the app launches, and after a few seconds, a view slides up and shows the ad, which can be dismissed with a button.
However distribution build from the App Store is different. When the modal view controller slides up, the UIWebView never loads. Remember, I present the view controller ONLY if able to download the ad data -- otherwise, the view is never presented.
Thankfully I implemented a timer in the ad view controller which will cause the modal view to dismiss itself if the webViewDidFinishLoad never fires (in which the timer is invalidated), so at least app users aren't too annoyed. But it's still ugly to have an empty view controller slide up and then slide away for apparently no reason.
Here's the relevant methods in the AppDelegate:
- (void)launchAd
{
[NetworkActivity showFor:#"ad"];
if (!alreadyActive && [ServerCheck serverReachable:#"openx.freewave-wifi.com" hideAlert:YES])
{
alreadyActive = YES;
[self performSelectorInBackground:#selector(downloadAdData) withObject:nil];
}
[NetworkActivity hideFor:#"ad"];
}
- (void)downloadAdData
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *baseURL = #"http://appdata.freewave-wifi.com/ad/";
NSString *file = (IS_IPAD) ? #"ipad.php" : #"iphone.php";
NSURL *adURL = [NSURL URLWithString:[baseURL stringByAppendingString:file]];
adData = [[NSData alloc] initWithContentsOfURL:adURL];
[self performSelectorOnMainThread:#selector(presentAdModal) withObject:nil waitUntilDone:NO];
[pool release];
}
- (void)presentAdModal
{
if (adData)
{
AdViewController *adView = [[AdViewController alloc] initWithNibName:nil bundle:nil];
[adView setAdData:adData];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:adView];
[navController setModalPresentationStyle:UIModalPresentationFormSheet];
[navController setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[tabBarController presentModalViewController:navController animated:YES];
[navController release], navController = nil;
[adView release], adView = nil;
}
else
LogError(#"Not presenting ad; unable to create data object.");
}
By the way, adData is defined in header with NSData *adData;
The AdViewController simply contains a UIWebView, which is loaded with
[webView loadData:adData MIMEType:#"text/html" textEncodingName:#"utf-8" baseURL:nil];
Again, this all works PERFECTLY, EVERY TIME with dev/release builds in simulator and physical devices -- just not on distribution build from app store. I have even converted the NSData to an NSString and barfed it out with NSLog() just to prove that the HTML was downloaded before presenting the AdView modally.
[sigh...]
EDIT 1: In case my original post was not clear, the webViewDidFinishLoad never gets called in distribution build (but it does in dev/release build).
EDIT 2: Also, just before I call
[webView loadData:adData MIMEType:#"text/html" textEncodingName:#"utf-8" baseURL:nil];
in the AdViewController, I added a temporary NSLog() and converted adData to NSString and logged it to the console, and the HTML was there. So, the UIWebView just refuses to load the NSData?
HOLY COW. I figure it out.
Okay, before I say what I found, I did want to correct my own original wording: the modal ad has never worked in the simulator, but always on devices. I know the simulator can have its quirks, so I never thought anything of it, especially since it always worked on the devices. I know this is an important detail that was missing for this discussion, but it's been a couple of weeks since I worked on this project, and I'd forgotten all about it until today.
Now then... While tinkering with things, I noticed the AdView.xib was not in my project file list. I expanded a few folders thinking maybe it was accidentally dragged into one of them, but it was not listed at all. This really has me puzzled, though -- Xcode NEVER complained about a missing resource (no warnings or errors; always a perfect compile).
So, I navigated to the physical location and added the AdView.xib into the project. Now, the modal ad is displayed in the simulator, which is a first. I figure that since now the app works correctly in the simulator, it should work fine in the distribution build (odd correlation to make, but it's all I got until my update hits the App Store).
Obviously, I'll be submitting an update, so I won't accept my own answer until after the update hits the App Store (assuming I have actually fixed it).
Ok, this is an extremely long shot, but perhaps worth considering.
The docs for NSData state that with regards to initWithContentsOfURL "The returned object might be different than the original receiver." So, if it was a different object, and one which was in fact autoreleased, consider this line in your code:
adData = [[NSData alloc] initWithContentsOfURL:adURL];
This won't add a retain count for adData -- you didn't write self.adData = or similar. So, bearing in mind the scenario mentioned whereby the returned NSData was autoreleased: your method downloadAdData wraps its content in an NSAutoreleasePool. This is correct practice. However, that might result in adData being released BEFORE presentAdModal is called on the main thread. So...
In presentAdModal you just check that adData isn't nil -- but it can be not nil, and still have been deallocated from memory at that point by your NSAutoreleasePool -- hence, you would in this situation trigger the "show web view" code, but be attempting to load an NSData object that had been trashed. Which probably would contain complete garbage, hence no successful "web view loaded" call.
As I said, a long shot, but the ony thing that jumps out at me at this point.
UPDATE:
A completely different cause of your problem might be this:
Your test environment (i.e. non App-Store builds) is making requests from a certain part of the internet (i.e. your office) which has permission to access the web server containing ads, due to either IP blocking or whatever network setup there is, whereas your App Store release builds are attempting to access the ad server from parts of the internet which are forbidden. Again, probably not the case, but worth mentioning.

uiimagepickerview controller creating memory leaks in iphone - why?

uiimagepickerview controller creating memory leaks in iphone - why?
Try to implement ui image picker view controller in your application & debug it.
You will find memory leaks in your application.
Why ui image picker view controller creates memory leaks.
-(void)addPhotos:(id)sender
{
if(imagePickerController==nil){
imagePickerController=[[UIImagePickerController alloc]init];
imagePickerController.delegate=self;
imagePickerController.sourceType=UIImagePickerControllerSourceTypeSavedPhotosAlbum;
imagePickerController.allowsImageEditing=YES;
imagePickerController.navigationBar.barStyle=UIBarStyleBlackOpaque;
}
[self.navigationController presentModalViewController:imagePickerController animated:YES];
}
dealloc of my view controller.
- (void)dealloc {
if(PhotoDateArray!=nil)[PhotoDateArray release];
if(imagePickerController!=nil) [imagePickerController release];
if(objDetail!=nil) [objDetail release];
if(Picimage!=nil) [Picimage release];
if(mySavePhotoController!=nil) [mySavePhotoController release];
if(LoadingAlert!=nil);
[super dealloc];
}
Video link explaining how I am getting the memory leak in it..
http://www.yourfilelink.com/get.php?fid=508534
Even though you have the nil check, it's still possible to leak memory. I think what is happening here is that you are calling alloc / init multiple times, but only releasing once. My guess it that addPhoto: is wired up to some button click, dealloc would only be called once when the delegate is trying to destroy. This creates a situation like this:
button click
alloc / init
button click
alloc / init (memory leak on first alloc'd picker)
close window
dealloc (free second alloc'd picker)
A better way might be the way Apple does it in the PhotoLocations and iPhoneCoreDataRecipes examples:
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.delegate = self;
[self presentModalViewController:imagePicker animated:YES];
[imagePicker release];
Then listen for the didFinishPickingImage and imagePickerControllerDidCancel messages to your delegate and a call to [self dismissModalViewControllerAnimated:YES]; in both places should suffice.
I dont know about the rest of the code, but do you ever have a release?
[imagePickerController release]
UIImagePickerController loads and initializes PhotoLibrary.framework the first time it is shown. This memory won't be reclaimed until your application is closed.
(the code you posted doesn't appear to have leaks as-is, but that doesn't mean it won't interact with the rest of your application in a way that causes them)
I can explain this because I was having the same problem.
Don't test memory on the simulator!
If you test the apple code on a device the memory problem disappears.
I was having a memory alloc leak which I found in Instruments. All I was doing was opening and closing the image picker (open/cancel) and using Apple code, my code and other people's code, just like yours above.
All were showing the allocation going up and up each time, as if the picker was not being released. If you tried to release it, it would crash (over released).
Then I found a really helpful web page which basically stated:
"This doesn't happen when testing on the device"
So I switched from the simulator and ran the tests on the device. Lo & behold there was no allocation increase and it behaved normally.
This however is totally evil and now we can place no trust in the simulator to do a reliable job.
I want to add this to save people, the time, pain and bewilderment of wondering wtf is going on!

Is this causing EXC_BAD_ACCESS?

I'm getting a EXC_BAD _ACCESS after leaving the method below. At that point htmlDocument becomes invalid, which it should since it falls out of scope. But is that why I'm getting the error? By the time the contentView (UIWebView) loads, htmlDocument is gone. But doesn't contentView already have what it needs from loadHTMLString?
- (void)viewDidLoad {
[super viewDidLoad];
//something other processing here
NSString *htmlDocument = [NSString stringWithFormat:#"<html> \n"
"<body>%#</body> \n"
"</html>", aboutContent];
[contentView loadHTMLString:htmlDocument baseURL:[NSURL URLWithString:#"http://www.abc.com"]];
}
Is there a better way to follow this all the way to failure? Leaving this method is the end of the line for my code. SDK stuff from there.
From your second post, the line you have commented out ([button release]) releases an object already marked to be released automatically.
Either remove the "autorelease" where you are doing an alloc for the button or remove the [button release] statement for the code to compile without errors and exceptions.
If an object is marked for autorelease, calling a release on it will be the same as calling a release on a deallocated instance, hence the error.
At a glance I would say there is nothing wrong with this code, assuming 'aboutContent' is a valid pointer to an object.
You can try running your app with Guard Malloc on the simulator, but that's not guaranteed to turn up anything. I'd suggest you just start commenting out statements until you find the one that's causing the error.
It's not clear what's going on with the code snippet you've just provided without more context. That said, it looks like all you want to do is load some HTML locally in the device. Why not just do this?
- (void)viewDidLoad {
[webView loadRequest:[NSURLRequest
requestWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:#"help/about.html"
ofType:#"html"]isDirectory:NO]]];
}
Using this technique, you can author HTML-based documents right in your phone without the user being wise to the fact that they are actually looking at a web view: this includes Javascript, CSS, the whole ball of a wax. I've seen people actually have their iPhone app go out to the Internet just to render a static page on the Internet which really is not necessary.
From your sample code in the second post, the button you release UIBarButtonItem *button is not retained by anything following the release you have commented out - and so it is deallocated.
You need to add the button to another view (using adSubview) in order for it to display and then you can release it. The parent view will retain the button.
Of course if you are going to refer to the button again, your view controller should keep the retain and release the button in its dealloc.