I'm wondering what's the correct way to spawn a bunch of windows from a callback, basically i'd like to hit a combination like Ctrl+Shift + Cmd + + and create a new window, which is not bound to the app delegate. Currently i have the following code in my AppDelegate.m:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSLog(#"Finished");
[NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^(NSEvent *event) {
NSUInteger ctrlPressed = [event modifierFlags] & NSEventModifierFlagControl;
NSUInteger shiftPressed = [event modifierFlags] & NSEventModifierFlagShift;
NSUInteger cmdPressed = [event modifierFlags] & NSEventModifierFlagCommand;
NSUInteger EqButton = 0x30;
if (ctrlPressed && shiftPressed && cmdPressed) {
if ([event keyCode] & EqButton) {
__strong ETTimerController *controller = [ETTimerController new];
[controller showWindow: self];
}
}
}];
}
This snippet creates a controller and a bound nib file, but i can't see the window, i assume it's collected by the ARC. How can i retain create and retain a new window without storing the reference within the AppDelegate instance?
Not only the window, but I think the window controller is being released here once the block exits.
Store it in a strong property of AppDelegate:
#property (nonatomic, strong) timerController *ETTimerController;
Then, instantiate it like this:
self.timerController = [ETTimerController new];
[self.timerController showWindow: self];
EDIT: As mentioned in the comments, the window controller reference needs to be owned by some persistent object (or be a static variable somewhere) otherwise it goes out of scope as soon as the method exits and ARC deallocates the object.
If you can't afford a dedicated property in the AppDelegate class (because you need to have a variable number of different window controllers, etc.), then you will need to consider what is the expected lifetime of each controller, and store a reference accordingly:
If your window controller is reused, i.e. its window is repeatedly shown and hidden but you only need at most one, make it a singleton and the static reference to the shared instance will take care of retaining it.
If you are building a document-based app, each instance of your NSDocument subclass will create all its window controllers within the makeWindowController() method, and retain them inside an array property.
If none of the above apply, you will need to think of something else that -again- will vary depending on your needs and specifications.
Related
I come from a C/C++ background and am currently learning a bit about Cocoa and Objective-C.
I have a weird behavior involving lazy initialization (unless I'm mistaken) and feel like I'm missing something very basic.
Setup:
Xcode 10.1 (10B61)
macOS High Sierra 10.13.6
started from a scratch Cocoa project
uses Storyboard
add files TestMainView.m/.h
under the View Controller in main.storyboard, set the NSView custom class as TestMainView
tested under debug and release builds
Basically, I create an NSTextView inside a view controller to be able to write some text.
In TestMainView.m, I create the chain of objects programmatically as decribed here
There are two paths:
first one is enabled by setting USE_FUNCTION_CALL to 0, it makes the entire code run inside awakeFromNib().
second path is enabled by setting USE_FUNCTION_CALL to 1. It makes the text container and text view to be allocated from the function call addNewPage() and returns the text container for further usage.
First code path works just as expected: I can write some text.
However second code path just doesn't work because upon return, textContainer.textView is nil (textContainer value itself is totally fine).
What's more troubling though (and this is where I suspect lazy init to be the culprit) is that if I "force" the textContainer.textView value while inside the function call, then everything works just fine. You can try this by setting FORCE_VALUE_LOAD to 1.
It doesn't have to be an if(), it works with NSLog() as well. It even works if you set a breakpoint at the return line and use the debugger to print the value ("p textContainer.textView")
So my questions are:
is this related to lazy initialization ?
is that a bug ? is there a workaround ?
am I thinking about Cocoa/ObjC programming the wrong way ?
I really hope I am missing something here because I cannot be expected to randomly check variables here and there inside Cocoa classes, hoping that they would not turn nil. It even fails silently (no error message, nothing).
TestMainView.m
#import "TestMainView.h"
#define USE_FUNCTION_CALL 1
#define FORCE_VALUE_LOAD 0
#implementation TestMainView
NSTextStorage* m_mainStorage;
- (void)awakeFromNib
{
[super awakeFromNib];
m_mainStorage = [NSTextStorage new];
NSLayoutManager* layoutManager = [[NSLayoutManager alloc] init];
#if USE_FUNCTION_CALL == 1
NSTextContainer* textContainer = [self addNewPage:self.bounds];
#else
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:NSMakeSize(FLT_MAX, FLT_MAX)];
NSTextView* textView = [[NSTextView alloc] initWithFrame:self.bounds textContainer:textContainer];
#endif
[layoutManager addTextContainer:textContainer];
[m_mainStorage addLayoutManager:layoutManager];
// textContainer.textView is nil unless forced inside function call
[self addSubview:textContainer.textView];
}
#if USE_FUNCTION_CALL == 1
- (NSTextContainer*)addNewPage:(NSRect)containerFrame
{
NSTextContainer* textContainer = [[NSTextContainer alloc] initWithSize:NSMakeSize(FLT_MAX, FLT_MAX)];
NSTextView* textView = [[NSTextView alloc] initWithFrame:containerFrame textContainer:textContainer];
[textView setMaxSize:NSMakeSize(FLT_MAX, FLT_MAX)];
#if FORCE_VALUE_LOAD == 1
// Lazy init ? textContainer.textView is nil unless we force it
if (textContainer.textView)
{
}
#endif
return textContainer;
}
#endif
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
}
#end
TestMainView.h
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
#interface TestMainView : NSView
#end
NS_ASSUME_NONNULL_END
I am not familiar with cocoa that much but I think the problem is ARC (Automatic reference counting).
NSTextView* textView = [[NSTextView alloc] initWithFrame:containerFrame textContainer:textContainer];
In the .h file of NSTextContainer you can see NSTextView is a weak reference type.
So after returning from the function it gets deallocated
But if you make the textView an instance variable of TestMainView it works as expected.
Not really sure why it also works if you force it though. ~~(Maybe compiler optimisation?)~~
It seems forcing i.e calling
if (textContainer.textView) {
is triggering retain/autorelease calls so until the next autorelease drain call, textview is still alive.(I am guessing it does not get drained until awakeFromNib function returns). The reason why it works is that you are adding the textView to the view hierarchy(a strong reference) before autorelease pool releases it.
cekisakurek's answer is correct. Objects are deallocated if there is no owning (/"strong") reference to them. Neither the text container nor the text view have owning references to each other. The container has a weak reference to the view, which means that it's set to nil automatically when the view dies. (The view has an non-nilling reference to the container, which means you will have a dangling pointer in textView.textContainer if the container is deallocated while the view is still alive.)
The text container is kept alive because it's returned from the method and assigned to a variable, which creates an owning reference as long as that variable is in scope. The view's only owning reference was inside the addNewPage: method, so it does not outlive that scope.
The "force load" has nothing to do with lazy initialization; as bbum commented, that it "works" is most likely to be accidental. I strongly suspect it wouldn't in an optimized build.
Let me assure you that you do not need to go around poking properties willy-nilly in Cocoa programming. But you do need to consider ownership relations between your objects. In this case, something else needs to own both container and view. That can be your class here, via an ivar/property, or another object that's appropriate given the NSText{Whatever} API (which is not familiar to me).
I am writing an app which is a sort of dictionary - it presents the user with a list of terms, and when clicked on, pops up a dialog box containing the definition. The definition itself may also contain terms, which in turn the user can click on to launch another definition popup.
My main app is stored in 'myViewController.m'. It calls a custom UIView class, 'CustomUIView.m' to display the definition (this is the dialog box that pops up). This all works fine.
The text links from the CustomUIView then should be able to launch more definitions. When text is tapped in my CustomUIView, it launches another CustomUIView. The problem is, that this new CustomUIView doesn't have access to the hash map which contains all my dictionary's terms and definitions; this is only available to my main app, 'myViewController.m'.
Somehow, I need to make my hash map, dictionaryHashMap, visible to every instance of the CustomUIView class. dictionaryHashMap is created in myViewController.m when the app opens and doesn't change thereafter.
I don't wish to limit the number of CustomUIViews that can be opened at the same time (I have my reasons for doing this!), so it would be a little resource intensive to send a copy of the dictionaryHashMap to every instance of the CustomUIView. Presumably, the solution is to make dictionaryHashMap a global variable.
Some of my code:
From myViewController.m:
- (void)viewDidLoad
{
self.dictionaryHashMap = [[NSMutableDictionary alloc] init]; // initialise the dictionary hash map
//... {Code to populate dictionaryHashMap}
}
// Method to pop up a definition dialog
- (void)displayDefinition:(NSString *) term
{
NSArray* definition = [self.dictionaryHashMap objectForKey:term]; // get the definition that corresponds to the term
CustomUIView* definitionPopup = [[[CustomUIView alloc] init] autorelease]; // initialise a custom popup
[definitionPopup setTitle: term];
[definitionPopup setMessage: definition];
[definitionPopup show];
}
// Delegation for sending URL presses in CustomUIView to popupDefinition
#pragma mark - CustomUIViewDelegate
+ (void)termTextClickedOn:(CustomUIView *)customView didSelectTerm:(NSString *)term
{
myViewController *t = [[myViewController alloc] init]; // TODO: This instance has no idea what the NSDictionary is
[t displayDefinition:term];
}
From CustomUIView.m:
// Intercept clicks on links in UIWebView object
- (BOOL)webView: (UIWebView*)webView shouldStartLoadWithRequest: (NSURLRequest*)request navigationType: (UIWebViewNavigationType)navigationType {
if ( navigationType == UIWebViewNavigationTypeLinkClicked ) {
[myViewController termTextClickedOn:self didSelectTerm:request];
return NO;
}
return YES;
}
Any tips on how to make the dictionaryHashMap visible to CustomUIView would be much appreciated.
I have tried making the dictionaryHashMap global by doing the following:
Changing all instances of 'self.dictionaryHashMap' to 'dictionaryHashMap'
Adding the line 'extern NSMutableDictionary *dictionaryHashMap;' to CustomUIView.h
Adding the following outside of my implementation in myViewController.m: 'NSMutableDictionary *dictionaryHashMap = nil;'
However, the dictionaryHashMap remains invisible to CustomUIView. As far as I can tell, it actually remains a variable which is local to myViewController...
It's not resource-intensive to pass around the reference (pointer) to dictionaryHashMap. A pointer to an object is only 4 bytes. You could just pass it from your view controller to your view.
But I don't know why you even need to do that. Your view is sending a message (termTextClickedOn:didSelectTerm:) to the view controller when a term is clicked. And the view controller already has a reference to the dictionary, so it can handle the lookup. Why does the view also need a reference to the dictionary?
Anyway, if you want to make the dictionary a global, it would be more appropriate to initialize it in your app delegate, in application:didFinishLaunchingWithOptions:. You could even make the dictionary be a property of your app delegate and initialize it lazily.
UPDATE
I didn't notice until your comment that termTextClickedOn:didSelectTerm: is a class method. I assumed it was an instance method because myViewController starts with a lower-case letter, and the convention in iOS programming is that classes start with capital letters. (You make it easier to get good help when you follow the conventions!)
Here's what I'd recommend. First, rename myViewController to MyViewController (or better, DefinitionViewController).
Give it a property that references the dictionary. Whatever code creates a new instance of MyViewController is responsible for setting this property.
Give CustomUIView properties for a target and an action:
#property (nonatomic, weak) id target;
#property (nonatomic) SEL action;
Set those properties when you create the view:
- (void)displayDefinition:(NSString *)term {
NSArray* definition = [self.dictionaryHashMap objectForKey:term];
CustomUIView* definitionPopup = [[[CustomUIView alloc] init] autorelease]; // initialise a custom popup
definitionPopup.target = self;
definitionPopup.action = #selector(termWasClicked:);
...
In the view's webView:shouldStartLoadWithRequest: method, extract the term from the URL request and send it to the target/action:
- (BOOL)webView: (UIWebView*)webView shouldStartLoadWithRequest: (NSURLRequest*)request navigationType: (UIWebViewNavigationType)navigationType {
if ( navigationType == UIWebViewNavigationTypeLinkClicked ) {
NSString *term = termForURLRequest(request);
[self.target performSelector:self.action withObject:term];
return NO;
}
return YES;
}
In the view controller's termWasClicked: method, create the new view controller and set its dictionary property:
- (void)termWasClicked:(NSString *)term {
MyViewController *t = [[MyViewController alloc] init];
t.dictionary = self.dictionary;
[t displayDefinition:term];
}
Create a class that will be used as singleton. Example.
You Should always keep your data in separate class as the mvc pattern suggest and that could be achieved by using a singleton class for all your dictionary terms and accesing them from every custom view when needed.
I'm seeing some disturbing irregularities concerning object allocation and initialization in an app I'm trying to write.
I have a 'root' Modelcontroller object, which in turn contains references to subcontrollers. The root controller is called modelController, and in it's init method it allocates and inits the subcontrollers like so:
- (id)init
{
NSLog(#"%#", #"ModelController begin init");
self = [super init];
if (self) {
LibraryController * tempLibrary = [[LibraryController alloc] init];
self.library = tempLibrary;
StoresController * tempStores = [[StoresController alloc] init];
self.stores = tempStores;
CLLocationManager * tempLocationManager = [[CLLocationManager alloc] init];
self.locationManager = tempLocationManager;
}
NSLog(#"%#", #"ModelController complete init");
return self;
}
Pretty standard. The subcontrollers' init code also contain an NSLog messages at the beginning and the end, for me to be able to see that all is well.
The properties are defined as
#property (strong) LibraryController * library;
#property (strong) StoresController * stores;
#property (strong) CLLocationManager * locationManager;
And I am using ARC.
What puzzles me is that sometimes I see the NSLogs from one of the subcontrollers, but not from the root controller. Sometimes I see the 'begin init' log message from the root controller, but not the 'complete init'. Sometimes I see no init log messages. The application launches anyway in any of these cases.
This happens seemingly at random, in one out of five launches or in one out of twenty launches. When it happens, the app acts very strange (but not every time, mind you), beachballing for no apparent reason and exhibiting general wonkiness.
As a side note, at one time I put a breakpoint in the init method of the StoreController class, which when pausing executing spit out a chunk of random data in the debugging console:
$m2303,3503,3603,3703,3803,3903#00$m2303,3503,3603,3a03#00$88ee410901000000981e420901000000001e42090100000060ee410901000000b062f668ff7f000070044391ff7f0000f00e0800000000000300000068200100dc62f668ff7f0000d862f668ff7f00000000000000000000717ddd8aff7f00000000000068200100801e420901000000000000000600000706000007000000007063f668ff7f000003280000000000007863f668ff7f000001ee410901000000f062f668ff7f00006c5bd391ff7f000000000000ff7f0000ab064391ff7f000000000000ffffffff032800000000000040
...and so on
Where should I begin to look to troubleshoot this?
The modelController is alloc init'd from the MyDocument equivalent class, and is modeled as a singleton.
The singleton implementation looks like this:
static ModelController *sharedModelController = nil;
+ (ModelController*)sharedManager
{
if (sharedModelController == nil) {
sharedModelController = [self new];
}
return sharedModelController;
}
Final note: I have tried removing the locationManager stuff and disabling/enabling the 'Restore state' preference in the scheme, but to no avail.
Sounds like you're doing some UI stuff not on the main thread.
This generally leads to weird behavior.
Make sure you call everything UI related on the main thread
Best guess: the ModelController object is being released. Perhaps the Singleton is faulty.
UPDATE | I've uploaded a sample project using the panel and crashing here: http://w3style.co.uk/~d11wtq/BlocksCrash.tar.gz (I know the "Choose..." button does nothing, I've not implemented it yet).
UPDATE 2 | Just discovered I don't even have to invoke anything on newFilePanel in order to cause a crash, I merely need to use it in a statement.
This also causes a crash:
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
newFilePanel; // Do nothing, just use the variable in an expression
}];
It appears the last thing dumped to the console is sometimes this: "Unable to disassemble dyld_stub_objc_msgSend_stret.", and sometimes this: "Cannot access memory at address 0xa".
I've created my own sheet (an NSPanel subclass), that tries to provide an API similar to NSOpenPanel/NSSavePanel, in that it presents itself as a sheet and invokes a block when done.
Here's the interface:
//
// EDNewFilePanel.h
// MojiBaker
//
// Created by Chris Corbyn on 29/12/10.
// Copyright 2010 Chris Corbyn. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#class EDNewFilePanel;
#interface EDNewFilePanel : NSPanel <NSTextFieldDelegate> {
BOOL allowsRelativePaths;
NSTextField *filenameInput;
NSButton *relativePathSwitch;
NSTextField *localPathLabel;
NSTextField *localPathInput;
NSButton *chooseButton;
NSButton *createButton;
NSButton *cancelButton;
}
#property (nonatomic) BOOL allowsRelativePaths;
+(EDNewFilePanel *)newFilePanel;
-(void)beginSheetModalForWindow:(NSWindow *)aWindow completionHandler:(void (^)(NSInteger result))handler;
-(void)setFileName:(NSString *)fileName;
-(NSString *)fileName;
-(void)setLocalPath:(NSString *)localPath;
-(NSString *)localPath;
-(BOOL)isRelative;
#end
And the key methods inside the implementation:
-(void)beginSheetModalForWindow:(NSWindow *)aWindow completionHandler:(void (^)(NSInteger result))handler {
[NSApp beginSheet:self
modalForWindow:aWindow
modalDelegate:self
didEndSelector:#selector(sheetDidEnd:returnCode:contextInfo:)
contextInfo:(void *)[handler retain]];
}
-(void)dismissSheet:(id)sender {
[NSApp endSheet:self returnCode:([sender tag] == 1) ? NSOKButton : NSCancelButton];
}
-(void)sheetDidEnd:(NSWindow *)aSheet returnCode:(NSInteger)result contextInfo:(void *)contextInfo {
((void (^)(NSUInteger result))contextInfo)(result);
[self orderOut:self];
[(void (^)(NSUInteger result))contextInfo release];
}
This all works provided my block is just a no-op with an empty body. My block in invoked when the sheet is dismissed.
EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
[newFilePanel setAllowsRelativePaths:[self hasSelectedItems]];
[newFilePanel setLocalPath:#"~/"];
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
NSLog(#"I got invoked!");
}];
But as soon as I try to access the panel from inside the block, I crash with EXC_BAD_ACCESS. For example, this crashes:
EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
[newFilePanel setAllowsRelativePaths:[self hasSelectedItems]];
[newFilePanel setLocalPath:#"~/"];
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
NSLog(#"I got invoked and the panel is %#!", newFilePanel);
}];
It's not clear from the debugger with the cause is. The first item (zero 0) on the stack just says "??" and there's nothing listed.
The next items (1 and 2) in the stack are the calls to -endSheet:returnCode: and -dismissSheet: respectively. Looking through the variables in the debugger, nothing seems amiss/out of scope.
I had thought that maybe the panel had been released (since it's autoreleased), yet even calling -retain on it right after creating it doesn't help.
Am I implementing this wrong?
It's a little odd for you to retain a parameter in one method and release it in another, when that object is not an instance variable.
I would recommend making the completionHandler bit of your beginSheet stuff an instance variable. It's not like you'd be able to display the sheet more than once at a time anyway, and it would be cleaner this way.
Also, your EXC_BAD_ACCESS is most likely coming from the [handler retain] call in your beginSheet: method. You're probably invoking this method with something like (for brevity):
[myObject doThingWithCompletionHandler:^{ NSLog(#"done!"); }];
If that's the case, you must -copy the block instead of retaining it. The block, as typed above, lives on the stack. However, if that stack frame is popped off the execution stack, then that block is gone. poof Any attempt to access the block later will result in a crash, because you're trying to execute code that no longer exists and has been replaced by garbage. As such, you must invoke copy on the block to move it to the heap, where it can live beyond the lifetime of the stack frame in which it was created.
Try defining your EDNewFilePanel with the __block modifier:
__block EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
This should retain the object when the block is called, which may be after the Panel object is released. As an unrelated side-effect, this will make also make it mutable within the block scope.
I'm creating a game that uses cards.
I have an AppController class with one instance in the nib.
The AppController instance has an NSArray instance variable called wordList.
On init, the nib's instance of AppController generates a new GameCard.
Every gamecard has an array of words containing 5 words selected at random from the the list in AppController.
Because the list is large, I'd like to read it into memory only once. Therefore, I want only one instance of AppController, as a singleton class. Every time a new GameCard is created from within AppController, it should access that same singleton instance to retrieve the wordlist.
So basically, I need a singleton AppController that creates GameCards, where each GameCard has a reference to the original AppController.
I'm not sure how to implement this. Sorry if the explanation was confusing.
A code example I found online follows (http://numbergrinder.com/node/29)
+ (AppController *)instance
{
static AppController *instance;
#synchronized(self) {
if(!instance) {
instance = [[AppController alloc] init];
}
}
return instance;
}
But when I tried to do something with it in a GameCard instance through the code below, my application took forever to launch and Xcode told me it was loading 99797 stack frames.
AppController *controller = [AppController instance];
It sounds like an infinite loop. Make sure that -[AppController init] isn't calling +[AppController instance].
Why does every card need a reference to the app controller?
If it's just to access its words, it's simpler to let each card own its words directly. Make a new method named initWithWords: the designated initializer for the GameCard class. Initialize each card with the array of its five words, and have the card own that array for its lifetime.
Removing the cards' references to the app controller would resolve the infinite loop that Tom astutely detected.
Also, if no word should appear on two cards at once, remember to take that into account when drawing from the app controller's Great Big Array Of Words, and when destroying cards (you may or may not want the words to go back into the pile for future cards).
It sounds like you're on the right track. I've never tried to put a reference to a singleton in a nib file, though. You may want to create a separate singleton class that maintains a copy of the data (DataManager, maybe?), and then call it from within your instance of AppController to fetch the words.
You may find that putting a singleton within a nib (using the code for a singleton in Stu's post) works just fine, though. Good luck!
It looks like you may be calling your class instance method from within your init method. Try something like this:
static AppController* _instance = nil;
- (id)init
{
// depending on your requirements, this may need locking
if( _instance ) {
[self release];
return _instance;
}
if( (self = [super init]) ) {
_instance = [self retain];
// do your initialization
}
return self;
}
+ (AppController*)instance
{
if( _instance ) return _instance;
else return [[AppController alloc] init];
}
This makes sure that only one instance of AppController is ever available and also that it's safe to allocate it as well as getting a copy through the instance class method. It's not thread safe, so if it's going to be accessed by multiple threads, you should add some locking around the checks to _instance.
The normal way to create an AppController/AppDelegate is to add a custom NSObject to your MainMenu/MainWindow.xib file. Set the class to be AppController. Link your UIApplication/NSApplication delegate reference to your AppController object. Then you can get your single AppController with either
(AppController*)[NSApp delegate];
or
(AppController*)[[UIApplication sharedApplication] delegate];
You never have to create it with alloc/init because it will be created when your application is launched. You don't have to worry about making it a singleton because no one will ever try to create another one. And you don't have to worry about how to access it because it will be the delegate of the UIApplication/NSApplication object.
All that said, if you need a global variable holding an array of words, then forget about the AppController and make a new singleton object which holds/reads the array. In which case you just need:
+ (NSArray *)sharedWordListArray
{
static NSArray *wordList;
if( !wordList ) {
wordList = [[NSMutableArray alloc] init];
// read array
}
return wordList;
}
If you really need thread safety, then simply call [WordList sharedWordListArray] from your app delegate's applicationDidFinishLaunching: method before starting any threads, or add an NSLock if you really want to defer the loading to later, but often its better to take the load time hit at the start of the program rather than unexpectedly when the user takes some later action.