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.
Related
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.
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
Can't find out why my code doesn't work. Please help someone.
I created my own class, implemented NSCoding protocol. Do not know what i miss or wrong.
Here is saving code
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *fileName = [documentsDirectory stringByAppendingPathComponent:#"Currency.plist"];
Item * item = [[Item alloc] init];
item.x = 3; item.y = 5; item.type = (TType) 3; item.isSelected = NO;
NSMutableArray * array = [[NSMutableArray alloc] init];
[array addObject:item];
[array fileName atomically:YES] // ( Doesn't Save the file ,returns NO);
Here is the code of my class
*.h*
#import <Foundation/Foundation.h>
enum TType
{
kNone = 0,
KFirst = 1,
....
};
#interface Item : NSObject <NSCoding>{
}
#property (nonatomic) int x;
#property (nonatomic) int y;
#property (nonatomic) enum TType type;
#property (nonatomic) BOOL isSelected;
#end
.m
#implementation Item
#synthesize x, y , type , isSelected;
#pragma mark NSCoding Protocol
- (void)encodeWithCoder:(NSCoder *)encoder;
{
[encoder encodeInt32:[self x] forKey:#"x"];
[encoder encodeInt32:[self y] forKey:#"y"];
[encoder encodeInt32:[self type] forKey:#"type"];
[encoder encodeBool:[self isSelected] forKey:#"isSelected"];
}
- (id)initWithCoder:(NSCoder *)decoder;
{
if ( ![super init] )
return nil;
[self setX:[decoder decodeInt32ForKey:#"x"]];
[self setY:[decoder decodeInt32ForKey:#"y"]];
[self setType:(TType)[decoder decodeInt32ForKey:#"color"]];
[self setIsSelected:[decoder decodeBoolForKey:#"isSelected"]];
return self;
}
#end
I think you'll find your answer at: objects conforming to nscoding will not writetofile
i.e. you can't serialize your Item class to a property list, since it isn't a property list object (NSString, NSData, NSArray, or NSDictionary).
See the documentation for writeToFile:atomically::
This method recursively validates that all the contained objects are property list objects before writing out the file, and returns NO if all the objects are not property list objects, since the resultant file would not be a valid property list.
I'm a newbie iOS developer.
I wrote a small application that save an NSMutableArray array with my objects that derived from NSObject.
Application do the save but the file isn't created in document directory and application can't read.
this issue is both on the simulator and my iPhone 3gs 4.2.1
My NSMutableArray definition inside the appDelegate class:
#property (nonatomic,retain, readwrite) NSMutableArray *places;
My NSObject class:
#import <Foundation/Foundation.h>
#interface Place : NSObject {
NSString *name;
NSString *location;
}
-(id) init:(NSString *)name: (NSString *)location;
#property (retain,nonatomic,readwrite) NSString *name;
#property (retain,nonatomic,readwrite) NSString *location;
#end
My StorageService library class:
#import "StorageService.h"
#implementation StorageService
-(id) init {
self = [super init];
if (self != nil) {
}
return self;
}
-(void) saveArrayToFile:(NSString*) filename : (NSMutableArray *)arrayToSave{
// get full path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *fullPath = [paths objectAtIndex:0];
fullPath = [fullPath stringByAppendingPathComponent:filename];
NSLog(#"Save in %#",fullPath);
[arrayToSave writeToFile:fullPath atomically:YES];
}
-(NSMutableArray*) readArrayFromFile:(NSString *)filename {
// get full path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *fullPath = [paths objectAtIndex:0];
fullPath = [fullPath stringByAppendingPathComponent:filename];
if ([[NSFileManager defaultManager] fileExistsAtPath:fullPath]) {
NSMutableArray *data = [[NSMutableArray alloc] initWithContentsOfFile:fullPath];
if (data == nil) {
data = [[NSMutableArray alloc] init];
}
NSLog(#"Read from %#",fullPath);
return data;
} else {
NSMutableArray *data = [[NSMutableArray alloc] initWithContentsOfFile:fullPath];
return data;
}
}
-(void) dealloc {
[super dealloc];
}
#end
and My functions in the appDelegate:
-(void) saveApplicationData {
[self.storageService saveArrayToFile : PLACES_FILE : self.places];
}
-(void) loadApplicationData {
self.places = [self.storageService readArrayFromFile:PLACES_FILE];
}
Here is my class that holds constant to filename:
#import <Foundation/Foundation.h>
extern NSString * const PLACES_FILE = #"Places.dat";
#interface ApplicationConstants : NSObject {
}
#end
So what is wrong?
Thank you guys.
What you want is to let Place conform to the NSCoding protocol, to allow for serialization to and from files (and in memory data if wanted)
Extend Place as (I have also changed the name of the init method as your name was against every naming practice iOS has):
#interface Place : NSObject <NSCoding> {
NSString *name;
NSString *location;
}
-(id)initWithName:(NSString *)name location:(NSString *)location;
#property (retain,nonatomic,readwrite) NSString *name;
#property (retain,nonatomic,readwrite) NSString *location;
#end
Your implementation is quite simple but you also need to implement two methods defined by the NSCoding protocol:
#implementation Place
#synthesize name, location;
-(id)initWithName:(NSString *)aName location:(NSString *)aLocation {
self = [super init];
if (self) {
self.name = aName;
self.location = aLocation;
}
return self;
}
-(id)initWithWithCoder:(NSCoder)decoder {
self = [super initWithCoder:decoder];
if (self) {
self.name = [decoder decodeObjectForKey:#"name"];
self.location = [decoder decodeObjectForKey:#"location";
}
return self;
}
-(void)encodeWithCoder:(NSCoder*)encoder {
[encoder encodeObject:self.name forKey:#"name"];
[encoder encodeObject:self.location forKey:#"location"];
[super encodeWithCoder:encoder];
}
#end
With this in place, saving the places array to disk is as easy as:
[NSKeyedArchiver archiveRootObject:places toFile:path];
And decoding just as easy:
places = [[KSKeyUnarchiver unarchiveObjectWithFile:path] retain];
To use writeToFile objects in array need to be plist capable type (NSDate, NSDate, NSString, NSArray, NSDictionary)
Implement NSCoding on the objects in array and use NSKeyedArchiver to serialize/deserialize.
write:
[NSKeyedArchiver archiveRootObject:myArray toFile:self.places];
read:
[NSKeyedUnarchiver unarchiveObjectWithFile:path];
More info can be found here:
Persisting Custom Objects
I am currently using the pragmatic screencast on Objective-C to help me program in objective-c. I have a background in Java and C++, but I am having a very difficult time getting used to everything in Objective(Mostly because I am not comfortable with the syntax).
Below is the error I am receiving with all the code.
I am also getting a warning in movie.m class as well: Wirtable atomic property 'title'
cannot be pair a synthesized setter/getter with a user defined setter/getter
thanks for your help.
I am receive this error
Current language: auto; currently objective-c
warning: Couldn't find class validation function, calling methods on uninitialized objects may deadlock your program.
Program received signal: “EXC_BAD_ACCESS”.
I ran it through the debugger and the address of movie in the code below is in red
main.m
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Movie *movie = [[Movie alloc] initWithTitle:#"iron man"
andRating:5
andYear:2008];
[movie play];
NSLog(#"our movie is %#", movie);
[pool drain];
return 0;}
Movie.h
interface Movie : NSObject {
NSString *title;
int rating;
int year;
}
- (id)initWithTitle:(NSString *)newTitle
andRating:(int)newRating
andYear:(int) year;
#property(assign) NSString *title;
#property(assign) int rating;
#property(assign) int year;
-(void) play;
#end
Movie.m
#import "Movie.h"
#implementation Movie
#synthesize title;
#synthesize rating;
#synthesize year;
-(id)initWithTitle:(NSString *)newTitle
andRating:(int)newRating
andYear:(int)newYear;
{
self = [super init];
if(nil != self){
self.title = newTitle;
self.rating = newRating;
self.year = newYear;
}
return self;
}
-(NSString *) description{
NSString *oldDescription = [super description];
return [NSString stringWithFormat: #"%# title =%#, rating =%d year=%#",
oldDescription, self.title, self.rating, self.year];
}
- (void)setTitle:(NSString *)newTitle {
title = [newTitle capitalizedString];
}
-(void) play {
NSLog(#"Playing %#", self);
}
You use year=%# when it should be year=%d.
Some more random thoughts:
You should retain or better even copy the title instead of assigning it.
The init method should be named
-(id)initWithTitle:(NSString *)aTitle
rating:(int)aRating
year:(int)aYear;
Don't forget a dealloc method then.
Your title property is an object type and so should in generally be either retain or copy -- in the case of NSString properties, it is traditional to use copy to avoid issues when you're passed an NSMutableString instead.
#property (copy) NSString* title;
Since you explicitly define the setter, you then need to implement this policy yourself, something like this:
- (void)setTitle:(NSString *)newTitle
{
[title release];
title = [[newTitle capitalizedString] copy];
}
You'll also need to include a dealloc method to clean up:
- (void) dealloc
{
[title release];
[super dealloc];
}