Crash when closing window in a MacOS application? - objective-c

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
}

Related

Apple Metal without Interface Builder

I am racking my brain on this one. I've been trying to duplicate the initial metal project provided by Apple but without using interface builder at all. I'm able to create a window, but it's blank, nothing is rendering and I can't figure out why.
main.m
#import <Cocoa/Cocoa.h>
#import "AppDelegate.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSApplication* app = [NSApplication sharedApplication];
AppDelegate* appDelegate = [[AppDelegate alloc] init];
[app setDelegate:appDelegate];
[app run];
}
return EXIT_SUCCESS;
}
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate> {
NSWindow *window;
}
#end
AppDelegate.m
#import <Metal/Metal.h>
#import "AppDelegate.h"
#import "GameViewController.h"
#interface AppDelegate ()
#end
#implementation AppDelegate
- (id)init {
if (self = [super init]) {
NSScreen* mainScreen = [NSScreen mainScreen];
NSRect frame = NSMakeRect(0, 0, mainScreen.frame.size.width / 2, mainScreen.frame.size.height / 2);
NSUInteger styleMask =
NSWindowStyleMaskTitled |
NSWindowStyleMaskResizable |
NSWindowStyleMaskClosable |
NSWindowStyleMaskMiniaturizable;
NSBackingStoreType backing = NSBackingStoreBuffered;
window = [[NSWindow alloc] initWithContentRect:frame styleMask:styleMask backing:backing defer:YES];
[window center];
GameViewController* gvc = [[GameViewController alloc] init];
MTKView* metalView = [[MTKView alloc] initWithFrame:frame device:MTLCreateSystemDefaultDevice()];
[metalView setClearColor:MTLClearColorMake(0, 0, 0, 1)];
[metalView setColorPixelFormat:MTLPixelFormatBGRA8Unorm];
[metalView setDepthStencilPixelFormat:MTLPixelFormatDepth32Float];
[metalView setDelegate:gvc];
[gvc setView:metalView];
[window setContentView:metalView];
//[window setContentViewController: gvc];
}
return self;
}
- (void)applicationWillFinishLaunching:(NSNotification *)notification {
[window makeKeyAndOrderFront:self];
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender {
return YES;
}
#end
The other files; GameViewController.h, GameViewController.m, Shaders.metal, SharedStructures.h; are the same as what XCode 8.2.1 (8C1002) auto generates when you create a Game project that uses Metal with Objective-c.
You'll notice the line [window setContentViewController: gvc]; is commented out. When this is uncommented I get an EXEC_BAD_ACCESS on the first line of GameViewController.m's render function dispatch_semaphore_wait(_inflight_semaphore, DISPATCH_TIME_FOREVER);
Clearly there's something that I'm missing, but I've been googling all day and I can't seem to figure it out. Any help is much appreciated.
The issue is that, when an NSViewController such as the GameViewController is not loaded from a NIB, it doesn't call the -viewDidLoad method. The GameViewController does important work in that method.
You can move the creation of the view into an override of -loadView in GameViewController. Create the view and assign it to self.view. You don't need to set its delegate. The existing GameViewController code does that already. Setting the view's properties should be moved to -_setupView where the existing code already does that sort of thing.
Don't create the view in -[AppDelegate init]. Obviously, you also won't be able to set the view controller's view there, either. Just set the view controller as the window's content view controller (uncomment that line) and the rest will be taken care of automatically. When the window requests the content view controller's view property, it will call your override of -loadView along with the existing -viewDidLoad, etc.

WebView being released too many times

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.

Modal session requires modal window Error for when trying to show second window in Xib

I got a Xib with two NSWindows inside and a Controller inherited from NSWindowController. The first window is connected to the window IBOutlet. The second is connected to a new IBOutlet mySecondWindow.
I then create the controller using
myController = [[MySheetController alloc] initWithWindowNibName:#"MySheet"];
Then I call a method on this controller which shows the first window as a Sheet:
[NSApp beginSheet:self.window modalForWindow:mainWindow modalDelegate:self didEndSelector: #selector(didEndSheet:returnCode:contextInfo:) contextInfo: nil];
This works as expected. But if i now run the call with my second window instead, it will give the error "Modal session requires modal window". And when i debug, i can see the mySecondWindow IBOutlet is nil.
[NSApp beginSheet:mySecondWindow modalForWindow:mainWindow modalDelegate:self didEndSelector: #selector(didEndSheet:returnCode:contextInfo:) contextInfo: nil];
When i now change the window IBOutlet to my second window, it will work normally.
So what do I have to do to get the second sheet shown. Why will it only load when it's connected to the window IBOutlet? Shouldn't the initWithWindowNibName call create all windows in the Xib?
Edit: so i made a minimal example:
AppDelegate.h:
#import <Cocoa/Cocoa.h>
#import "MyWindowController.h"
#interface AppDelegate : NSObject <NSApplicationDelegate>
{
MyWindowController* mwc;
}
#property (assign) IBOutlet NSWindow *window;
#end
AppDelegate.m:
#import "AppDelegate.h"
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
mwc = [[MyWindowController alloc] initWithWindowNibName:#"Window"];
// 1. the following works:
[NSApp beginSheet:mwc.window modalForWindow:self.window modalDelegate: self didEndSelector: #selector(didEndSheet:returnCode:contextInfo:) contextInfo: nil];
// 2. this one doesn't
[NSApp beginSheet:mwc.mySecondWindow modalForWindow:self.window modalDelegate: self didEndSelector: #selector(didEndSheet:returnCode:contextInfo:) contextInfo: nil];
}
- (void)didEndSheet:(NSWindow *)sheet returnCode:(NSInteger)returnCode contextInfo:(void *)contextInfo
{
[sheet orderOut:self];
}
#end
MyWindowController.h:
#import <Cocoa/Cocoa.h>
#interface MyWindowController : NSWindowController
#property (strong) IBOutlet NSPanel *mySecondWindow;
#end
MyWindowController.m:
#import "MyWindowController.h"
#implementation MyWindowController
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if (self) {
// Initialization code here.
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
}
#end
Then i made a new Xib with a NSWindow in it and a second NSPanel. I set the class of the File's Owner to the MyWindowControlller class. Then i connected the NSWindow to the window property of the files owner and i made a new IBOutlet und connected the NSPanel to it. For both windows i deselected "Visible at Launch".
When running the first beginSheet call, all works as expected. But when i instead run the second line with the mySecondWindow it gives me a:
*** Assertion failure in -[NSApplication _commonBeginModalSessionForWindow:relativeToWindow:modalDelegate:didEndSelector:contextInfo:], /SourceCache/AppKit/AppKit-1187.40/AppKit.subproj/NSApplication.m:3920
Modal session requires modal window
I got a solution:
- (id)initWithWindowNibName:(NSString *)windowNibName
{
self = [super initWithWindowNibName:windowNibName];
if (self) {
[self window];
}
return self;
}
I need to call self.window one time, then everything works out ok. The windows will be loaded lazy when accessing the window property. That's why.
I guess there's a better way to force it to load all windows, so i can use the second window first, but well this works.
See the comments here: How show sheet loaded from xib? (MacOSx)

NSApplicationDelegate application:openFile. Never reaches to openFile: function

I want to open a file dropping it on the app icon.
When I do it my app is opened so the file extension is well defined and related to my app.
But the application:openFile: function never is called. so I can't open the file dropped in my app.
I traced openFile: but never goes.
All the answers that I found are just to add in the delegate the openFile: and that's all but not in my case.
Any help will be very appreciate it. Thanks a lot in advance.
This is my environment.
The plist has got the extension of files to be opened. My app is opened when I drop the files.
I initialize my delegate at the beggining of the app,
mydelegate = [[MyController alloc] init];
And in the delegate,
in the include,
#interface MyController : NSObject <NSApplicationDelegate> {
#private
NSWindow *window;
}
#property (assign) IBOutlet NSWindow *window;
-(id) init;
-(BOOL) application: (NSApplication*)sharedApplication openFile:(NSString*) fileName;
#end
And in the .m file,
#implementation MyController
#synthesize window;
- (id)init{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillFinishLaunching:)
name:NSApplicationWillFinishLaunchingNotification object:nil];
}
return self;
}
- (void) applicationWillFinishLaunching:(NSNotification *)aNotification{
NSLog(#"applicationWillFinishLaunching");
}
-(BOOL) application: (NSApplication*)sharedApplication openFile:(NSString*) fileName {
NSLog(#"openFile=%#", fileName);
return YES;
}
#end
At least in the code provided above, you are not explicitly setting the app's delegate to be an instance of MyController. Are you setting the delegate anywhere?
Immediately following [[MyController alloc] init], try this:
[[NSApplication sharedApplication] setDelegate: mydelegate];
Without making this connection, the app won't know who is supposed to handle delegate responsibilities.
OR
The most common way to handle drag and drop onto the dock icon is to simply implement:
-(BOOL)application:(NSApplication *)sender openFile:(NSString *)path
as part of the AppDelegate class that is auto-generated for you by Xcode when you start a project.
If you have an AppleEvent event handler listening to for 'odoc' Open Document apple events:
NSAppleEventManager.shared().setEventHandler(self,
andSelector: #selector(handle(event:replyEvent:)),
forEventClass: AEEventClass(kCoreEventClass),
andEventID: AEEventID(kAEOpenDocuments))
Then the handler will intercept the calls and the normal App Delegate methods will not be called.

Going Crazy with UITextField Input in a Very Simple App

I have a PromoCodeViewController which is triggered using the following code:
#implementation Demo_WebServiceCallingUsingiOSAppDelegate
#synthesize window = _window,promoCodeController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.promoCodeController = [[PromoCodeViewController alloc] init];
[self.window addSubview:self.promoCodeController.view];
// Override point for customization after application launch.
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
return YES;
}
The PromoCodeViewController contains the UITextField and I implement the UITextFieldDelegate for the PromoCodeViewController as shown:
#interface PromoCodeViewController : UIViewController<UITextFieldDelegate>
{
IBOutlet UITextField *promoCodeTextField;
}
#property (nonatomic,retain) IBOutlet UITextField *promoCodeTextField;
#end
I implement the textFieldShouldReturn method as shown:
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
return TRUE;
}
I have even setup the PromoCodeViewController to be the delegate for the UITextField events. When I start typing in the TextBox it throws "Program received signal. EXC_BAD_ACCESS". It happens when I type second character in the UITextField. What am I doing wrong?
UPDATE 1:
The error comes on the following section:
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([Demo_WebServiceCallingUsingiOSAppDelegate class]));
}
}
It looks as though you're missing an #synthesize statement for the promoCodeTextField property. Add the following to your Demo_WebServiceCallingUsingiOSAppDelegate class (which by the way, might benefit from renaming):
#synthesize promoCodeTextField = _promoCodeTextField;
Without the #synthesize statement, Xcode's Nib File Editor will directly set the instance variable instead of calling the accessor methods (since they don't exist); as a consequence the text field is never retained.
What object is the delegate of the textfield? Objects do not usually retain their delegates (since the delegate is often retaining a reference to the object, and retain-cycles lead to memory leaks).
My hunch is that your text field's delegate is defined and attached in your nib, but isn't being retained anywhere, so shortly after the nib is loaded the delegate is deallocated. As soon as the text field attempts to do anything with the delegate, it crashes.
If so, make sure something (usually the application delegate) is retaining a reference to whatever object is serving as the delegate of your text field.