WebView being released too many times - objective-c

I'm trying to load a webpage in a WebView in order to take a snapshot of the website. The WebView is contained in a temporary window that I create for this purpose. However, shortly after I release the WebView and the temporary window, the WebView is sent another release message, while it has already been deallocated. This is the error message in the debugger with NSZombieEnabled set to YES.
*** -[WebView release]: message sent to deallocated instance 0x608000125820
I can't figure out what is causing the WebView to be released too many times. The thing that makes it extra confusing is that the problem only occurs while loading certain URL's. For example: when trying to take a snapshot of http://www.google.com everything is fine, but when using http://edition.cnn.com it almost always crashes.
This is what (a simplified version of) the code looks like:
#interface AppDelegate ()
#property (nonatomic, strong) NSWindow *tempWindow;
#property (nonatomic, strong) WebView *webView;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
CGRect frame = CGRectMake(0, 0, 1200, 695);
self.tempWindow = [[NSWindow alloc] initWithContentRect:frame styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
self.webView = [[WebView alloc] initWithFrame:frame];
self.webView.frameLoadDelegate = self;
self.tempWindow.contentView = self.webView;
[[self.webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://edition.cnn.com"]]];
}
#pragma mark - WebFrameLoadDelegate
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
if (frame != [sender mainFrame]) {
return;
}
// take snapshot here...
[self takeSnapshot];
// get rid of web view and temp window
[self.webView stopLoading:nil];
[self.webView setFrameLoadDelegate:nil];
self.webView = nil;
self.tempWindow = nil;
}

When using ARC there seems to be problem with retaining/releasing the WebView in some situations. From my testings I found out that loading an empty NSStringin the mainFrame of the WebView before releasing it should solve the problem.
See also a short blog entry on this topic.

Related

Crash when closing window in a MacOS application?

I am attempting to create a functional MacOS application but with absolutely no xib or storyboard file just to see how it is done.
In the AppDelegate.m I create and show a window and set the application to terminate after last window closed:
#import "AppDelegate.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)Notification {
NSWindow *const Window = [[NSWindow alloc] initWithContentRect:(NSRect){.size = {800, 512}} styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:YES];
[Window center];
[Window makeKeyAndOrderFront:Window];
// Insert code here to initialize your application
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)Sender {
return YES;
}
#end
AppDelegate.h:
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject<NSApplicationDelegate>
#end
In the Main.m file is the following:
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
int main(void) {
#autoreleasepool {
[NSApplication sharedApplication].delegate = (AppDelegate *){[[AppDelegate alloc] init]}; // I also tried using setDelegate to no avail
[NSApp run];
}
return 0;
}
A window is created, but the issue is that when I close the window, the app crashes, showing an Thread 1: EXC_BAD_ACCESS (code=1, address=0x20) at the [NSApp run] line in Main.m. Somehow the application does not terminate properly and crashes instead. Clearly I am missing something but the question is what?
Edit: I noticed an odd occurance which is that the crash only occurs when ARC (Automatic Reference Counting) is enabled.
The problem is the window is automatically released (and thus deallocated) upon closure. This, combined with the automatic reference counting, presumably creates a sort of double free error. To solve this problem without disabling ARC or disabling releaseWhenClosed, Window is made a global or instance variable. Doing so will prevent ARC from releasing the window after already having been released by being closed.
NSWindow *Window;
// ...
- (void)applicationDidFinishLaunching:(NSNotification *)Notification {
Window = [[NSWindow alloc] initWithContentRect:(NSRect){.size = {800, 512}} styleMask:NSWindowStyleMaskTitled|NSWindowStyleMaskClosable|NSWindowStyleMaskMiniaturizable|NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:YES];
[Window center];
[Window makeKeyAndOrderFront:Window];
// Insert code here to initialize your application
}

What common ways are there to release persistent memory usage not released by ARC?

