OCUnit & NSBundle - unit-testing

I created OCUnit test in concordance with "iPhone Development Guide". Here is the class I want to test:
// myClass.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#interface myClass : NSObject {
UIImage *image;
}
#property (readonly) UIImage *image;
- (id)initWithIndex:(NSUInteger)aIndex;
#end
// myClass.m
#import "myClass.m"
#implementation myClass
#synthesize image;
- (id)init {
return [self initWithIndex:0];
}
- (id)initWithIndex:(NSUInteger)aIndex {
if ((self = [super init])) {
NSString *name = [[NSString alloc] initWithFormat:#"image_%i", aIndex];
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:#"png"];
image = [[UIImage alloc] initWithContentsOfFile:path];
if (nil == image) {
#throw [NSException exceptionWithName:#"imageNotFound"
reason:[NSString stringWithFormat:#"Image (%#) with path \"%#\" for current index (%i) wasn't found.",
[name autorelease], path, aIndex]
userInfo:nil];
}
[name release];
}
return self;
}
- (void)dealloc {
[image release];
[super dealloc];
}
#end
And my unit-test (LogicTests target):
// myLogic.m
#import <SenTestingKit/SenTestingKit.h>
#import <UIKit/UIKit.h>
#import "myClass.h"
#interface myLogic : SenTestCase {
}
- (void)testTemp;
#end
#implementation myLogic
- (void)testTemp {
STAssertNoThrow([[myClass alloc] initWithIndex:0], "myClass initialization error");
}
#end
All necessary frameworks, "myClass.m" and images added to target. But on build I have an error:
[[myClass alloc] initWithIndex:0] raised Image (image_0) with path \"(null)\" for current index (0) wasn't found.. myClass initialization error
This code (initialization) works fine in application itself (main target) and later displays correct image. I've also checked my project folder (build/Debug-iphonesimulator/LogicTests.octest/) - there are LogicTests, Info.plist and necessary image files (image_0.png is one of them).
What's wrong?

