event scope - objective-c

Given
#interface Canvas:NSView {
NSNumber * currentToolType;
...
}
declared in my .h file
and in the .m file
- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
currentToolType=[[NSNumber alloc]initWithInt:1];
}
return self;
}
and further down
-(void)mouseUp:(NSEvent *)event
{
NSLog(#"tool value in event: %d",[currentToolType intValue]);
//rest of code
}
-(NSBezzierPath *)drawPath:(NSRect)aRect
{
NSLog(#"tool value in draw: %d",[currentToolType intValue]);
//rest of drawPath method code that uses the value of currentToolType in a switch statment
}
-(IBAction)selectToolOne:(id)sender
{
[currentToolType release];
[currentToolType = [[NSNumber alloc]initWithInt:0];
}
-(IBAction)selectToolTwo:(id)sender
{
[currentToolType release];
[currentToolType = [[NSNumber alloc]initWithInt:1];
}
The action methods are the only place where currentToolType is changed. But, for some reason, it seems to be a different instance of currentToolType in the mouseUp. I did not write (or synthesize) accessors for the var as it is used only by itself. I noticed that initWithFrame is called twice - I'm assuming it's for the parent window and the NSView?
What am I missing?THANKS!
This is an XCode generated Document based app using COCOA and Obj-C. I'm new at both.

You mention that initWithFrame: is called twice. Your initWithFrame: should only be called once (unless you happen to have two Canvas views).
Is it possible you have the Canvas view in your nib/xib file and are also creating another in code (with alloc/initWithFrame:)?
In which case you have two Canvas objects. You probably have one hooked up to your controls and the other one is in the window (and thus responding to the mouseUp: and it is giving you the same value every time).
If you have the Canvas view setup in IB, you can fix this problem by removing your code that is creating the second one.

You've probably run in to a special case: NSNumber could have cached instances to represent commonly-used numbers.
Two observations, though:
You're wasting a whole lot of memory using NSNumber when you could be simply using NSIntegers or maybe an old-fashioned enumerated type, completely avoiding the object overhead.
You never actually showed your code for when you look at the instances of NSNumber; without it, there's not really enough information here to answer your question.

Related

Cocoa class member variable allocated inside function call nil unless forced to init/load

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).

Setting/getting global variables in objective-C

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.

Call a method every time a parameter is set on Objective-C (Cocoa)

I currently have a class with 15 properties (and growing), and I'm finding myself having to call an update method every time one of those properties change.
Currently, I'm overriding every setter with a code like this:
-(void)setParameterName:(NSUInteger)newValue {
if (_param == newValue)
return;
_param = newValue;
[self redraw];
}
The method [self redraw]; being the key here.
Is there a better way to do it? Should I be using keyValue observers (the method observeValue:forKeyPath:ofObject:change:context:)?
Notes:
All properties (so far) are assign (mostly enum, NSUInteger, CGFloat and BOOL);
All those properties are set using bindings (method bind:toObject:withKeyPath:options:). Except when loading from the filesystem (which is not important, as I already call the drawing methods on every object after the loading is done);
The value changes are only for the current object. I do not need to be told when changes occur on other objects;
I have other properties that I don't need to watch the changes on it (because it will have no effect on my output and drawing the output is kinda time-consuming).
Thanks!
Since these properties are updated using bindings, which invoke -setValue:forKey:, you can override that method instead of writing custom setters:
+ (NSArray *) keysAffectingDrawing {
static NSArray *singleton;
if (!singleton)
singleton = [NSArray arrayWithObjects:
#"property1",
#"property2",
#"property3",
nil];
return singleton;
}
- (void) setValue:(id) value forKey:(NSString *) key {
[super setValue:value forKey:key];
if ([[CustomClass keysAffectingDrawing] containsObject:key]) [self redraw];
}
(I was first inclined recommend key-value observing but agree it's not the best solution here. I think the reason is in part that there's only one object, and in part because the design doesn't follow MVC. Usually in MVC an object that draws itself isn't the one with all the properties.)
(Added: Ahh, I see. The model is responsible for rendering the properties to a bitmap, and that's what -redraw does. That's fine MVC. To make it clearer, I recommend changing the name of the method from -redraw to something like -updateImage or -renderImage, since it doesn't actually do any drawing.)
You could use the Key-Value Observing to avoid repeating in all properties setter the method call, however i think that calling the method directly in the setter is not the wrong way to do it, and could even be faster ...

Using stuff from other files in Cocos2d?

I am making a game (obviously) and I noticed that my HelloWorldLayer.m file is getting EXTREMELY cramped. I KNOW that there is a way to run methods from other .m files, I just don't know how. For example, I want to have a Character.h and Character.m file. Can I make it so in the HelloWorldLayer init layer, it just uses everything from the Character files instead of having to declare everything in the HelloWorldLayer? I hope my question makes sense, and any help is appreciated. Thanks!
Here is Character.m:
#implementation Character
#synthesize health,velocity;
-(void)dealloc {
[super dealloc];
}
-(id)initWithTexture:(CCTexture2D *)texture rect:(CGRect)rect
{
if((self = [super initWithTexture:texture rect:rect]))
{
[self scheduleUpdate];
}
return self;
}
-(void)update:(ccTime)dt {
[self setPosition:ccp(self.position.x,self.position.y)];
self = [CCSprite spriteWithFile:#"nukeboyGreen.gif"];
}
#end
And here's HelloWorldLayer.m (I skimped it down and took out the parts that aren't necessary):
self = [super init];
if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {
CGSize winSize = [[CCDirector sharedDirector] winSize];
character = [Character spriteWithFile:#"nukeboyGreeen.gif"];
character.position = ccp(winSize.width/2,winSize.height/2);
character.scale = 0.15;
[self addChild:character];
Note that I have a Character declared in HelloWorldLayer.h
This is where object oriented programming comes to the rescue. OOP encourages you to encapsulate variables and functions that is pertinent to an object in that object itself. In your case, you should put the methods that are specific to Character in the Character class, and only get your HelloWorld to trigger those methods.
Examples:
#interface Character : CCSprite {
...
}
- (void)didCollideWith:(Object *)object;
- (void)moveTo:(CGPoint)nextPoint;
- (void)shootArrow:(ckDirection)direction;
- (BOOL)isAlive;
- (int)numberOfLivesRemaining;
...
#end
Then in HelloWorldLayer:
[character moveTo:ccp(100, 200)];
[character shootArrow:kDirectionUp];
if (![character isAlive]) {
[self showGameOver];
}
Not only that your HelloWorldLayer is less cluttered, you can easily understand what your code does by simply looking at the reasonably named methods.
EDIT:
To answer your question as in the comment about how to designate the sprite image in Character class:
#implementation Character
- (id)init {
self = [super initWithFile:#"sprite_character.png"];
if (self) {
// further customization
}
return self;
}
#end
EDIT (after code was added to the question):
First let me point out a few mistake (sorry for the lack of softer word):
You rarely need your sprite to call the [self scheduleUpdate] or [self schedule:SEL]. Normally people implement the update (or tick) method at the CCLayer or CCScene level, where the purpose is to check all the actors (sprites, menus, nested layers etc) for collision/interaction and update their attributes. If you just want to animate movement of a sprite to a specific position, just call runAction method from CCLayer (in the init, update, ccTouchBegan or somewhere). You can read cocos2d-iphone tutorial on Actions by clicking here. So, move the update method and the scheduleUpdate call into your HelloWorldLayer, and then you no longer need to override initWithTexture.
I'm seeing you instantiating a CCSprite in the update method. My above point on the inappropriateness of update method in CCSprite notwithstanding, there is something more important you need to understand when you implement a method: that is you need to decide how and how often your method is going to be used/called. Since the update method is going to be called once per frame (that is 60 times per second), it is simply wrong to unconditionally instantiate an object in that method. You are making the iPhone to allocate (and deallocate) the object with no apparent reason, wasting the processor time/power the device has. You might want to ask where should you instantiate the CCSprite. The answer is in the init method because that method is only called once per object instance. Again, all you need to know is whether a method is going to be called once or multiple times, and decide whether a piece of code should be in there or somewhere else.
In your code for HelloWorldLayer, did you realize that you are calling the super init* methods twice. You don't need to call [super init] since [super initWithColor:ccc4( ... )] is going to call specific init method internally. Although it is not entirely wrong to do so, you are going to break the 'assumption' that init is going to be called once per instance, so you might end up breaking some object integrity unintentionally (and believe me it's going to be hard to debug later)
And finally, care to enlighten me what's the real purpose of the line [self setPosition:ccp(self.position.x,self.position.y)];. You basically set the position of the self object to its current position, so that's like saying "hey you, move your position to your current position" and he'll be like "huh?" :P

Singleton Design

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.