PDA

View Full Version : Obj-C Newbie question




Soulstorm
Jun 10, 2006, 10:45 AM
Lets say that I have an NSString that I want to give it a value.

Am I obliged to use the standard input functions that C has? My problem is that NSString doesn't have any limitations as to how many characters I will give. But all plain C functions do have this limitation, so I must know in advance the number of characters I must declare the array which will hold my NSString to hold. So I have to do it this way:

char p[30];
scanf("%s",p);
NSString *test = [[NSString alloc] initWithCString: p];
Is there any other way, so that I am not forced to declare how many characters I will read in advance?

I suppose I could do this using Objective C++ (using the string class of the STL and then converting the string class to plain character array and inserting it to the string) but I am searching for a plain Obj-C style input.

Any ideas? Sorry if that's a newbie's question, I am just learning Obj-C, and I come from a strong C++ experience.



caveman_uk
Jun 10, 2006, 11:18 AM
AFAIK there's no Cocoa or foundation method that directly reads an NSString from stdin or the command line. So you do have to do all that scanf messing about with buffers. You could also use gets instead of scanf.

You could of course wrap all this nastiness in a category for NSString so the messiness is hidden in just one bit of code.

csubear
Jun 10, 2006, 11:40 AM
you could also do something like this (if your using obj-c++)


std::string buff;
std::cin >> buff;
NSString* s = [[NSString stringWithCString:buff.c_str()] retain];

Soulstorm
Jun 10, 2006, 03:54 PM
I want to avoid using Obj-C++ for the time being...

One question though. Is ObjectiveC++ portable? I mean... I know there are ways to program objective-c on windows. Is there such support for Objective C++ (considering that it is just a frontend by Apple)?

HiRez
Jun 10, 2006, 04:18 PM
I agree with caveman, just wrap it up in your own method, which you only have to write once, then you can do it all in a single call like:

NSString *s = [NSString stringFromConsoleWithMaxLength:30];

savar
Jun 10, 2006, 08:45 PM
char p[30];
scanf("%s",p);
NSString *test = [[NSString alloc] initWithCString: p];
Is there any other way, so that I am not forced to declare how many characters I will read in advance?

Why don't you want to declare the buffer size ahead of time? I know it seems limiting, but any input function (whether you write or somebody else does) will have to create an input buffer. So just set the size to something large enough to hold your input string, and make sure to use overflow-safe APIs:


char buff[255];
fgets(buff, sizeof(buff), stdin);


If there's a possibility of reading a very large amount of data, use a resizeable array or linked list of array.

I don't think there's a way in Cocoa to read a string from stdin -- why would there be? Cocoa is a GUI framework.

csubear
Jun 10, 2006, 10:29 PM
I want to avoid using Obj-C++ for the time being...

One question though. Is ObjectiveC++ portable? I mean... I know there are ways to program objective-c on windows. Is there such support for Objective C++ (considering that it is just a frontend by Apple)?

I don't know if Obj-C++ is an apple only extension to gcc or not. As for compiling objective-c on windows. There is gnu-step. But its not really that portable. I know they have most of the core foundations and cocoa libraries, but I know you have to redo the nibs.

I think you are really asking alot if you want your program to compile on both gnu-step and xcode.

mduser63
Jun 10, 2006, 11:10 PM
I don't know if Obj-C++ is an apple only extension to gcc or not. As for compiling objective-c on windows. There is gnu-step. But its not really that portable. I know they have most of the core foundations and cocoa libraries, but I know you have to redo the nibs.

I think you are really asking alot if you want your program to compile on both gnu-step and xcode.

As long as you're only using Core Foundation stuff, Obj-C is actually relatively portable. You have to be careful with some OS X-only methods and vice-versa, but for the most part everything works OK.

Soulstorm
Jun 11, 2006, 04:17 AM
I think you are really asking alot if you want your program to compile on both gnu-step and xcode.Not really. If I strictly use the Objective C Foundation Framework then I will have no problem compiling on Windows.

EDIT: as for Objective C++... yes, it is supported by GNU. I saw that in http://www.gnu.org/software/gcc/