Found only one solution for this problem.
When I build my unit-tests, the main bundle's path is not equal to my project's bundle (created .app file). Moreover, it's not equal to LogicTests bundle (created LogicTests.octest file).
Main bundle for unit-tests is something like /Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator3.1.3.sdk/Developer/usr/bin. And that's why program can't find necessary resources.
And the final solution is to get direct bundles:
NSString *path = [[NSBundle bundleForClass:[myClass class]] pathForResource:name ofType:#"png"];
instead of
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:#"png"];

Related

Subclassed CCSpriteBatchNode object is nil

I subclassed CCSpriteBatchNode to make an object that conforms to NSCoding. I was mainly interested in the string name of the CCSpriteBatchNode. After setting break points I realized that the object's string name is always nil. I have a feeling it that my overridden methods might be a contributing factor but I'm not really sure about that. Please see relevant code below:
SpriteBatchNode interface:
#interface SpriteBatchNode: CCSpriteBatchNode {
NSString* batchImageName;
}
SpriteBatchNode implementation:
const NSUInteger defCapacity = 29;
#implementation SpriteBatchNode
#synthesize batchImageName;
+(id)batchNodeWithFile:(NSString*) imageFile
{
return [[self alloc] initWithFile:imageFile capacity:defCapacity];
}
-(id)initWithFile:(NSString *)fileImage {
self = [super initWithFile:fileImage capacity:defCapacity];
if (!self) return nil;
batchImageName = fileImage;
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
NSString* spriteBatchNodeFileImage = [[aDecoder decodeObjectForKey:#"batchImageName"] copy];
self = [super initWithFile:spriteBatchNodeFileImage capacity:defCapacity];
if (!self) return nil;
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:batchImageName forKey:#"batchImageName"];
}
#end
If you aren't using ARC I see two problems here:
batchImageName string is not retained
batchNodeWithFile: is not sending autorelease to the returned instance
Other than that you're using an unusual init style, the common style is this:
if (self)
{
batchImageName = fileImage;
}
return self;
Checking self for nil, then returning nil if it is, is somewhat redundant.

Get notification's from NSNotificationServer in C++ program

I'm creating a simple C++ application with cmake on Mac.
There is a main in C++ source code file, that create C++ class. Inside this class I'm allocating objective C object, that adding itself to observers in NSNotificationCenter. And I'm not receiving those notifications.
There is a code:
Notifications.h
class LaunchNotification {
public:
LaunchNotification();
virtual ~LaunchNotification();
void StartNotifications();
void StopNotifications();
private:
void *monitor;
};
Notifications.mm
#interface Monitor : NSObject
-(id) init;
-(void) appLaunchedNotification :(NSNotification *) notification;
-(void) appTerminatedNotification :(NSNotification *) notification;
#end
#implementation Monitor
- (id) init
{
self = [super init];
if (self)
{
count = 0;
NSNotificationCenter *notCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
[notCenter addObserver : self
selector:#selector(appLaunchedNotification:)
name:NSWorkspaceDidLaunchApplicationNotification
object:nil];
[notCenter addObserver : self
selector:#selector(appTerminatedNotification:)
name:NSWorkspaceDidTerminateApplicationNotification
object:nil];
}
return self;
}
- (void) appLaunchedNotification : (NSNotification *) notification
{
NSString *path = [[notification userInfo]objectForKey: #"NSApplicationPath"];
}
- (void) appTerminatedNotification : (NSNotification *) notification
{
NSString *path = [[notification userInfo]objectForKey: #"NSApplicationPath"];
}
- (void) dealloc
{
NSNotificationCenter *notCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
[notCenter removeObserver : self];
[super dealloc];
}
#end
LaunchNotification::LaunchNotification() : monitor(NULL)
{}
LaunchNotification::~LaunchNotification()
{
StopNotifications();
}
void LaunchNotification::StartNotifications()
{
if (NULL == monitor)
{
monitor = [[Monitor alloc] init];
}
}
void LaunchNotification::StopNotifications()
{
if (NULL != monitor)
{
[(id)monitor release];
}
}
You need a run loop because otherwise, NSWorkspace has no mechanism to gain control of your application's thread in order to post notifications.
While the docs say run loops are automatically created, they are not automatically executed. Think about it: how can a thread be simultaneously running your code and running the code in the run loop?
Any tasks that you need to do while you are monitoring the notification centre need to be done in the context of runloop events e.g. on an NSTimer event, or you need a separate thread for that other stuff.

body null when accessed from another method

I have a contact listener that handles contact between two box2d bodies. I am accessing the bodies from the Contacter in the HelloWorldLayer since box2d recommends that contacting bodies should be saved and changes implemented after the timestep. Please see the code below:
Contacter.h:
#import "CCPhysicsSprite.h"
#interface Contacter : CCPhysicsSprite {
}
#property(nonatomic, assign) NSMutableArray* arrayOfBodies;
#property(nonatomic, assign) CCPhysicsSprite* spriteToDestroy;
-(void)physicsSpritesContact:(CCPhysicsSprite*)onePhysicsSprite otherSprite:(CCPhysicsSprite*)twoPhysicsSprite;
#end
Contacter.mm:
#import "Contacter.h"
#import "Box2D.h"
#implementation Contacter
#synthesize arrayOfBodies = _arrayOfBodies;
#synthesize spriteToDestroy = _spriteToDestroy;
-(void)destroyBodies:(b2Body*)body {
_arrayOfBodies = [[NSMutableArray alloc] init];
NSValue *bodyValue = [NSValue valueWithPointer:body];
[_arrayOfBodies addObject:bodyValue];
}
-(void)physicsSpritesContact:(CCPhysicsSprite*)onePhysicsSprite otherSprite: (CCPhysicsSprite*)twoPhysicsSprite; {
int firstTag = onePhysicsSprite.tag;
int secondTag = twoPhysicsSprite.tag;
if (((firstTag == 90) && (secondTag == 101 )) || ((firstTag == 101) && (secondTag == 90))) {
if (tag1 == 90) {
[self destroyBodies:onePhysicsSprite.b2Body];// adds body to array to be destroyed
spriteToDestroy = onePhysicsSprite; // taking note of sprite to be destroyed
}
else if (tag2 == 90) {
[self destroyBodies:twoPhysicsSprite.b2Body];
spriteToDestroy = twoPhysicsSprite;
}
}
}
The following method within HelloWorldLayer.mm is called in the update method:
-(void)removeDestroyedBodiesAndSprites {
bodyContact = [Contacter node];
if ([bodyContact arrayOfBodies]) {
for (NSValue* bodyValue in [bodyContact arrayOfBodies]) {
b2Body *removeBody;
removeBody = (b2Body*)[bodyValue pointerValue];
world->DestroyBody(removeBody);
removeBody = NULL;
[self removeChild:[bodyContact spriteToDestroy]];
}
}
}
There is contact but the sprite is not removed and body is not destroyed in removeDestroyedBodiesAndSprites. After testing with a CCLOG I found that the for loop was not satisfied meaning that the arrayOfBodies could be null. Which is surprising since the contact was established. I would appreciate your assistance.
UPDATED
Below is the contact listener:
TestContactListener.h:
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#import "Box2D.h"
#import "GameObjects.h"
#import "Contacter.h"
class TestContactListener : public b2ContactListener {
public:
Contacter* contacter;
void BeginContact(b2Contact* contact);
};
TestContactListener.mm:
#import "TestContactListener.h"
void TestContactListener:: BeginContact(b2Contact *contact)
{
contacter = [Contacter node];
b2Fixture *fixtureA = contact->GetFixtureA();
b2Fixture *fixtureB = contact->GetFixtureB();
b2Body *fixtureABody = fixtureA->GetBody();
b2Body *fixtureBBody = fixtureB->GetBody();
CCPhysicsSprite* physicsSprite = (CCPhysicsSprite*)fixtureABody->GetUserData();
CCPhysicsSprite* physicsSprite2 = (CCPhysicsSprite*)fixtureBBody->GetUserData();
[contacter physicsSpritesContact:physicsSprite otherSprite:physicsSprite2];
}
Move this from the destroyBodies method to an init method, so it is only called once:
_arrayOfBodies = [[NSMutableArray alloc] init];
Take a good look at your destroyBodies method. You create a new array, replacing any existing array, every time you call it. Therefore you will only destroy the last body you passed in to that method.
If you are not using ARC you'll also leak all the replaced arrays.

cocos2d: Subclassing CCMenuItem triggers a lot of "removeChildByTag: child not found!"

I am trying to subclass a CCMenuItem using the following code:
GenericButton.h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface GenericButton : CCMenuItemSprite {
}
+(id) itemwithTitle:(NSString*)title withBGColor: (ccColor3B) bgColor andFGColor:(ccColor3B)fgColor;
#end
GenericButton.m
#import "GenericButton.h"
#import "HelpfulClasses.h"
#implementation GenericButton
+(id) itemwithTitle:(NSString*)title withBGColor: (ccColor3B) bgColor andFGColor:(ccColor3B)fgColor{
CCSprite*genericButtonBG = [CCSprite spriteWithSpriteFrameName:#"genericButtonBG.png"];
genericButtonBG.color=bgColor;
CCSprite*genericButtonBGPressed = [CCSprite spriteWithSpriteFrameName:#"genericButtonBGPressed.png"];
genericButtonBGPressed.color=bgColor;
CCMenuItemSprite*button = [CCMenuItemSprite itemWithNormalSprite:genericButtonBG selectedSprite:genericButtonBGPressed];
CCSprite*fgButton = [CCSprite spriteWithSpriteFrameName:#"genericButton.png"];
fgButton.color=fgColor;
[button addNodeInMiddleOfParent:fgButton];
CCLabelBMFont *buttonTitle = [CCLabelBMFont labelWithString:title fntFile:#"font.fnt"];
if ([title length]>7) {
buttonTitle.scale=0.85;
}
buttonTitle.color=ccYELLOW;
[fgButton addNodeInMiddleOfParent:buttonTitle];
return button;
}
// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
// in case you have something to dealloc, do it in this method
// in this particular example nothing needs to be released.
// cocos2d will automatically release all the children (Label)
// don't forget to call "super dealloc"
[super dealloc];
}
#end
But whenever I am using GenericButton*button = [GenericButton item....], in a CCScene, there is a lot of"removeChildByTag: child not found!" showing on the console. Am I doing something wrong?
Cheers
After two months you've probably figured this out yourself. Doesn't this website have a way to PM somebody? So sorry if I resurrect and old thread.
You are not including all your code for this class. However, I can point out something I saw that is an issue and could possibly be the source of your problem. In your class method you are creating and returning a pointer to "CCMenuItemSprite" called "button". This should be a pointer to your class "GenericButton".

xcode array of Arrays and UITableViews

I am new to Xcode and Objective-C programming and need some help.
I am looking to create a basic program for the iOS that uses hierarchal-data and 2 separate UITableViews. I want the second UITableView to be populated by an array that is passed between viewControllers, based on which cell/row is selected in the first UITableView.
The program compiles but I get a SIGABRT error when running the program. Can someone help me fix the SIGABRT and pass the mainArray to the second tableView?
Here is how far I have gotten.
My code:
ArrayTableViewController.h
#interface arrayTableViewController : UITableViewController
#property (nonatomic, strong) NSMutableArray *mainArray;
#property (nonatomic, strong) NSMutableArray *secondArray;
#property (nonatomic, strong) NSMutableArray *thirdArray;
#end
ArrayTableViewController.m
#import "ArrayTableViewController.h"
#import "table2.h"
#implementation arrayTableViewController
#synthesize mainArray, secondArray, thirdArray;
-(void) viewDidLoad {
[super viewDidLoad];
mainArray = [[NSMutableArray alloc] initWithObjects: secondArray, thirdArray, nil];
secondArray = [[NSMutableArray alloc] initWithObjects: #"123", #"456", nil];
thirdArray = [[NSMutableArray alloc] initWithObjects: #"78", #"90", nil];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [mainArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = [mainArray objectAtIndex:[indexPath row]];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
table2 *table2Controller = [[table2 alloc] initWithNibName:#"table2" bundle:nil];
table2Controller.arrayForDisplay = [[mainArray objectAtIndex: [indexPath row]] objectAtIndex:1];
[self.navigationController pushViewController:table2Controller animated:YES];
}
#end
table2.h
#import <UIKit/UIKit.h>
#interface table2 : UITableViewController
#property (nonatomic, strong) NSArray *arrayForDisplay;
#end
table2.m
#implementation table3
#synthesize arrayForDisplay;
Then the same cell configuration style that was used in ArrayTableViewController.m
Edits:
After making the necessary changes, when I run the program and select a row, I get a SIGABRT error at the following line.
int main(int argc, char *argv[])
{
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([ArrayTableAppDelegate class]));
}
}
What would you recommend? Should I turn off ARC and call my own releases? so that I can get to the second tableView?
First mistake:
In viewDidLoad method, you have created the mainArray with secondArray and thirdArray as elements even before you allocated those arrays.
Second mistake:
In cellForRowAtIndexPath: method:
Check the line cell.textLabel.text = [mainArray objectAtIndex:[indexPath row]];
Actually the textLabel expects a NSString value to set. But you are setting an array.
Edit:
Set the value to textLabel as following:
cell.textLabel.text = [[mainArray objectAtIndex:[indexPath row]] objectAtIndex:0];
Actually this will set the first value of the array. But that it depends on your requirement.
Edit 2:
arrayForDisplay is an array variable but you are setting a string variable to that in the statement
table2Controller.arrayForDisplay = [[mainArray objectAtIndex: [indexPath row]] objectAtIndex:1];
Do it as follows:
table2Controller.arrayForDisplay = [mainArray objectAtIndex: [indexPath row]];