new to Obj C and programming in general - learned a lot from this site and really appreciate everyone's contributions.
My scenario is as follows (programming an iPhone game which explains the funny names)
In my main gameLoop (which is in my view controller) if a certain condition is met I create an enemy - the cherry bomb
if (bounceCounterGlobal % 2 == 0 && bounceCounterGlobal > 1 && cherryBombSwitch == 0){
[self addCherryBomb];
}
The addCherryBomb method is as follows:
-(void) addCherryBomb{
CherryBomb *myCherryBomb = [[CherryBomb alloc] init];
[cherryBombArray insertObject:myCherryBomb atIndex:0];
[myCherryBomb release];
[[cherryBombArray objectAtIndex:0] initializeCherryBomb];
[self.view addSubview:[[cherryBombArray objectAtIndex:0] cherryBombView]];
cherryBombSwitch = 1;
}
The CherryBomb header file is short:
#import <Foundation/Foundation.h>
#import "SimpleGameViewController.h"
#interface CherryBomb : NSObject {
UIImageView *cherryBombView;
NSTimer *cherryBombDetonateTimer;
NSTimer *cherryBombMoveTimer;
}
#property (nonatomic, retain) UIView *cherryBombView;
-(void) initializeCherryBomb;
-(void) detonateCherryBomb;
-(void) moveCherryBomb;
#end
What I would like to do is when the cherry bomb detonates (which is determined within the cherryBomb object), I would like the object to remove itself from the cherryBombArray which is an ivar of the view controller.
I tried calling a view controller class method to do this - but I am unable to access ivars of the view controller (because it is a class method). I do not know how to communicate back to the view controller class to tell it to remove the exploded object.
#implementation CherryBomb
...
-(void) detonateCherryBomb{
NSLog(#"KABOOM!");
cherryBombDetonateTimer = nil;
[cherryBombMoveTimer invalidate];
[cherryBombView removeFromSuperview];
//I would like to remove this object from the view controller's cherryBombArray
}
#end
Your help is greatly appreciated. Thank you in advance!
I recommend you to create some "Environment" object that will handle all the gaming logic.
The cherryBomb shouldn't deal with its explosion. The cherryBomb can store many information (size of the explosion, type of explosion, and so on), but the effects of the cherryBomb on the others "things" (characters, bombs, whatever) shouldn't be calculated by the cherryBomb itself.
I'm not very used to game programming, but this aspect of architecture/design is common: each object/class has its responsibilities.
The cherryBomb represents a bomb, no more (and not the "graphic" aspect either).
The Environnement represents a "world" at current instant, and modelizes actions/interactions between the elements of the world.
There is a lot to say about the best way to design a game...
Anyway, to give your question an answer, you can still use "events". The bomb can send a message to your controller telling him: "I've exploded, remove me".
In the bomb:
[[NSNotificationCenter defaultCenter] postNotificationName:#"kaBOOM"
object:self];
In the controller:
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(methodToCallWhenKaBOOM)
name:#"kaBOOM"
object:nil];
And
- (void)methodToCallWhenKaBOOM:(NSNotification *)note
{
// do stuffs
}
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html
There are a number of ways to do this, and you may want to think about the exact division of labour here, in terms of controllers and models. Things can get pretty spaghettified if you have too much calling back and forth.
However, without getting into all of that, the basic thing you need to do to allow one object to access another object is to give the first a reference to the second.
In this case, you're actually creating the CherryBomb in the view controller, so it's easy to just pass it a reference at that point. Give your CherryBomb class another ivar like this:
SimpleGameViewController* cherryBombViewController;
Modify CherryBomb so that either the init method or your initializeCherryBomb one (these probably should just be a single method, btw) takes such a pointer and assigns it to the ivar:
- (void) initializeCherryBomb:(SimpleGameViewController*)vc
{
// ... whatever other stuff you do in here, plus something like:
cherryBombViewController = vc;
}
When you call this, pass it self as the vc parameter. Then later on, when your bomb detonates it can invoke some method you add on the controller to remove itself:
[cherryBombViewController handleDetonationOfCherryBomb:self];
Note that you absolutely should not access the controller's array directly -- that's an implementation detail your bomb should have no knowledge of. You can get away with being a little sloppy in your control structures in simple cases, but never screw with your encapsulation.
Read up on the MVC design pattern. If you find ivars which you need to share among views, they should probably go in a higher level Model object (the M of MVC), instead of having some view's peaking into other view's ivars. A pointer to this Model object can then be passed down to all the view objects that need to access it.
I think this is a good application of key value observing. You need a property of the cherry bomb which represents its state e.g.
#property (assign) BOOL isExploded;
Any object that is interested in whether the cherry bomb has exploded registers itself for KVO on the isExploded property. For example the view controller might do:
[cherryBomb addObserver: self
forKeyPath: #"isExploded"
options: ....
context: ....];
and in -observeValueForKeyPath:ofObject:change:context: for the view controller remove the cherry bomb from the array.
Your detonate method does the following as well as everything else it is currently doing:
[self setExploded: YES];
Related
A very basic question on how to interact between classes here: how can I trigger an action called by clicking on a button linked to one class (the graphic user interface in my case - which does not contain any drawing code) inside another class (my drawing class - which is defined programmatically)?
Thanks!
Edited: I have tried to implement the solutions suggested below but I didn't manage to trigger the action from the other class. I have two classes: the main view controller and a class with the drawing code. Any advice would be highly appreciated. Thanks!
//MainViewController.m
//This class has a xib and contains the graphic user interface
- (void)ImageHasChanged
{
//do something on the GUI
}
//DrawView.m
//This class has no associated xib and contains the drawing code
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
//I want to call ImageHasChanged from MainViewController.m here
//How can I do this?
}
Inter-class functionality is done simply by importing one class into the other, and calling an accessible method/instance variable on the import.
For the button IBAction example in your question:
ClassA.m (This will be imported via its header):
#import "ClassA.h"
#implementation ClassA
// This is a class-level function (indicated by the '+'). It can't contain
// any instance variables of ClassA though!
+(void)publicDrawingFunction:(NSString *)aVariable {
// Your method here...
}
// This is a instance-level function (indicated by the '-'). It can contain
// instance variables of ClassA, but it requires you to create an instance
// of ClassA in ClassB before you can use the function!
-(NSString *)privateDrawingFunction:(NSString *)aVariable {
// Your method here...
}
#end
ClassB.m (This is your UI class that will call the other method):
#import "ClassA.h" // <---- THE IMPORTANT HEADER IMPORT!
#implementation ClassB
// The IBAction for handling a button click
-(IBAction)clickDrawButton:(id)sender {
// Calling the class method is simple:
[ClassA publicDrawingFunction:#"string to pass to function"];
// Calling the instance method requires a class instance to be created first:
ClassA *instanceOfClassA = [[ClassA alloc]init];
NSString *result = [instanceOfClassA privateDrawingFunction:#"stringToPassAlong"];
// If you no longer require the ClassA instance in this scope, release it (if not using ARC)!
[instanceOfClassA release];
}
#end
Side note: If you're going to require ClassA a lot in ClassB, consider creating a class-wide instance of it in ClassB to re-use wherever it's required. Just don't forget to release it in dealloc (or maybe set it to nil in ARC) when you're finished with it!
Finally, please consider reading through the Apple Docs on Objective-C classes (and indeed all other sections of the documentation relevant to what you're trying to achieve). It is a bit time-consuming, but very well invested in the long run into building your confidence as an Objective-C programmer!
//As you said an instance of MainViewController has to be created first
MainViewController *instanceOfMainViewController = [[MainViewController alloc]init];
[instanceOfMainViewController ImageHasChanged];
//Thanks for your help Andeh!
Actually you can use #protocol(Delegate) to interact message between two classes this is standard way Or refer this document
http://developer.apple.com/library/ios/#documentation/General/Conceptual/CocoaEncyclopedia/DelegatesandDataSources/DelegatesandDataSources.html to learn more
I am trying to change the class of objects created with a nib with the iPhone SDK.
The reason for this is; i dont know until runtime what the class is that i want the nib object to be (though they will have the same UIView based super class), and i dont want to create a different nib for every eventuality - as the .nib will be the same for each, apart from the class of one object.
I have been successful, with a couple of methods, but either have some knock on effects or am unsure of how safe the methods I have used are:
Method 1: Override alloc, on the super class and set a c variable to the class I require:
+ (id) alloc {
if (theClassIWant) {
id object = [theClassIWant allocWithZone:NSDefaultMallocZone()];
theClassIWant = nil;
return object;
}
return [BaseClass allocWithZone:NSDefaultMallocZone()];
}
this works well, and i assume is 'reasonably' safe, though if have a nib with the correct class as the class identity in the Nib, or I alloc a subclass myself (without setting 'theClassIWant') - an object of the base class is created. I also dont really like the idea of overriding alloc...
Method 2: use object_setClass(self,theClassIWant) in initWithCoder (before calling initWithCoder on the super class):
- (id) initWithCoder:(NSCoder *)aDecoder {
if (theClassIWant) {
// the framework doesn't like this:
//[self release];
//self = [theClassIWant alloc];
// whoa now!
object_setClass(self,theClassIWant);
theClassIWant = nil;
return [self initWithCoder:aDecoder];
}
if (self = [super initWithCoder:aDecoder]) {
...
this also works well, but not all the subclasses are necessarily going to be the same size as the super class, so this could be very unsafe! To combat this i tried releasing and re-allocing to the correct type within initWithCoder, but i got the following error from the framework:
"This coder requires that replaced objects be returned from initWithCoder:"
dont quite get what this means! i am replacing an object in initWithCoder...
Any comments on the validity of these methods, or suggestions of improvements or alternatives welcome!
While I'm curious to see if you can pull this off using your approach, you may want to consider using custom placeholder objects.
I want to change an image on a view, from a popup dialog of 4-6 icons (imagine like changing your image on a messenger application).
The way I implement this modal popup is by creating a new view at IB, with opacity on the background, and then I load this as a subview:
IconsViewController *iconsViewController = [[IconsViewController alloc] initWithNibName:#"IconsView" bundle:nil];
[self.view addSubview:iconsViewController.view];
So, when the user touches an icon, I have
- (IBAction)iconIsSelected:(id)sender {
switch ([sender tag]) {
case 1:
[(ParentViewController*)[self superview] changeIcon];
break;
case 2:
// same here..
break;
default:
break;
}
[self.view removeFromSuperview];
[self release];
}
The changeIcon just sets the image to a corresponding icon.
As you can guess, this is not working - the changeIcon message never works.
I can't understand what am I doing wrong, any help much appreciated!
You have a few choices here...
First one is create a property on your IconsViewController of type ParentViewController*, for example:
#property (readwrite,nonatomic,assign) ParentViewController* parentController; // weak reference
To break this down further:
readwrite because we want to be able to access the value via [self parentController] but also change it via [iconsViewController setParentController:self]
nonatomic because I'm not too worried about threading
assign to make it a "weak reference" where the parent will not be retained by the child. If they each retain the other, it could lead to memory leaks later because unless explicitly released you'd end up with a retain circle causing neither object to hit a zero retain count.
When you load from nib, set the property:
IconsViewController *iconsViewController = [[IconsViewController alloc] initWithNibName:#"IconsView" bundle:nil];
iconsViewController.parentController = self;
Then, call to it from inside of iconIsSelected like this:
[[self parentController] changeIcon];
Alternatively, you can create a delegate protocol:
#protocol IconViewSelectedDelegate (NSObject)
- (void) changeIcon;
#end
And use that protocol as a property, instead of the parent view controller type. This is more abstract, but it keeps the design cleaner. The parent view controller would then implement that delegate protocol, as one of many others.
Another option is to use NSNotificationCenter and publish/subscribe to events from your dynamic view. This is the "loosest" coupling between the two objects, but it might be overkill for this scenario.
The superview of a view is a view, not a view controller, yet you cast the superview to be of class ParentViewController. If the view has no superview, it returns nil, and message to nil are no-ops (which explains why you don't crash there).
BTW, that [self release] at the end is highly suspicious.
I have a problem that I solved using delegates, but now I am thinking I may have made a mistake.
This is what I want to do.
I have a class that runs on a delay. When it is done it has a finished delegate that it calls.
Now I have the main class that creates two of these delay classes.
I don't want them to both be handled by the same isfinished method in the main class. I want to use two different ones.
However I believe with the protocol method of creating delegates that this will not work for me.
Is there a way around this?
delayclass setdelegates MainclassFunction1
delayclass setdelegates MainclassFunction2
If I understand you correctly, take a look at the NSTableViewDelegate protocol. There, each delegate method's first argument is the NSTableView instance sending the message.
You can solve your issue by changing your delegate methods to have your delegating object send itself as an argument. Then, in your delegate, you'd do something like this:
if (theDelegator == objectA)
{
// Do something
}
if (theDelegator == objectB)
{
// Do something else
}
This way, you've got one cleanly-implemented delegate method that can handle multiple objects delegating to it.
Using delegates doesn't seem like the correct approach to me; they're generally used for augmenting behavior. What sounds most appropriate here is the target/selector pattern, like NSTimer.
#interface MyObject : NSObject {
#private
id target;
SEL selector;
}
#property(assign) id target;
#property SEL selector; /* The selector must return void and accept one argument, which is the MyObject instance that invoked the method. */
#end
#implementation MyObject
- (void)notifyTarget {
[[self target] performSelector:[self selector] withObject:self];
}
#synthesize target;
#synthesize selector;
#end
This is generally the cleanest approach since the delegate callback doesn't need to disambiguate the sender. Using notifications seems like too much overhead for a problem in this domain.
As mentioned, commonly delegate methods would include the object initiating the callback, so you can differentiate that way. Alternately you could have the object post a notification instead, which will also make the originator available.
Why are you not just using NSTimer, adding different timers and having them call whatever selectors you like in the class you are using as a delegate now?
Something like:
NSTimer *timer1 = [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:#selector(myMethod1:) userInfo:nil repeats:YES];
NSTimer *timer2 = [NSTimer scheduledTimerWithTimeInterval:0.5f target:self selector:#selector(myMethod2:) userInfo:nil repeats:YES];
Where your methods are:
- (void) myMethod1:(NSTimer*)theTimer
{
// do Something
}
- (void) myMethod2:(NSTimer*)theTimer
{
// do Something different
}
You want to save off and retain both timer1/timer2 references, so that you can stop the timers in dealloc ([timer1 invalidate]).
Short note: Generally, it's bad style to have "if" statements that switch on an object. We all do it occasionally for getting that second list w/o needing a new controller, but switching is what method calls do internally, so ideally you'd just let the ObjC runtime take care of doing the right thing. Several options:
-(void) tableViewSelectionDidChange: (NSTableView*)theView
{
SEL theAction = NSSelectorFromString( [NSString stringWithFormat: #"tableView%#SelectionDidChange:", [theView autosaveName]] );
[self performSelector: theAction withObject: theView];
}
-(void) tableViewUKSourceListSelectionDidChange: (NSTableView*)theView
{
// UKSourceList-table-specific stuff here.
}
-(void) tableViewUKUsersListSelectionDidChange: (NSTableView*)theView
{
// UKUsersList-table-specific stuff here.
}
This works best when you have a non-localized string label, like the autoSave name, but can also use the tag, although that makes the code less readable (which one is "table 1"?). Sometimes it's better to just write a subclass that has a special string for that purpose, or even has methods where you can specify selector names to forward the delegate methods to.
Caleb's suggestion is also good, it's also called "target/action" in case you want to google for it. I have several (Mac) classes that have a regular "action" for clicks, a "doubleAction" for double clicks etc.
I'm doing the following:
- (void) accelerometer: (UIAccelerometer *)accelerometer didAccelerate: (UIAcceleration *)acceleration {
if (self.lastAcceleration) {
double i = self.lastAcceleration.x;
It works fine until I actually tilt the phone. Then I get EXC_BAD_ACCESS on the last line. lastAcceleration is a property with a retain. When I look at "x" in the debugger, it has a large negative value. Why would that throw a EXC_BAD_ACCESS exception only on tilt?
-- EDIT (Since this answer applies to responses below) --
I added this and now it works:
- (void)dealloc {
[lastAcceleration release];
Why would that matter? Also, should it be
[self.lastAcceleration release];
I wasn't previously releasing lastAcceleration anywhere. Here is the header declaration:
#interface MyViewController : UIViewController <UIAccelerometerDelegate> {
UIAcceleration *lastAcceleration;
}
#property(nonatomic, retain) UIAcceleration *lastAcceleration;
#end
My hunch is that the accelerometer API has nothing to do with the crash, the code you have shown smells like bad memory management, given that you're mixing ivar and property access I suspect you might be doing the same in other parts you're not showing.
Anyway a couple best practice things:
any object you have a pointer for in your class you should have retained, and conversely when you release it you should also zap the pointer so you don't risk accessing it after it has been deallocated (the exception to this rule are some patterns like the delegate object, where retaining the object would cause a retain cycle, but that's a whole other topic)
ivar setters and getters that are automatically generated via the #synthesized directive will retain and release the object for you for code that simply looks like it's assigning a pointer, so they're pretty handy, but property access (self.something = ...) and ivar access (something = ...) are not equivalent so you have to be careful
One easy way to make sure you don't mix the two up is to do something like this:
#interface MyObject : NSObject
{
SomethingObject *_something;
}
#property (nonatomic, retain) SomethingObject *something;
#end
#implementation MyObject
#synthesize something = _something;
#end
What we're doing here is making the ivar and property names slightly different, so that you are more aware of which one you're using, and the compiler will bark if you use don't use the bare something = ... syntax.
Now the #synthesize'd accessors are something like this:
- (void)setSomething:(SomethingObject *)newSomething
{
[newSomething retain];
[_something release];
_something = newSomething;
}
- (SomethingObject *)something
{
return _something;
}
With all that out of the way, [lastAcceleration release] is a bad thing to do because it isn't also setting the lastAcceleration pointer to nil, you are not guaranteed that it won't be deallocated and if you accidentally use it you are likely to crash.
[self.lastAcceleration release]; is incorrect because accessors take care of all the retain/release stuff for you.
The correct thing to do here is self.lastAcceleration = nil; that, if you look at the accessor code, will release and set the pointer to nil.
What is likely happening is that you are releasing lastAcceleration somewhere without also setting it to nil, and the if (self.lastAcceleration) { check is hitting a released object.
Main reason to have retained properties is to avoid explicit retain/release calls and memory management bugs associated with them. But in dealloc method either way is fine, since object will cease to exist soon.
[self.lastAcceleration release]; - not necessary.
[lastAcceleration release]; self.lastAcceleration = nil;
Both are fine if used in dealloc.
Outside of dealloc use only
self.lastAcceleration = nil;
EXC_BAD_ACCESS is raised when you access released memory. My guess would be that you somewhere released self.lastAcceleration but didn't set it to null.
Are you sure it is related to tilting?