Catfish_Man
Jun 11, 2006, 10:43 PM
Perhaps NSInputStream (file:///Developer/ADC%20Reference%20Library/documentation/Cocoa/Reference/Foundation/Classes/NSInputStream_Class/Reference/Reference.html#//apple_ref/doc/uid/20001982) could be useful? I haven't used it, so I could be completely wrong, but it sounds promising.

Soulstorm
Jun 12, 2006, 04:06 AM
std::string buff;
std::cin >> buff;
NSString* s = [[NSString stringWithCString:buff.c_str()] retain];
Can you tell me why you chose to retain the object, please? I am now reading about memory management, and I just can't understand why you did that.

whooleytoo
Jun 12, 2006, 05:17 AM
std::string buff;
std::cin >> buff;
NSString* s = [[NSString stringWithCString:buff.c_str()] retain];
Can you tell me why you chose to retain the object, please? I am now reading about memory management, and I just can't understand why you did that.

Retaining the NSString ensures it isn't dealloc'ed by another thread or by a subsequent call in your code, before you're finished with it. This may or may not be necessary, depending on your code; and does of course require it be released when you're finished with the NSString.

It's also necessary if you need the NSString outside the current scope, (i.e. after the current method/function has returned), as it's likely the NSString returned by stringWithCString: (or any such method) will be autoreleased.

caveman_uk
Jun 12, 2006, 06:17 AM
It's also necessary if you need the NSString outside the current scope, (i.e. after the current method/function has returned), as it's likely the NSString returned by stringWithCString: (or any such method) will be autoreleased.
Objects returned from methods should be autoreleased. It's the convention that other than alloc, copy, and mutableCopy methods always return autoreleased objects. It is the job of the code that called the method to retain any returned objects. It is not the job of the called method.

whooleytoo
Jun 12, 2006, 10:14 AM
Objects returned from methods should be autoreleased. It's the convention that other than alloc, copy, and mutableCopy methods always return autoreleased objects. It is the job of the code that called the method to retain any returned objects. It is not the job of the called method.

Just checking: we are agreeing with each other, are we not?

csubear
Jun 12, 2006, 10:24 AM
Objects returned from methods should be autoreleased. It's the convention that other than alloc, copy, and mutableCopy methods always return autoreleased objects. It is the job of the code that called the method to retain any returned objects. It is not the job of the called method.

I retained it so that it would so what your previous code snippet did. If I didn't retain it, it would be de-alloced when then autorelease pool is emptied. In cocoa the autorelease pool on the main thread is emptied at the end of the event loop.

Soulstorm
Jun 12, 2006, 10:41 AM
I am trying to create a program that is intended to be a bank simulation. So, I made an account class which will hold each account, and a bank class, which is supposed to represent the main bank.

I have attached the files below. please have a look at them, they are not very long.

My problem is that at my main I want to find a way to make a loop which will insert accounts into the bank. I could do this with a while statement, but I chose the 'for' loop for testing purposes.

Can you tell me how am I going to do this and be 'memory management-friendly'? I have managed to insert the values into the bank, but not also release the objects properly!

#include <iostream>
#include <string>
#import <Foundation/Foundation.h>
#import "Account.h"
#import "bank.h"
#include "functions.mm"

#define AAI(x) Account *x = [[Account alloc] init];

using std::cin;
using std::cout;
using std::string;

Bank *myBank = [Bank alloc];

void addAccToBank(){
string s;

int i;
int deposit;
for(i=0; i<2; i++){
NSMutableString *str;
Account *acc = [[Account alloc] init];
cout << "Give a name: ";
getline(cin, s);
cout << "Give a deposit: ";
cin >> deposit;
cin.get();

str = [[NSMutableString alloc] initWithCString: s.c_str()];
[acc setName: str andDeposit: deposit];

[myBank addToBank: acc];

cout << [acc retainCount] << " is the account's retain count\n";
cout << [str retainCount] << " is the string's retain count\n";
}
}

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[myBank initialize];

addAccToBank();

[myBank printAllEntries];
[pool release];
return 0;
}I have ommited the release commands. Note that the result is wrong! Both accounts on the bank have the same values, even if I give them different ones! Can you give me a solution?

1)When I add an object to an NSArray, isn't that element deeply copied into the NSArray? Why does this happen?
2)How can I fix the problems I am having with my program?

These memory management problems are driving me nuts. Any help will be greatly appreciated.

EDIT: Changed the main. it shows the results fine, now, but I really don't know how am I going to deallocate all the object's I've use in the addAccToBank() function... Let alone all the other memory leaks I've produced.

