NSMutableArray: add and extract struct - objective-c

I'm trying to store some data in an NSMutableArray. This is my struct:
typedef struct{
int time;
char name[15];
}person;
This is the code to add a person:
person h1;
h1.time = 108000;
strcpy(h1.name, "Anonymous");
[highscore insertObject:[NSValue value:&h1 withObjCType:#encode(person)] atIndex:0];
So, I try to extract in this way:
NSValue * value = [highscore objectAtIndex:0];
person p;
[value getValue:&p];
NSLog(#"%d", p.time);
The problem is that the final log doesn't show me 108000!
What is wrong?

Your code looks correct (and works for me), so I deduce that you aren't initializing highscore. So when you send the insertObject:atIndex: message to it, nothing happens. When you then send the objectAtIndex: method to it, you get nil back. When you send getValue: to the nil NSValue *value, it does nothing, so your person p is left filled with random stack garbage, which is why your NSLog doesn't print 108000.

As stated in my initial comment there rarely is a reason to do this kind of stuff with pure c structs. Instead go with real class objects:
If you're unfamiliar with the syntax below you may want to look at these quick tutorials on ObjC 2.0 as well as read Apple's documentation:
A Quick Objective-C 2.0 Tutorial
A Quick Objective-C 2.0 Tutorial: Part II
Person Class:
// "Person.h":
#interface Person : NSObject {}
#property (readwrite, strong, nonatomic) NSString *name;
#property (readwrite, assign, nonatomic) NSUInteger time;
#end
// "Person.m":
#implementation Person
#synthesize name = _name; // creates -(NSString *)name and -(void)setName:(NSString *)name
#synthesize time = _time; // creates -(NSUInteger)time and -(void)setTime:(NSUInteger)time
#end
Class use:
#import "Person.h"
//Store in highscore:
Person *person = [[Person alloc] init];
person.time = 108000; // equivalent to: [person setTime:108000];
person.name = #"Anonymous"; // equivalent to: [person setName:#"Anonymous"];
[highscore insertObject:person atIndex:0];
//Retreive from highscore:
Person *person = [highscore objectAtIndex:0]; // or in modern ObjC: highscore[0];
NSLog(#"%#: %lu", person.name, person.time);
// Result: "Anonymous: 108000"
To simplify debugging you may also want Person to implement the description method:
- (NSString *)description {
return [NSString stringWithFormat:#"<%# %p name:\"%#\" time:%lu>", [self class], self, self.name, self.time];
}
which will allow you to just do this for logging:
NSLog(#"%#", person);
// Result: "<Person 0x123456789 name:"Anonymous" time:108000>

Reimplement Person as an Objective-C object and reap the benefits:
Person.h:
#interface Person : NSObject
{
int _time;
NSString *_name;
}
#property (assign, nonatomic) int time;
#property (retain, nonatomic) NSString *name;
#end
Person.m:
#import "Person.h"
#interface Person
#synthesize time = _time;
#synthesize name = _name;
- (id)init
{
self = [super init];
if (self != nil)
{
// Add init here
}
return self;
}
- (void)dealloc
{
self.name = nil;
[super dealloc];
}
#end

Related

Objective-C: How to create object instance with constructor that references arrays

I'm trying to create an array of objects, but it's not working as expected. I have a Person class with a name property and I'm trying to instantiate Person objects with the names from another array like the code below. Instead of the names in the names array being used, the program outputs "(null)". So that means that it's not working as expected.
ViewController.m
names = [NSArray arrayWithObjects:#"Mike", #"John", #"Jimmy", #"Tim", nil];
personsArray = [[NSMutableArray alloc] initWithCapacity:4];
for (int i = 0; i <= 4; i++) {
Person *person = [[Person alloc] initWithName:[names objectAtIndex:i]];
NSLog(#"%#", [person name]); // outputs "(null)"
[personsArray addObject:person];
}
Person.m
#import "Person.h"
#implementation Person
-(id)initWithName:(NSString *)name {
if (self = [super init]) {
name = name;
}
return self;
}
#end
Person.h
#import <Foundation/Foundation.h>
#interface Person : NSObject
#property (nonatomic, strong) NSString *name;
-(id)initWithName:(NSString *)name;
#end
Please help!
This line in -[Person initWithName:] is your problem:
name = name;
You are just assigning the argument variable to itself. You need to assign the instance variable. You probably want to copy the input string, so:
_name = [name copy];

Objective C tyring to learn the correct way to pass method parameters to an object and get the correct result

I'm new to objective C and have been trying to teach myself through books and online tutorials.
I'm trying to write a simple class that has one method that puts a name together. However, when I try to pass values to it, I get a null result.
#import <Foundation/Foundation.h>
#interface TSMPerson : NSObject
#property NSString *first;
#property NSString *last;
#property NSString *middle;
#property NSString *fullName;
- (NSString *) stickNamesTogether: (NSString *) first : (NSString *) middle : (NSString *) last;
#end
The implementation is:
#import "TSMPerson.h"
#implementation TSMPerson
-(NSString *)stickNamesTogether: (NSString *)first : (NSString *) middle : (NSString *) last
{
self.fullName=[self.first stringByAppendingString:#" "];
self.fullName=[self.fullName stringByAppendingString:self.middle];
self.fullName=[self.fullName stringByAppendingString:#" "];
self.fullName=[self.fullName stringByAppendingString:self.last];
return self.fullName;
}
#end
The problem I'm having is that when I call this method from another class, I get a null result and I'm not sure what I'm doing wrong:
#import "TSMViewController.h"
#import "TSMPerson.h"
#interface TSMViewController ()
#property (weak, nonatomic) IBOutlet UILabel *viewText;
#property (weak, nonatomic) IBOutlet UITextField *first;
#property (weak, nonatomic) IBOutlet UITextField *middle;
#property (weak, nonatomic) IBOutlet UITextField *last;
#property (weak, nonatomic) IBOutlet UIButton *printName;
#end
#implementation TSMViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (IBAction)printName:(id)sender {
TSMPerson *person = [[TSMPerson alloc]init];
person.fullName=[person stickNamesTogether:self.first.text :self.middle.text :self.last.text];
self.viewText.text=person.fullName;
NSLog(#"fullname is %#",person.fullName);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
When I use this alternate code, it works:
/*
TSMPerson *person = [[TSMPerson alloc]init];
person.first=self.first.text;
person.middle=self.middle.text;
person.last=self.last.text;
person.fullName=[person stickNamesTogether:person.first :person.middle :person.last];
self.viewText.text=person.fullName;
*/
but that gives me the extra task of setting the instantiated properties of the person class to the properties in the viewcontroller. I'm not sure why I can't just pass those property values right into my method and get back the values I want.
I realize this there is probably some basic thing I'm missing, but I can't figure out what it is. Any help would be appreciated.
Thanks
It's because you are using the properties instead of the variables passed in.
You should use first, middle, last instead of self.first, self.middle, self.last.
Your commented out code works because you are setting those properties first.
I think this is what you want.
-(NSString *)stickNamesTogether: (NSString *)first : (NSString *) middle : (NSString *) last
{
self.fullName=[first stringByAppendingString:#" "];
self.fullName=[self.fullName stringByAppendingString:middle];
self.fullName=[self.fullName stringByAppendingString:#" "];
self.fullName=[self.fullName stringByAppendingString:last];
return self.fullName;
}
Unless you want to also set those properties too (which the above method will not do). Since you ARE setting the fullName property (self.fullName) and returning that.
In which case you want
-(NSString *)stickNamesTogether: (NSString *)first : (NSString *) middle : (NSString *) last
{
self.first = fist;
self.middle = middle;
self.last = last;
self.fullName=[first stringByAppendingString:#" "];
self.fullName=[self.fullName stringByAppendingString:middle];
self.fullName=[self.fullName stringByAppendingString:#" "];
self.fullName=[self.fullName stringByAppendingString:last];
return self.fullName;
}
You may reduce confusion by using a different name for the passed in variables.
-(NSString *)stickNamesTogether: (NSString *)passedFirst : (NSString *) passedMiddle : (NSString *) passedLast
{
self.first = passedFirst;
self.middle = passedMiddle;
self.last = passedLast;
self.fullName=[passedFirst stringByAppendingString:#" "];
self.fullName=[self.fullName stringByAppendingString:passedMiddle];
self.fullName=[self.fullName stringByAppendingString:#" "];
self.fullName=[self.fullName stringByAppendingString:passedLast];
return self.fullName;
}
One last suggestion not related to your problem. Typically you would use stringWithFormat instead for somethign like this
-(NSString *)stickNamesTogether: (NSString *)passedFirst : (NSString *) passedMiddle : (NSString *) passedLast
{
self.first = passedFirst;
self.middle = passedMiddle;
self.last = passedLast;
self.fullName=[NSString stringWithFormat:#"%# %# %#", passedFirst, passedMiddle, passedLast];
return self.fullName;
}
When you make this call:
self.fullName=[self.first stringByAppendingString:#" "];
self.first is nil, and when you pass a message to a nil object, you get nil in return.
It goes on from there, repeatedly setting self.fullName to nil.
What you should do is set the properties with the local vars that you've passed into the method first.
#ansible has some good suggestions as well.

this method crashes if I use a property and works fine if I declare the variable within the method--why?

I'm working on a programmable calculator, and for the life of me I can't understand what I'm doing wrong.
Here are the relevant parts of the code. (The code is unfinished, so I know there's extra stuff floating around.)
CalculatorViewController.m
#import "CalculatorViewController.h"
#import "CalculatorBrain.h"
#interface CalculatorViewController ()
#property (nonatomic) BOOL userIsEnteringNumber;
#property (nonatomic) BOOL numberIsNegative;
#property (nonatomic,strong) CalculatorBrain *brain;
#property (nonatomic) NSArray *arrayOfDictionaries;
#property (nonatomic) NSDictionary *dictionary;
#end
#implementation CalculatorViewController
#synthesize display = _display;
#synthesize history = _history;
#synthesize userIsEnteringNumber = _userIsEnteringNumber;
#synthesize numberIsNegative;
#synthesize brain = _brain;
#synthesize arrayOfDictionaries;
#synthesize dictionary;
-(CalculatorBrain *)brain
{
if (!_brain) _brain = [[CalculatorBrain alloc] init];
return _brain;
}
/*snip code for some other methods*/
- (IBAction)variablePressed:(UIButton *)sender
{
NSString *var = sender.currentTitle;
NSDictionary *dict = [self.dictionary initWithObjectsAndKeys:[NSNumber numberWithDouble:3],#"x",[NSNumber numberWithDouble:4.1],#"y",[NSNumber numberWithDouble:-6],#"z",[NSNumber numberWithDouble:8.7263],#"foo",nil];
[self.brain convertVariable:var usingDictionary:dict];
self.display.text = [NSString stringWithFormat:#"%#",var];
self.history.text = [self.history.text stringByAppendingString:sender.currentTitle];
[self.brain pushOperand:[dict objectForKey:var] withDictionary:dict];
}
#end
And here's CalculatorBrain.m.
#import "CalculatorBrain.h"
#interface CalculatorBrain ()
#property (nonatomic, strong) NSMutableArray *operandStack;
#end
#implementation CalculatorBrain
#synthesize operandStack = _operandStack;
-(void)pushOperand:(id)operand withDictionary:(NSDictionary *)dictionary
{
NSNumber *operandAsObject;
if (![operand isKindOfClass:[NSString class]])
{
operandAsObject = operand;
}
else
{
operandAsObject = [dictionary objectForKey:operand];
}
[self.operandStack addObject:operandAsObject];
}
-(double)popOperand
{
NSNumber *operandAsObject = [self.operandStack lastObject];
if (operandAsObject) [self.operandStack removeLastObject];
return [operandAsObject doubleValue];
}
-(double)convertVariable:(NSString *)variable usingDictionary:dictionary
{
double convertedNumber = [[dictionary objectForKey:variable] doubleValue];
return convertedNumber;
}
#end
The thing I'm having trouble understanding is in the CalculatorViewController.m method - (IBAction)variablePressed:(UIButton *)sender. This line crashes the program:
NSDictionary *dict = [self.dictionary initWithObjectsAndKeys:[list of objects and keys]];
But if I make it
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:[list of objects and keys]];
then everything works fine. But if I try to do
NSDictionary *dict = [[self.dictionary alloc] initWithObjectsAndKeys:[list of objects and keys]];
which seems to me the right thing to do, then XCode won't let me, so I'm obviously not understanding something.
Any thoughts?
+alloc allocates memory for an object. -init... methods initialize the object.
[self.dictionary initWithObjectsAndKeys:... calls -dictionary which is either going to return a dictionary set in that property or nil and then attempts to call init... on it. If the dictionary exists then you are attempting to initialize an object more than once which is not valid. If the property has not been set then the getter will return nil and sending an init... message to nil will do nothing. Either way this is not what you want to do.
[[self.dictionary alloc] init... is also invalid, as the compiler warns you. Now you try to obtain an object from -dictionary and then call the class method +alloc on it.
There seems to be some fundamental confusion here about how objects are created and what property accessors do. I'm not sure how to address that besides suggesting looking at object creation and dot syntax.

Why is this instance object being changed?

I'm very new at objective C, I'm just learning. I did the techotopia tutorial "An_Example_SQLite_based_iOS_4_iPhone_Application_(Xcode_4)", then tried to implement it again with FMDB. (I'd post the link to the tutorial but it let's me only post 2 links max)
The problem: In initWithFrame I create eventDB. Then in addEvent, after a keypress, the eventDB.database's contents are changed. This is eventDB in initWithFrame and this is it in addEvent.
#import "appTracker.h"
#implementation appTracker
- (id) initWithFrame:(NSRect)frameRect
{
self = [super initWithFrame:frameRect];
eventDB = [[appTrackerDB alloc] init];
return self;
}
- (void) keyDown: (NSEvent *) event
{
NSString *chars = [event characters];
unichar character = [chars characterAtIndex: 0];
if (character == 'A') {
NSLog (#"Adding event");
[self addEvent:#"test_arg"];
}
}
- (void) addEvent: (NSString *) name
{
[eventDB setName:name];
[eventDB setPhone:name];
[eventDB setAddress:name];
[eventDB setStatus:name];
[eventDB saveData];
}
...
#end
Using GDB I stepped through and found that it is changing in main.m (autogenerated by XCode4) here: (not really sure what this code does or why it's there)
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **)argv);
}
I'm unfamiliar with objective C. Can someone help me figure out why my eventDB.database object is being changed? I'm probably not managing some memory correctly or totally misinterpreting how you are supposed to do this. Any help would be appreciated.
eventDB is an instance of:
#import <Foundation/Foundation.h>
#import "FMDatabase.h"
#interface appTrackerDB : NSObject {
NSString *name;
NSString *address;
NSString *phone;
NSString *status;
NSString *databasePath;
FMDatabase *database;
}
Thanks!
Also [eventDB saveData] is:
- (void) saveData
{
[database executeUpdate:#"insert into user (name, address, phone) values(?,?,?)",
name, address, phone,nil];
}
And created the database with:
#implementation appTrackerDB
#synthesize name,address,status,phone;
- (id)init
{
self = [super init];
if (self) {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docsPath = [paths objectAtIndex:0];
NSString *path = [docsPath stringByAppendingPathComponent:#"database.sqlite"];
database = [FMDatabase databaseWithPath:path];
[database open];
[database executeUpdate:#"create table IF NOT EXISTS user(ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, ADDRESS TEXT, PHONE TEXT)"];
if ([database hadError]) {
NSLog(#"DB Error %d: %#", [database lastErrorCode], [database lastErrorMessage]);
}
name = #"TEST";
}
return self;
}
You don't actually retain the Database. In Objective-C you need to manually retain the Objects, especially if they are not properties. (e.g. name is declared as a property, database is not)
Retaining means that you own the Object. databaseWithObject retains the Database but calls autorelease on it, which normally means, it will delete the reference as soon as possible after the calling method is finished.
Depending on your platform, e.g. OS X instead of iOS, you could enable the GarbageCollection-feature. This would mean, that the OSX/Objective-C environment would do a lot of the memory management for you. But for this to be of any use, you would need to the declare the pertaining instance variables as properties and use the appropriate setter- and getter-methods on them.
Here is an example of a property-declaration (appTrackerDB.h):
#import <Foundation/Foundation.h>
#import "FMDatabase.h"
#interface appTrackerDB : NSObject {
/*
These are only necessary when
using iOS-Versions prior to
iOS 4, or if you really
need to manipulate the values
without utilizing the setter-/getter-
methods.
*/
NSString *name;
NSString *address;
NSString *phone;
NSString *status;
NSString *databasePath;
FMDatabase *database;
}
#property (retain) NSString *name,*address;
#property (retain) NSString *phone,*status,*databasePath;
#property (retain) FMDatabase *database;
appTrackerDB.m:
#implementation appTrackerDB
#synthesize name,address,status,phone;
#synthesize databasePath,database;
An example setter method you would call instead of you manual assignment is:
[self setDatabase:...]; instead of assigning a value directly database = ...
Setter methods like setVariableName and getter methods like variableName
are synthesized for you by the #synthesize directive.

Objective C class initialization errors

I have the following objective C class. It is to store information on a film for a cinema style setting, Title venue ID etc. Whenever I try to create an object of this class:
Film film = [[Film alloc] init];
i get the following errors: variable-sizedobject may not be initialized, statically allocated instance of Objective-C class "Film", statically allocated instance of Objective-C class "Film".
I am pretty new to Objective C and probably am still stuck in a C# mindset, can anyone tell me what I'm doing wrong?
Thanks michael
code:
// Film.m
#import "Film.h"
static NSUInteger currentID = -1;
#implementation Film
#synthesize filmID, title, venueID;
-(id)init {
self = [super init];
if(self != nil) {
if (currentID == -1)
{
currentID = 1;
}
filmID = currentID;
currentID++;
title = [[NSString alloc] init];
venueID = 0;
}
return self;
}
-(void)dealloc {
[title release];
[super dealloc];
}
+(NSUInteger)getCurrentID {
return currentID;
}
+(void)setCurrentID:(NSUInteger)value {
if (currentID != value) {
currentID = value;
}
}
#end
// Film.h
#import <Foundation/Foundation.h>
#interface Film : NSObject {
NSUInteger filmID;
NSString *title;
NSUInteger venueID;
}
+ (NSUInteger)getCurrentID;
+ (void)setCurrentID:(NSUInteger)value;
#property (nonatomic) NSUInteger filmID;
#property (nonatomic, copy) NSString *title;
#property (nonatomic) NSUInteger venueID;
//Initializers
-(id)init;
#end
You need your variable that holds the reference to your object to be of a reference type. You do this by using an asterisk - see below:
Film *film = [[Film alloc] init];
Coming from Java I often think of the above as:
Film* film = [[Film alloc] init];
I tend to associate the 'reference' marker with the type. But hopefully someone more versed in C/C++/ObjC will tell me why this is wrong, and what the 'asterisk' is actually called in this context.