As I move from one viewcontroller to another (containing images, a scrollview, labels and other stuf) my persistent is not released.
In instruments it currently looks like this:
Facts:
I'm using Objective-C, ARC and Auto Layout
My "- (void)dealloc{ }" is being called but there is nothing in it
NSZombie is not enabled
I have tried:
Replacing all occurrences of self with weakSelf
typeof(self) __weak weakSelf = self;
I use imageWithContentsOfFile not imageNamed
[UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"thumbUp" ofType:#"png"]]
not
[UIImage imageNamed:#"thumbUp.png"]
In "-(void)viewDidDisappear:(BOOL)animated" I have:
Invalidate all timers
[_searchAnimationTimer invalidate];
Setting all stuff to nil (every object and variable)
_searchAnimationTimer = nil;
Removing observers
[[NSNotificationCenter defaultCenter] removeObserver:weakSelf]
Setting all delegates to nil
[self.tableView setDelegate:nil];
Setting all data sources to nil
[self.tableView setDataSource:nil];
I've added [super viewDidDisappear:(BOOL)animated]; in the end of "-(void)viewDidDisappear:(BOOL)animated"
-(void)viewDidDisappear:(BOOL)animated{
//Code here
[super viewDidDisappear:(BOOL)animated];
}
In "- (void)viewDidLoad" I:
Dismiss ViewControllers using both
[weakSelf.presentingViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
and
_mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
_vc = [_mainStoryboard instantiateViewControllerWithIdentifier:#"Omraden"];
[_vc dismissViewControllerAnimated:NO completion:^{ }];
In .h I have:
I have created an IBOutlet for all objects in storyboard.
And set all #property to weak
#property (weak, nonatomic) IBOutlet UIView *contentView;
Whats my next step? What common changes can I do? I'm new at memory management. Need more info? I'll answer fast :)
List of solutions so far:
In "-(void)viewDidDisappear:(BOOL)animated", also set the image of every UIimageView to nil like this:
_someImageView.image = nil;
I also added this:
for (CALayer* layer in [weakSelf.view.layer sublayers])
{
[layer removeAllAnimations];
}
Started using #autoreleasepool inside for-statements (not sure where else to use it)
#autoreleasepool{
//code
}

Resizing a WebView instance

I'm just starting to learn OSX Cocoa app development. I would like to display a website inside a native OSX window. I thought a WebView would be the right way. I would like the webview to always take up 100% of the containing windows' size.
After struggling a bit, I understand how to catch the 'window resize' event, but I have no clue how to resize the web view according to the windows new size.
Here's what I have so far:
AppDelegate.h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#interface AppDelegate : NSObject <NSApplicationDelegate, NSWindowDelegate>
#property (assign) IBOutlet NSWindow *window;
#property (weak) IBOutlet WebView *websiteWebview;
#end
AppDelegate.m
#import "AppDelegate.h"
#implementation AppDelegate
- (NSSize) windowWillResize:(NSWindow *)sender toSize:(NSSize)frameSize
{
WebView *view = [self websiteWebview];
[view setFrame:CGRectMake(0, 0, 1000, 1000)];
return frameSize;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[[self window] setDelegate:self];
NSURL *url = [[NSURL alloc] initWithString:#"http://conradk.com"];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
[[[self websiteWebview] mainFrame] loadRequest:request];
}
#end
I thought calling [view setFrame:CGRectMake(0, 0, 1000, 1000)] would resize the web view as well, but it seems to not be the case.
Any tips / hints please? Is a WebView the right way to do this? Thanks for your help!
You need to make your WebView part of the window's contentView.
[self.window setContentView:self.websiteWebview];
By default, this will let the webView auto-resize with the window. You'll only need to mess with the sizing if you want the webview to do something other than match the size of the window.

Objective C: Download Thread not affecting UI, even possible?

I'm working through the Standford course on ItunesU and there they say a thread to download something should not do anything to the UI, this should only happen on the Main Thread.
Well, in my example I'm downloading a picture from Flicker and I want to setup this picture (via a segue) in a UIScrollView. So while I'm downloading this picture in the "side" thread I'm setting the image property of the UIScrollview to image etc. However this doesn't work obviously, because I don't know the imagesize yet and I also don't have a reference to that image object yet to set right?
So how do you handle that? I hope I'm clear..here is my example:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(NSIndexPath *)sender{
NSDictionary *selectedPhoto = [self.photos objectAtIndex:sender.row];
[self.defaults addPhotoToRecentlyViewed:selectedPhoto];
[self.defaults saveDefaults];
PhotoViewer *photoViewer = segue.destinationViewController;
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
photoViewer.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:spinner];
[spinner startAnimating];
dispatch_queue_t photoDownload = dispatch_queue_create("photoviewever", nil);
dispatch_async(photoDownload, ^{
NSData *data = [NSData dataWithContentsOfURL:[FlickrFetcher urlForPhoto:selectedPhoto format:FlickrPhotoFormatLarge]];
UIImage *image = [UIImage imageWithData:data];
photoViewer.image = image;
dispatch_async(dispatch_get_main_queue(), ^{
photoViewer.title = [selectedPhoto objectForKey:FLICKR_PHOTO_TITLE];
photoViewer.navigationItem.rightBarButtonItem = nil;
});
});
}
and my PhotoViewer:
#import "PhotoViewer.h"
#interface PhotoViewer ()
#property (nonatomic, strong) IBOutlet UIScrollView *scrollView;
#property (nonatomic, strong) IBOutlet UIImageView *imageView;
#end
#implementation PhotoViewer
#synthesize scrollView = _scrollView;
#synthesize imageView = _imageView;
#synthesize image = _image;
- (void)viewDidLoad
{
[super viewDidLoad];
self.scrollView.delegate = self;
[self setupImage];
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
return self.imageView;
}
- (void)setupImage{
self.imageView.image = self.image;
self.imageView.frame = CGRectMake(0, 0, self.image.size.width, self.image.size.height);
self.scrollView.contentSize = self.image.size;
[self.imageView setNeedsDisplay];
}
#end
You should move this line:
photoViewer.image = image;
into the "completion" block you dispatch back to the main thread. PhotoViewer appears to be a view controller, and as such it's subject to the same "main thread only" rules as other UI. Also, it looks like you'll need to call -setupImage again from that main thread "completion" block. (Otherwise the image will never get pushed into the image view, as you noted.)
Also in -setupImage, you want to check that self.image returns a non-nil value before addressing into it. Depending on which compiler you're using, the behavior of calling a struct-returning Objective C method is 'undefined' (i.e. self.image.size returns a CGSize struct). (Although in recent compilers, it returns a zero filled struct.)

AdMob in iOS with UIWebView

I'm using the latest Xcode (4.4.1) and developing for iOS 5.1. I am utilizing the bottom tab bar interface provided by Apple. One of the tabs uses a UIWebView that utilizes the full screen space. When I try to add a standard banner provided by AdMob, it does not add a banner at all. I was following along with: https://developers.google.com/mobile-ads-sdk/docs/admob/fundamentals. Code attached below
About.h
#import <UIKit/UIKit.h>
#import "GADBannerView.h"
#interface About : UIViewController <UIWebViewDelegate> {
IBOutlet UIWebView *webView;
// Declare one as an instance variable
GADBannerView *bannerView_;
}
#property (nonatomic, retain) UIWebView *webView;
#end
About.m
#import "About.h"
#import "GADBannerView.h"
#import "GADRequest.h"
#import "constants.h"
#implementation About
#synthesize webView;
//#synthesize bannerView = bannerView_;
+ (void)initialize {
// Set user agent (the only problem is that we can't modify the User-Agent later in the program)
NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:UserAgent, #"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];
}
- (void)viewDidLoad {
[super viewDidLoad];
NSString *fullURL = ([IsBeta isEqualToString: #"true"]) ? #"http://beta.wouldyouratherapp.com/questions/index/0/1" : #"http://wouldyouratherapp.com/questions/index/0/1";
NSURL *url = [NSURL URLWithString:fullURL]; NSURLRequest *requestObj = [NSURLRequest requestWithURL:url]; [webView loadRequest:requestObj];
// Create a view of the standard size at the bottom of the screen.
// Available AdSize constants are explained in GADAdSize.h.
bannerView_ = [[GADBannerView alloc] initWithAdSize:kGADAdSizeBanner];
// Specify the ad's "unit identifier." This is your AdMob Publisher ID.
bannerView_.adUnitID = MyAdUnitID;
// Let the runtime know which UIViewController to restore after taking
// the user wherever the ad goes and add it to the view hierarchy.
bannerView_.rootViewController = self;
[self.view addSubview:bannerView_];
// Initiate a generic request to load it with an ad.
[bannerView_ loadRequest:[GADRequest request]];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
#end
Yes, I have added all the frameworks already, and MyAdUnitID is already defined in the constants file, so everything SHOULD be working, but I guess I am missing something. Any help?
If you're adding the bannerView_, you'll have to decrease the height of your webView accordingly to make room for the bannerView_. Since the origin of the ad looks like its at (0,0), you probably want something similar to this in your adView:DidReceiveAd: callback:
webView.frame = CGRectMake (0, bannerView_.frame.size.height, webView.frame.size.width, webView.frame.size.height - bannerView_.frame.size.height);