HiRez
Jun 12, 2006, 01:48 PM
1)When I add an object to an NSArray, isn't that element deeply copied into the NSArray? Why does this happen?Normally objects that you add to an NSArray are not deep-copied into the array, the array simply saves a reference to the object and sends it a retain message (increases its retain count by one). When you release the array, it sends each object in the array a release message. If any of those result in a retain count of 0, the object will be deallocated.

As for why you're getting the same values in both accounts, I'm not sure. One thing I would ask is why you feel the need to be using C++ here. You are free to do that of course, but IMO it's kind of a bizarre hybrid that is only partially supported. You'd do better in the long run to use straight Objective-C and C if you're using the Cocoa libraries, which technically are portable but for all practical purposes are not at this time, and not for anything in the AppKit (GUI). Learning Objective-C doesn't take all that long (you're already using a bunch of it anyway), and it will make things smoother for you and you'll probably get a lot more help doing it that way.

Other points:

-- Not sure why you're using a mutable string here, because it doesn't appear you're doing anything with it. Even if your Account class uses a mutable string, I would pass an NSString as the initial value, since it is set when you read it from the console.

-- It's a bit strange to separate your alloc and init statements as you have it here (although you have initialize instead of init, see below). Technically that should be ok, but generally they always go together (makes it easier to keep track of it). If you aren't allocating the variable when you declare it, just assign nil to it.

-- Looks like you're using initialize instead of init on the Bank object. They're not the same thing. initialize is a class method that is called by the runtime before anything is used in the class, in general you would never call this method directly (usually it's overridden in a subclass). This may explain some of the weirdness you're seeing.

-- You've alloc-inited the str varable, but you never release it. This is a memory leak. Either you need to use an autoreleased allocation method ("stringWith..."), or you need to autorelease it yourself ("alloc] init] autorelease]"), or you need to send that object a release message when you're done with it. Same thing with Account, and same thing with Bank. Whenever you alloc-init something, you need to release it when done, whether by autoreleasing it and letting the autorelease pool handle it, or by explicitly releasing it yourself.

Understanding retain-release memory management is critical to working with Objective-C and Cocoa (hell, I still get confused sometimes), I would not try to do much more until you understand the basics of it pretty well. It does become more natural over time, but it's pretty confusing at first. Definitely read Apple's Objective-C PDF and some of the books such as the Hillegass and Kochan ones (do a search, they've been listed here many times).

Soulstorm
Jun 12, 2006, 03:49 PM
Well, I try my best. I have fixed the initialize thing you told me for the bank class. I also used plain Objective C in my files. I have attached the new files below.

And I should have been more clear about what I'm looking for:

this is the new main:
#import <Foundation/Foundation.h>
#import "Account.h"
#import "bank.h"

Bank *myBank = [[[Bank alloc] init] initialize];

void addAccToBank(){

int i;
for(i=0; i<2; i++){
NSAutoreleasePool *tempPool = [[NSAutoreleasePool alloc] init];
char p[30];
int deposit;

NSString *str;
Account *acc = [[[Account alloc] init] autorelease];
printf("Give a name: ");
scanf("%s", p);
printf("Give a Deposit: ");
scanf("%i", &deposit);

str = [[[NSString alloc] initWithCString: p] autorelease];
[acc setName: str andDeposit: deposit];
[myBank addToBank: acc];


printf("%i is the account's retain account\n", [acc retainCount]);
printf("%i is the string's retain count\n", [str retainCount]);
[tempPool release];
}

}

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[myBank autorelease];

addAccToBank();

[myBank printAllEntries];
[pool release];
return 0;
}

And this is my output:
bankaccount(f) has exited with status 0.
[Session started at 2006-06-12 23:38:47 +0300.]
Give a name: account1
Give a Deposit: 9384
2 is the account's retain account
1 is the string's retain count
Give a name: account2
Give a Deposit: 9999
2 is the account's retain account
1 is the string's retain count
2 customers:
Name: account1
Deposit: 9384
Name: account2
Deposit: 9999
bank deallocating...

bankaccount(f) has exited with status 0.

Question 1:How can I release the objects from the function without harming the contents of the myBank class?
Question 2: What I really want to do with that piece of code is this: I want to have a loop that will accept accounts and insert them into the bank's array container. After that, I want to deallocate whatever temporary objects I needed to make the insertion to the 'myBank' class. So, I want my bank to contain just a deep copy of the objects (so, each position in the array will have an account object that will have a retain count of 1) But I cannot do this, apparently, and I don't know why!

As far as the books are concerned, I have finished Kochan's book yesterday, and having read every single page of it, it doesn't refer anything that will give any solution to my problem. Even the sample programs contain an Addressbook program (similar to my bank program) but do not utilize any loop... Even the assignment to arrays is done using multiple objects of the same kind, and not just one object that is intended to be created and deallocated inside a loop.

As for Apple's Obj-C pdf, I can't find it... where is it? I also have a book by Apple, named "Learning Cocoa with Objective C" and another book named "Cocoa Programming", by 3 writers, Anguish, Buck, Yacktman.

Soulstorm
Jun 12, 2006, 05:09 PM
I think I did it! Please, check my code to see if I'm causing a memory leak again... Here is my main
#import <Foundation/Foundation.h>
#import "Account.h"
#import "bank.h"

void addAccToBank(Bank *myBank){
int i;
for(i=0; i<2; i++){
NSAutoreleasePool *tempPool = [[NSAutoreleasePool alloc] init];
char p[30];
int deposit;

NSString *str;
Account *acc = [[[Account alloc] init] autorelease] ;
printf("Give a name: ");
scanf("%s", p);
printf("Give a Deposit: ");
scanf("%i", &deposit);

str = [[[NSString alloc] initWithCString: p] autorelease];
[acc setName: str andDeposit: deposit];

[myBank addToBank: acc];

[tempPool release];
printf("%i is the account's retain account\n", [acc retainCount]);
printf("%i is the string's retain count\n", [str retainCount]);

}

}

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Bank *myBank = [[Bank alloc] init];
[myBank autorelease];

addAccToBank(myBank);

printf("%i is the retainCount\n", [[[myBank bankContainer] objectAtIndex: 1] retainCount]);

[myBank printAllEntries];
[pool release];
return 0;
}
and here (http://att.macrumors.com/attachment.php?attachmentid=50148&stc=1&d=1150150699) is the rest of the source code...

Final question to see if I'm right: By exiting the loop inside the function, the references to the objects are lost, but the objects themselves can be accessed from inside the Bank class, right? So that means that deallocating each object in each location of the array of the Bank class, I will be able to deallocate all memory taken by the objects whose references are now lost, right?

I hope that's true... However, I see that although the retainCount of each element of the containerArray of the myBank class is 1 in the main, when the entire array gets deallocated, I receive no message that the account in every position is deallocated (I have set this to happen in the -dealloc function inside the Account.m file). Why is that?

Sorry guys if I'm bugging you, but I really need your help, as the only live reference I have now, apart from my books is the internet... :o

HiRez
Jun 12, 2006, 07:05 PM
OK, that's starting to look a lot better.

-- In general if you pass an object to a class (let's say a string to the Account class), if the class wants to keep that reference around, it's responsible for retaining it (and also for releasing it when it's done with it). So if you're doing a standard set type of method, you can create (allocate) a string, pass it to the other class (such as in your setName: accessor method in your Account class), and then immediately release it in the same scope that you created it in. That's important. If you allocate an object inside a for loop, and you are finished with it, release it inside the for loop (or autorelease it when you create it). After you add your account to your bank class, the NSArray will retain it when you add it, so it's safe to release it (which you have done here using autorelease).

-- Now, you've got some problems with your accessor methods. Let's look at your setName: method (Account class):-(void) setName: (NSString *) theName
{
[accName autorelease];
accName = [[NSString alloc] initWithString: theName];
}Here again you could have a memory leak because you're allocating an object and not releasing it afterwards. There are several different ways you can write accessors, but the most common is like this:-(void)setName:(NSString*)theName {
[theName retain];
[accName release];
accName = theName;
}I see that you do actually release it in your dealloc: method. In this case that'll probably work, but IMO you're asking for trouble. As I said, it will probably work here, but I recommend doing the retain and release in the same place whenever possible, in this case in the setter method. In your dealloc method, call [self setName:nil];, that will release it. It's also an Objective-C convention to name your setter methods exactly the same as the variable name, prefixed by "set". This will become important for certain things you may use someday, such as Cocoa Bindings. So in your case, the method should really be named setAccName, -OR- the variable should be named simply "name" (thus making your existing setName: method match).

-- You've got a problem in your Bank dealloc: method where you deallocate Bank (that's the class), which is the same method you're currently in. I think you want to deallocate bankContainer here instead (and "accounts" is probably a more appropriate name, but that's nitpicking, sorry).

-- It is not necessary to create an autorelease pool at every step. It's often only necessary to create one pool per project. The objects you create and detroy will still be cleared out once each pass through the event loop. Bear in mind every time you create and destroy a pool, there is some overhead involved, so you generally only want to create subpools when there is a real need to, such as when lots of memory is being allocated in a loop. Even in that case, you often won't want to do a pool for each loop iteration. It's something you have to balance with how much stuff you're creating and destroying (or expecting to) during the actual program execution.

-- If you decide to use autorelease, you can usually use one of the autoreleased constructors and save yourself some work:[acc setName:[NSString stringWithCString:p]];And you're still using a deprecated method here, you should be getting compiler warnings for that. ;)

Soulstorm
Jun 13, 2006, 02:31 AM
Ok I think I see things more clearly now... Thank you for taking the time to look at my code and making me understand things better, you've been very helpful...

So now, here is my final code:
#import <Foundation/Foundation.h>
#import "Account.h"
#import "bank.h"

void addAccToBank(Bank *myBank){
int i;
for(i=0; i<2; i++){
char p[30];
int deposit;

NSString *str;
Account *acc = [[Account alloc] init];
printf("Give a name: ");
scanf("%s", p);
printf("Give a Deposit: ");
scanf("%i", &deposit);

str = [[NSString alloc] initWithCString: p encoding:NSASCIIStringEncoding];
[acc setName: str andDeposit: deposit];

[myBank addToBank: acc];
[str release];
[acc release];

printf("%i is the account's retain account\n", [acc retainCount]);
printf("%i is the string's retain count\n", [str retainCount]);

}

}

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Bank *myBank = [[Bank alloc] init];
[myBank autorelease];

addAccToBank(myBank);
[myBank printAllEntries];

[pool release];
return 0;
}As you can see in the rest of the program (http://att.macrumors.com/attachment.php?attachmentid=50185&stc=1&d=1150183803), I have used only 1 autorelease pool and manually released the objects inside the loop to make them have a retain count of 1 when they exit the loop. I have also fixed the bug in the Bank -dealloc method. I haven't changed the setters' names, I will do that afterwards. And don't be afraid to nitpick my code, that's what these forums are for! :) By the way, Xcode didn't show me any warnings about the method you are talking, but the documentation says that it is indeed deprecated.

So, my final and only problem is why am I not getting any release messages for the account objects that have already been inserted into the bank's array?

HiRez
Jun 13, 2006, 04:40 AM
Oops, I gave you some misleading information there, I said you want to deallocate bankContainer, but actually you want to release bankContainer (which will in turn cause it to be deallocated when its retain count is 0). You rarely should call dealloc directly on an object, if ever, just send it a release and the runtime checks when the retain count gets to 0, triggering the dealloc method to be called.

So, my final and only problem is why am I not getting any release messages for the account objects that have already been inserted into the bank's array?
Hmm, I'm not sure what you mean by this. What do you mean by "getting release messages"? Normally you are the one sending the release messages. What's happening when you run the application?

Soulstorm
Jun 13, 2006, 06:27 AM
Hmm, I'm not sure what you mean by this. What do you mean by "getting release messages"? Normally you are the one sending the release messages. What's happening when you run the application?
My question was why I am not getting the messages "Account deallocation..." at the end of the program. As you can see, I have written at the -dealloc method of the account class to print such a message, so that I see when the dealloc method of the account class is called.

I changed the Bank's class -dealloc method to release[/b] the array, and not [i]deallocate it directly. That fixed my problem and answered my question.

Thanks a lot for your responses, HIRez!

GeeYouEye
Jun 13, 2006, 02:41 PM
Just as an experiment you might want to try, see if you can use NSInputStream (http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSInputStream_Class/Reference/Reference.html) with /dev/stdin as the file path. Just append any data that comes in to an NSMutableData.

loonman
Apr 3, 2010, 08:59 AM
I'm not sure anyone is still reading this thread, but i have the answer you need.
you need to know how standard in works on unix, which is the base of Mac OSX.
In UNIX, everything is a "file".

so:
/System/Library/Frameworks/Foundation.framework
#import NSFileHandle.h

NSData* MyData = [[NSFileHandle fileHandleWithStandardInput]
availableData];

NSString* StdInData = [[NSString alloc] initWithData:MyData
encoding:NSASCIIStringEncoding];