I'm trying to learn the basics of Objective-C through reading "Learning Objective-C 2.0" Theres an exercise on categories where your asked to add a method to NSString through use of categories. My simple program is below. it (should) take a string and then reverses the order of the words.
Main
#import <Foundation/Foundation.h>
#import "CatNSString.h"
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSString *test = #"Dog bites Man";
NSString *test1 = nil;
test1 = [test1 reverseWords: test];
NSLog(#"%# : %#", test, test1);
}
return 0;
}
Interface
#import <Foundation/Foundation.h>
#interface NSString (CatNSString)
- (NSString*) reverseWords:(NSString*)string;
#end
Implementation
#import "CatNSString.h"
#implementation NSString (CatNSString)
- (NSString*) reverseWords: (NSString*) string
{
NSString *stringReturn = nil;
NSArray *arrayString = [string componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
stringReturn = [string stringByAppendingString:#"hello"];
for (NSString *word in arrayString)
{
NSString *stringTmp1 = word;
NSString *stringTmp2 = stringReturn;
stringReturn = [stringTmp1 stringByAppendingString:stringTmp2];
NSLog(#"stringTmp1: %#", stringTmp1);
}
return stringReturn;
}
#end
It compiles but the program acts as though the method is never called. If I place a NSLog call in the method there is no output to the console. Can anyone see what I'm not doing that I should be doing?
You just did it wrong way.
Here is the proper way to define a Category:
#interface NSString (CatNSString)
-(NSString *)reverse;
#end
#implementation NSString (CatNSString)
-(NSString*)reverse {
// `Self` keyword will refer to your original string, no need to pass it as a parameter
NSArray *words = [self componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
return [[[words reverseObjectEnumerator] allObjects] componentsJoinedByString:#" "];
}
#end
And in your main code, do the following:
NSString *test = #"Dog bites Man";
NSLog(#"%#", [test reverse]);
Related
I am trying to learn how to make simple classes.
So far I am not getting the results expected using addObject and my class.
Here is what I have:
In my view controller:
#import "onoffclass.h"
In its viewDidLoad:
NSMutableArray *inTable;
onoffclass *therec;
onoffclass *readrec;
inTable = [NSMutableArray array];
therec = [[onoffclass alloc]init];
readrec = [[onoffclass alloc]init];
for (int lop=0;lop<3;lop++){
therec.parsedID = [NSString stringWithFormat:#"%i",lop];
[inTable addObject:therec];
NSLog(#"lop=%i onoff.parsedID=%#",lop,therec.parsedID);
for (int z=0;z<[inTable count];z++){
readrec = inTable[z];
NSLog(#" inTable[%i] parsedID=%#",z,readrec.parsedID);
}
}
In my onoffclass.h:
#interface onoffclass : NSObject
#property NSString *parsedID;
#property NSString *parsedOn;
#property NSString *parsedOff;
#property NSString *parsedAdj;
#property NSString *parsedRoom;
#property NSString *parsedBuilding;
#property NSString *parsedWho;
#property NSString *parsedInfo;
#property NSString *parsedBillable;
-(onoffclass*)initWithSomeString: (NSString*)blah AndSomeNum: (int)num;
-(NSString*)description;
#end
In my onoffclass.m:
#import <Foundation/Foundation.h>
#import "onoffclass.h"
#implementation onoffclass {
NSString *_parsedID;
NSString *_parsedOn;
NSString *_parsedOff;
NSString *_parsedAdj;
NSString *_parsedRoom;
NSString *_parsedBuilding;
NSString *_parsedWho;
NSString *_parsedInfo;
NSString *_parsedBillable;
}
-(onoffclass*)initWithSomeString: (NSString*)blah AndSomeNum: (int)num {
self = [super init];
_parsedID = blah;
_parsedOn = #"on";
_parsedOff = #"off";
_parsedAdj = #"adj";
_parsedRoom = #"room";
_parsedBuilding = #"building";
_parsedWho = #"who";
_parsedInfo = #"info";
_parsedBillable = #"billable";
return self;
}
-(NSString*)description {
return [NSString stringWithFormat: #"%#", _parsedID];
}
#end
Here is the output:
lop=0 onoff.parsedID=0
inTable[0] parsedID=0
lop=1 onoff.parsedID=1
inTable[0] parsedID=1
inTable[1] parsedID=1
lop=2 onoff.parsedID=2
inTable[0] parsedID=2
inTable[1] parsedID=2
inTable[2] parsedID=2
Why does it appears that addObject is updating all array indices and how do I fix this?
Thanks,
Dale
You are creating one instance of onoffclass (by the way class names should start with a capital letter) then you are adding the same instance again and again in the loop. Since classes are reference types changing a property affects all occurrences of the same instance.
Solution is to put the line to create an instance in the loop
for (int lop = 0; lop < 3; lop++) {
therec = [[onoffclass alloc] init];
...
Have a weird error on method declaration and call...
MyObject.h --- declarations. has been trimmed down
#import <Foundation/Foundation.h>
#interface MyObject : NSObject
- (void) useFlattenHTML;
- (NSString *) flattenHTML:(NSString *) inHtml;
- (NSString *) justAStringMethod;
#end
And method defined and called like this...
MyObject.m --- method declaration and usage. Trimmed down
#import "MyObject.h"
#implementation MyObject
- (NSString *) flattenHTML:(NSString *) inHtml {
NSScanner *theScanner;
NSString *text = nil;
theScanner = [NSScanner scannerWithString:inHtml];
while ([theScanner isAtEnd] == NO) {
[theScanner scanUpToString:#"<" intoString:NULL] ;
[theScanner scanUpToString:#">" intoString:&text] ;
inHtml = [inHtml stringByReplacingOccurrencesOfString:[NSString stringWithFormat:#"%#>", text] withString:#""];
}
//
inHtml = [inHtml stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
return inHtml;
}
- (NSString *) justAStringMethod
{
// Some calls.
}
- (void) useFlattenHTML
{
NSString* resultStr = [self.flattenHTML #"Some html tagged string"];
NSString* anotherStr = [self.justAStringMethod];
}
#end
I get
Property 'flattenHTML' not found on object of type 'MyObject *'
Maybe because you're using the dot notation in a case where it doesn't make much sense :
in useFlattenHTML method, self.flattenHTML is the same as [self flattenHTML], which doesn't exist, as you only have [self flattenHTML:someString].
On top of that, dot notation is possible, but you should keep it for fields declared as #property only
I have an object w/ an NSString for a property. I would like to use a custom setter for this property so that it always trims all whitespace when the string is set.
Normally this is done with NSString *blah2 = [blah stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];, but I'm not sure about how to formulate this for a custom setter. Isn't it bad to use autoreleased objects like this in the setter? Should I use a mutable string instead and then convert it to unmutable?
Thanks for any help you guys can provide.
I'm going to presume ARC.
#import <Foundation/Foundation.h>
#interface Foo:NSObject
#property (nonatomic,copy) NSString *myString;
#end
#implementation Foo
#synthesize myString = _myString;
- (void)setMyString:(NSString *)aString;
{
_myString = [[aString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] copy];
}
#end
int main(int argc, char *argv[]) {
#autoreleasepool {
Foo *someFoo = [[Foo alloc] init];
someFoo.myString = #"Blah ";
NSLog(#"Length = %ld",[[someFoo myString] length]); // prints 4
}
}
In the setter, I do copy the parameter of the setter in the event someone tries to provide an instance of NSMutableString - presuming that Foo is not expected to modify the original string.
EDIT:
Or if manual reference counting, the setter might be:
- (void)setMyString:(NSString *)aString;
{
if( aString != _myString ) {
[_myString release];
_myString = [[aString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] copy];
}
}
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.
So I presume this is a memory issue, here's the code:
- (NSString *)giveMeAStringGoddammit
{
NSString *s;
// switch statement to choose which string to assign to s, so essentially:
s = #"a string";
return s;
}
And the calling code:
NSString *aString;
aString = [self giveMeAStringGoddammit];
However after this call, aString has an invalid summary when debugged and crashes when run.
I suspect I'm missing a retain or something, can someone help? Thanks.
What you've got works just fine:
#import <Foundation/Foundation.h>
#interface Test : NSObject
- (NSString *)giveMeAStringGoddammit;
#end
#implementation Test
- (NSString *)giveMeAStringGoddammit
{
NSString *s;
// switch statement to choose which string to assign to s, so essentially:
s = #"a string";
return s;
}
#end
int main (int argc, const char * argv[])
{
#autoreleasepool {
Test *t = [[Test alloc] init];
NSLog(#"t says: %#", [t giveMeAStringGoddammit]);
}
return 0;
}
The output of this program is:
t says: a string
To make this a little more realistic, let's change it to use a property:
#import <Foundation/Foundation.h>
#interface Test : NSObject
#property(copy, nonatomic) NSString *string;
- (NSString *)giveMeAStringGoddammit;
#end
#implementation Test
#synthesize string;
- (NSString *)giveMeAStringGoddammit
{
NSString *s;
// switch statement to choose which string to assign to s, so essentially:
s = self.string;
return s;
}
#end
int main (int argc, const char * argv[])
{
#autoreleasepool {
Test *t = [[Test alloc] init];
t.string = #"Hello world!";
NSLog(#"t says: %#", [t giveMeAStringGoddammit]);
}
return 0;
}
This does what you'd expect:
t says: Hello world!
You have created a pointer object and it's expected to increment it's retain count whenever you referring them, for increasing retain count the string should be initiated and allocated with memory else you could use [NSString stringwithString:[self giveMeAStringGoddammit]. You can use this definition only when you exactly need it reference locally. because whenever you try to refer it out side the auto release pool will crash the app (hence it's not retained manually). So if you need to use it out side of the function, better use [NSString alloc]init] and then load your string to the pointer object. Well the way to make your code to work is add the lines NSString *aString = [NSString stringWithString:[self giveMeAStringGoddammit]];
NSLog(#"My Str:%#",aString); Hooray now the goddammit string was given......