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
Related
I want to store the continuously updated values returned by the locationManager:didUpdateHeading: method in either a global int or a property int, so that other functions of the MotionHandler class can use it. However, this delegate method doesn't seem to be able to store its values globally but only locally. Why is that? Is it because it's not an actual MotionHandler method? How can I work around this problem? Thank you for your help.
MotionHandler.m
#import "MotionHandler.h"
#interface MotionHandler()
{
CLLocationManager *locationManager;
int degrees; // the global in question..
}
#end
#implementation MotionHandler
-(void) startCompassUpdates
{
locationManager =[[CLLocationManager alloc] init];
locationManager.delegate=self;
[locationManager startUpdatingHeading];
}
-(void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading
{
// This is working, a new value is stored in "degrees" & logged on the console after each update. However it only seems to be updating "degrees" locally..
degrees = (int)locationManager.heading.magneticHeading;
NSLog(#"from delegate method: %i", degrees);
}
-(int) showDegrees
{
return degrees; // This is not working. Whenever I call this method, "degrees" is always zero. Why isn't this global being updated by the previous method ?
}
TheViewController.m
//...
- (void)viewDidLoad
{
[super viewDidLoad];
currentMotionHandler = [[MotionHandler alloc] init];
[currentMotionHandler startCompassUpdates];
while(1==1)
{
NSLog(#"from showDegrees method: %i",[currentMotionHandler showDegrees]); // this just keeps returning zero..
}
}
//...
As per OP request, I have transferred my comments to an answer:
You need to stop using while loop to get constant feedback of the changing value. As Cocoa Touch is an event-based system you can't hijack its run loop by creating an infinite loop in this way. Even outside of an event-based system, using such a tight loop would hurt performance and give little gain.
If you want continuous update (or something that appears to be continuous) you can:
Use a timer to call a method every X milliseconds (see Apple Guide).
Use a background thread (see Apple Guide).
I would prefer to use the timer approach as that has the lowest overhead and runs the method in the same thread as the rest of the UI, avoiding any possible threading issue.
in my project I'm managing several Drawing objects. I'm trying to add a copy of a SmartPath object to a drawing object. This works. But when the drawing gets deallocated the SmartPath does not. I have put some extra code in the dealloc of the Drawing to explicitly set clear the pointer to the SmartPath. For some reason this works (the retain count was 1). I know I can probably copy the SmartPath and assign that to a strong parameter to fix this leak. But I'm relatively new to IOS and want to know how to use the copy parameters properly in combination with ARC.
Here is the code:
Drawing.h:
#interface Drawing : NSObject{
#private
SmartPath* rawLinePath; //path that is build up from straight lines between input points
SmartPath* smoothLinePath; //smoothened version of rawLinePath
}
#property(atomic,copy)SmartPath* rawLinePath;
#property(atomic,copy)SmartPath* smoothLinePath;
Drawing.m
#implementation Drawing
#synthesize rawLinePath;
#synthesize smoothLinePath;
-(id)init
{
if (self = [super init])
{
[NSThread detachNewThreadSelector:#selector(pointMonitor)
toTarget:self
withObject:nil];
}
return self;
}
-(void)dealloc{
rawLinePath=nil;
}
SmartPath.m
/*
* Init - set all variables in the correct state
*/
-(id)init
{
if (self = [super init])
{
visible=TRUE;
color = [UIColor redColor].CGColor;
width = SMARTPATH_LINE_WIDTH;
path = CGPathCreateMutable();
lock = [[NSLock alloc]init];
}
return self;
}
/*
* dealloc - clean up after self
*/
-(void)dealloc{
CGPathRelease(path);
}
/*
* copy method to be able to pass a SmartPath to a copy property
*/
-(id)copyWithZone:(NSZone *)zone{
SmartPath *pathCopy = [[SmartPath allocWithZone: zone] init];
pathCopy.visible =visible;
pathCopy.color = color;
pathCopy.width = width;
return pathCopy;
}
I hope any of you knows the answer to this problem.
Best regards
Your problem is your call to -detachNewThreadSelector:toTarget:withObject:. This retains target, which is self, and does not release it until pointMonitor exits. I suspect this never happens, so you've effectively created a retain loop.
You should almost never use -detachNewThreadSelector:toTarget:withObject:. It can create an unbounded number of threads. Instead, you should generally use dispatch queues, NSTimer, NSOperation or other async mechanisms. NSThread objects are generally only appropriate for long-lived producer/consumer threads (and usually those are still handled better with the newer tools like dispatch queues).
I'm not certain what pointMonitor does, but is there any reason it needs its own thread at all? You can do a lot of very good Cocoa development and never fork a thread. Could you use an NSTimer here? Note that most of these techniques retain their target until they fire (just like NSThread). If they didn't, you'd crash when they fired.
Without knowing what you're trying to do, I'm not certain which approach to recommend. You may want to put that together as a new question.
By not starting instance variable names with an underscore, you end up with code where you never know whether you are using an accessor method or an instance variable. As a result, you can never be sure whether a copy is made or not.
If you do that in other places, there's a good chance that a reference to your SmartPath object gets stuck somewhere. And what are you doing creating NSLock objects? Do you need to do anything that #synchronized can't do with much less code?
And if you use a newer Xcode version, get rid of all the instance variables and #synthesize statements. Just declare the properties.
And excuse me, but detaching a thread from an init method is just sick.
I am implementing an object reuse scheme using a singleton class.
What I do basically is:
MyClass* obj = [[MyClassBank sharedBank] getReusableItem];
The bank is just an NSMutableSet tweaked for optimum reusability. When I was happily implementing this Singleton, I had in mind that I will just do the following when I am done with "obj":
[[MyClassBank sharedBank] doneWithItem:obj];
Currently, My code would work if I where to use it this way, but I later realized that I sometimes add "obj" to an "NSCollection", and sometimes I call:
[theCollection removeAllObjects];
At first I thought about making my own class that is composed of a collection, then I would iterate the objects within the collection and call:
[[MyClassBank sharedBank] doneWithItem:obj];
But, that's too much of a hassle, isn't?
A neat idea (I think) popped into my mind, which is to override: -(oneway void)release;, so, I immediately jumped to Apple's documentation, but got stuck with the following:
You would only implement this method to define your own reference-counting scheme. Such implementations should not invoke the inherited method; that is, they should not include a release message to super.
Ao, I was reluctant to do that idea .. basically:
-(oneway void)release{
if ([self retainCount] == 1) {
//This will increment retain count by adding self to the collection.
[[MyClassBank sharedBank] doneWithItem:self];
}
[super release];
}
Is it safe to do that?
PS: Sorry for the long post, I want the whole idea to be clear..
EDIT:
How about overriding alloc alltogther and adding [[MyClassBank sharedBank] getReusableItem]; there?
Suggested method:
You're playing with the reference counting system. 99.9999999999999999% of the time this is a bad idea. I would highly recommend going with a different mechanism. Perhaps these objects could implement their own reference count that's independent of the retainCount? Then you could use that referenceCount to actually control when an object is ready to be re-used or not.
Not suggested method:
If, for some weird reason, you can't do that, then you could do the following thing that is still a bad idea and that i don't recommend you actually use:
You can override dealloc:
- (void)dealloc {
[ivar release], ivar = nil;
[anotherIvar release], anotherIvar = nil;
somePrimitive = 0;
// do not call [super dealloc]
}
- (void)_reallyDealloc {
[self dealloc]; // clean up any ivars declared at this level
[super dealloc]; // then continue on up the chain
}
Basically, the dealloc method would be the point at which the object is ready for re-use. When you're totally done with the object and finally want it to go away, you can use the _reallyDealloc method to continue on up the chain, eventually resulting in the object getting freed.
PLEASE don't do this. With things like Automatic Reference Counting, this is going to introduce you into a world of hurt and really bizarre debugging scenarios. A lot of the tools and classes and stuff depend on the reference counting mechanism to be working without alteration, so screwing around with it is usually not a Good Idea™.
For ppl who find this approach interesting/useful, Here is a cleaner way than calling [super dealloc]; directly (which is definitely bad)
//BAD!
//-(void)dealloc{
// for some reason, the retainCount at this point == 1
// if (![[BankStep sharedBank] purgeFlag]) {
// [self resetObject];
// [[BankStep sharedBank] doneWithItem:self];
// } else {
// [children release];
// [super dealloc];
// }
//}
by calling [[Bank sharedBank] purgeBank]; , set the flag to true, then remove all objects from the NSSet.
Adapted solution:
#Joe Osborn idea of using categories to implement a returnToBank Method!
UPDATE: I realized that the "initWithFrame" method is never called, so I placed my array's init elsewhere. Thanks for reading. (for anyone, what's the point of initWithFrame if it is not called?"
I've been staring at this code for about an hour and am probably missing a simple and obvious issue. I'm merely trying to keep an array of touched points. My UIView's init says this:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
self.drawn=[[NSMutableArray alloc] init ];
return self;
}
Then I have this in another method:
CGPoint point=[[touches anyObject] locationInView:self];
NSLog(#"touched point: %f, %f",point.x,point.y);
[self.drawn addObject:[NSValue valueWithCGPoint:point]];
The NSLog confirms that "point" exists and contains x and y data.
Why then does my "drawn" array never get anything? I have read the NSValue tutorials and I seem to be doing this correctly. An NSLog of [self.drawn count] always shows 0 despite that this code has triggered.
And of course "drawn" is also an ivar of my custom UIView, properly synthesized also.
Your initWithFrame: never gets called,* and drawn is never created. From the Resource Management Guide:
In iOS, any object that conforms to the NSCoding protocol is initialized using the initWithCoder: method. This includes all subclasses of UIView[...] Custom views in iOS do not use the initWithFrame: method for initialization.
The value at drawn is therefore nil, and calling [nil count] gives 0; [nil addObject:] similarly does nothing.
That said, your initWithFrame: also has two memory problems. Your assignment to the property over-retains the array, and you should be doing that assignment inside the if block.
When you create an NSMutableArray using alloc/init, you own that array. When you then assign it using a property which is defined as retaining, you have an extra claim on that object, which can eventually cause a leak.
Second: if, for whatever reason, the call to [super initWithFrame:frame] fails, self will be invalid (nil), and using one of its properties will likewise be incorrect. That's the purpose of if(self).
You should do this instead:
if(self){
self.drawn = [NSMutableArray array];
}
This creates an autoreleased NSMutableArray which your view then properly retains.
*I wish I knew why too. It does get used in OS X.
I would check if the
- (id)initWithFrame:(CGRect)frame
is called.
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.