So I'm having trouble with OOAD, properties, the self keyword, etc. I wanted to just create a simple test project that has a UITableView. I have an ivar of
NSArray *tableData;
how would I write a setter and getter method for this? I thought my setter would look like:
- (void)setTableData:(NSArray *)array {
[tableData autorelease];
tableData = [array retain];
}
Then when I try to use this method in my viewDidLoad, I realize that I need to create an instance of my ViewController. This seems like what not to do when I look at how it's done in books where they create a property for the NSArray, then in viewDidLoad just do a
NSArray *array = [[NSArray alloc] initWithObjects:#"1", #"2", #"3", #"4", nil];
self.tableData = array;
[array release];
I'm kind of just trying to understand what goes on behind the scenes, to try to understand OOAD principles, ivars, properties, self, etc. Thanks in advance.
I'm sure you already know that using #synthesize will create setter/getter methods for you, but it's good to know what's going on "under the hood" to understand the concepts.
As far as a setter method goes, you're probably better off with something like this:
- (void)setTableData:(NSArray *)array {
if (tableData != array) {
[tableData release];
tableData = [array retain];
}
}
This basically checks to make sure the new array is actually different than the current. If it is, it releases the old instance and sets the new one.
For a getter method, just use:
- (NSArray *)tableData (
return tableData;
}
This can be accessed by calling self.tableData. And of course setting the array is done just as you have done, with self.tableData = array;
I hope that helps. If you need more information, just say so and I'll be happy to try and explain further
Related
Currently developing my first Native iPhone application, the application is going to be integrating within an existing .net application and will be using webServices. However the problem i am facing i believe is nothing more than my understanding of xcode.
I hope i can explain this...
OK within my first view, within the viewDidLoad i go off to the webservice and return my list of items (these populate the tableview) this works completely fine:
--- snippet ---
- (void)viewDidLoad {
//GET ALL ITEMS
myArray = [[NSMutableArray alloc] init];
MyWebService *webService = [[MyWebService alloc] init];
myArray = [webService getAllNewsFunction];
[super viewDidLoad];
}
--- snippet ---
Ok so i now have my tableview populated and awaiting for you to select your item (in this case a news article) i need to determine the selectedItem in order to populate the next view with the details of the article. However it appears that within the didSelectRowAtIndexPath method my array list is no longer accessible. I am very confused at this point due to if i simply create a list of items within my viewDidLoad within the current view without using my webservice for example:
--- snippet ---
// listOfItems = [[NSMutableArray alloc] init];
// [myArray addObject:#"Iceland"];
// [listOfItems addObject:#"Greenland"];
// [listOfItems addObject:#"Switzerland"];
// [listOfItems addObject:#"Norway"];
// [listOfItems addObject:#"New Zealand"];
// [listOfItems addObject:#"Greece"];
// [listOfItems addObject:#"Italy"];
// [listOfItems addObject:#"Ireland"];
--- snippet ---
the above is accessible within didSelectRowAtIndexPath and i can populate the detailview.
Can you please help me pinpoint the problem i am facing and how this can be solved. I understand i have given you the very basics so if i need to provide more information i am happy to do so.
-- update --
ok based on your response i have update the following.
I have now declared myArray as a property
#property (nonatomic, retain) NSMutableArray *myArray;
and also updated the line to:
self.myArray = [webService getAllNewsFunction];
-- update --
however i now receive an error "program received signal: "SIGABRT"" on the self.myArray line.
any ideas?
Thanks Again
If you are following naming conventions then the method [webService getAllNewsFunction] will return an autoreleased array. Therefore when you come to access it again it will most likely have been released already.
If you have used a #property to declare myArray (which you should to save yourself from these problems) then you can resolve this by doing:
self.myArray = [webService getAllNewsFunction];
This line:
myArray = [[NSMutableArray alloc] init];
is also superflous and causing a memory leak as you are reassigning the value of myArray to [webService getAllNewsFunction] immediately after without releasing the new NSMutableArray you alloc/init'd
UPDATE
So from reading your update I think you need to firstly look at the warnings your project now has which will probably read something like:
Property 'myArray' requires method 'setMyArray' to be defined - use #synthesize, #dynamic or provide a method implementation in ...
The next clue to the problem appears in the console I get something like this
-[TestAppDelegate setMyArray:]: unrecognized selector sent to instance ...
So what it all boils down to is you either need to provide the methods
- (NSMutableArray *)myArray;
- (void)setMyArray:(NSMutableArray *)myArray;
or let the compiler do it for you by adding a #synthesize statement like this
.m
#implementation YourClass
#synthesize myArray;
// The rest of your class methods
- (void)dealloc
{
// release other ivars
[myArray release];
[super dealloc];
}
The synthesize is the easier, quicker options and the compiler will arrange for the coorect memory management depending on which options you use in your #property declaration.
I have a property in my class, which is an NSArray. I am retaining the property.
My question is, what is the proper way to add objects to that array without leaking and making the retain count too high?
This is what I am using:
.h:
NSArray *foodLocations;
#property (nonatomic, retain) NSArray *foodLocations;
// I make sure to synthesize and release the property in my dealloc.
.m
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *tempFood = [[NSArray alloc] initWithArray:[self returnOtherArray]];
self.foodLocations = tempFood;
[tempFood release];
}
Is this the correct way to do it?
Yes this is correct and my preferred way of doing it as it renders the code more readable.
You are essentially allocating a temporary array and then assigning it to your property with a retain attribute, so it is safe to dealloc it as your property now "owns" it. Just remember that you still need to release it in your dealloc method.
You could also initialise the array and assign it to the property in the view controllers init method, depending on whether you need the property to be available to you before the view actually loads (i.e. in case you want to read the value of the property before pushing the view controller etc...)
you will typically want to declare the property copy in this case.
in most cases, immutable collection accessors should be copy, not retain. a lot of people get this wrong, and end up writing a lot of copying manually and sharing objects which should not be shared, thinking they are doing themselves good by cutting a corner.
copying in this form (the collection) is shallow. the objects in the array are not copied, just the array's allocation.
a good implementation of an immutable collection can simply implement copy by retaining self. if the argument is mutable, you want a copy anyhow (in the majority of cases).
your program is then simplified to a declaration of:
// note: copy, not retain. honor this if you implement the accessors.
#property (nonatomic, copy) NSArray * foodLocations;
and then the setter:
self.foodLocations = [self returnOtherArray];
of course, you must still init, dealloc, and handle thread-safety appropriately.
good luck
That looks fine. You don't actually need the tempFood variable, you can just do:
self.foodLocations = [[NSArray alloc] initWithArray:[self returnOtherArray]];
[self.foodLocations release];
or:
self.foodLocations = [[[NSArray alloc] initWithArray:[self returnOtherArray]] autorelease];
Or:
#synthesize foodLocations=_foodLocations;
then in code
_foodLocations = [[NSArray alloc] initWithArray:someOtherArray];
This avoids the autorelease required by
self.foodLocations = [[[NSArray alloc] initWithArray:someOtherArray] autorelease];
Yes, that is correct. Also good to keep in mind is what #synthesize is, in effect, doing for you. A synthesized (& retained) setter is functionally equivalent to the following code:
- (void)setVar:(id)_var {
[_var retain];
[var release];
var = _var;
[var retain];
[_var release];
}
So, basically, every time you call self.var = foo, it releases the previously stored value and retains the new one. You handle the reference counting in your code, and the setter handles its own.
I have a mutableArray that I fill up with objects. When I try to refill the array, I first use removeAllObjects - which produces a memory leak...
The properties of the object are synthesized, retained and released on dealloc.
The Array is initialized on viewDidLoad like this:
theArray = [[NSMutableArray alloc] initWithCapacity:10];
... and it's retained and synthesized. (#property (nonatomic, retain) NSMutableArray *theArray)
I'm adding the objects in a while-loop like this:
myObject *theObject = [[myObject alloc] init];
theObject.someProperty = #"theprop";
[theArray addObject: theObject];
[theObject release];
then on the next call of the method, I remove all objects like this:
[theArray removeAllObjects];
That's where the leak occurs. If I comment this line out, the leak doesn't appear. So I guess I'm doing something wrong in my object?
Seems like the problem is solved...
a) I didn't realize, that when I use instruments, the app isn't compiled before launch - thus, some of the changes I made were not taking into effect, when using instruments. So now I first build and run after a change and then run it in instruments.
b) thus, I don't really know what solved the problem. But it might be that I had the dealloc-method in my object wrong.
I was using:
[super dealloc];
[myProperty release];
instead of the other way around:
[myProperty release];
[super dealloc];
Thanks for the help, though!
Does myObject have any retained properties? If so, are you setting them to nil in the dealloc message? If not, when it is dealloced it won't release the objects that its properties are set to.
#property (nonatomic, retain) NSMutableArray *filteredListContent;
----
#synthesize filteredListContent;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSMutableArray *test = [[NSMutableArray alloc] init];
[test addObject:#"test string"];
[filteredListContent addObjectsFromArray:test];
NSLog(#"%#", test);
NSLog(#"Filtered Array is %#", filteredListContent);
[window makeKeyAndVisible];
}
My Log for test shows 'test string' but 'Filtered list array is (null)'
How do I set the array 'filteredListContent' with the array test...
What am I doing wrong? :-(
Are you creating and initializing filtersListContent anywhere? Your code looks right, that should work.
You should also make sure to release your test variable, you have a memory leak here.
You have to actually create filteredListContent, say with [[NSMutableArray alloc] init]. The error you are getting is that you are calling a method, -addObjectsFromArray:, on an object that is still nil: never created. As such, it just returns nil, and the list is never filtered.
filteredListContent is a pointer to a NSMutableArray, it does not have any memory assigned to it, as a result you cannot call methods on it. The compiler does not generate an error because you are passing a message to nil which is perfectly alright.
Thanx for that.
so I changed the line...
[filteredListContent addObjectsFromArray:test];
to read...
filteredListContent = [NSMutableArray arrayWithArray:test];
This done it. I think I understand it now, though I declared it, I never created it...
Thanx.
I'm working on an iphone application and having some trouble with memory leaks. I've read some docs about garbage collection that it make it sound simple but I must be missing something. I've got a viewController that needs access to an array which may need to repopulated from time to time. Here is a simplified version of what I have:
//AppDelegate.m
- (NSMutableArray *)getMathFacts {
//Some database stuff
NSMutableArray * arr = [[NSMutableArray alloc] init];
while (sqlite3_step(math_fact_statement) == SQLITE_ROW) {
[arr addObject:[[NSNumber numberWithInt:sqlite3_column_int(math_fact_statement, 0)] autorelease]];
}
return arr;
}
//ViewController.h
#interface ReviewViewController : UIViewController {
NSMutableArray *reviewArr;
}
#property (retain, nonatomic) NSMutableArray *reviewArr;
//ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[self loadMathFacts];
}
- (void)loadMathFacts {
self.reviewArr = [appDelegate getMathFacts];
}
- (void)loadAllMathFacts {
self.reviewArr = [appDelegate getAllMathFacts];
}
-(IBAction) getAll {
[reviewArr release];
[self loadAllMathFacts]
}
GetAllMathFacts is similar to getMathFacts, it just has a different SQL statement.
When I run this checking for Leaks it is like a sieve. It seems like something simple, but I feel like I've tried everything and it just moves the leak around.
Any advice would be appreciated.
Thanks
The iPhone OS actually doesn't have garbage collection. What you're doing with retain/release is called reference counting.
The solution to your problem is probably to make getMathFacts return an autoreleased object (change return arr; to return [arr autorelease];), because the definition of the property reviewArr is probably something like #property (retain) NSArray *reviewArr;, which means every time you call self.reviewArr = something;, something is retained, which means after you set reviewArr in loadMathFacts and loadAllMathFacts, reviewArr is retained one time too much.
In getMathFacts, you do a
NSMutableArray * arr = [[NSMutableArray alloc] init];
that array is owned by you. It has a retain count of 1. Later when
- (void)loadMathFacts {
self.reviewArr = [appDelegate getMathFacts];
}
that same array is now retained by reviewArr and the retain count goes to 2.
When you then do a
-(IBAction) getAll {
[reviewArr release];
[self loadAllMathFacts]
}
in the first release statement, your array is now getting released once and the retain count goes to 1. In [self loadAllMathFacts]
- (void)loadAllMathFacts {
self.reviewArr = [appDelegate getAllMathFacts];
}
self.reviewArr will release the the array, before retaining a new array. After this release the retain count is down to 0. I don't see a leak here. Maybe in -getAllMathFacts?
Now, one thing I would change to make things look a little better is this:
- (void)loadMathFacts {
NSMUtableArray array = [appDelegate getMathFacts];
self.reviewArr = array;
[array release];
}
- (void)loadAllMathFacts {
NSMUtableArray array = [appDelegate getAllMathFacts];
self.reviewArr = array;
[array release];
}
-(IBAction) getAll { // you don't need to release in here
[self loadAllMathFacts]
}
Plus you should rename your get method calls to use "new" instead of "get" since the convention is that if you return something that is owned by the caller, it should have new or copy in the name of the method.
As another answerer said, you could use autorelease, although I'd rather use autorelease in other situations. But if you were to do it with autorelease this is how I'd do it:
//AppDelegate.m
- (NSMutableArray *)getMathFacts {
//Some database stuff
NSMutableArray * arr = [[NSMutableArray alloc] init];
while (sqlite3_step(math_fact_statement) == SQLITE_ROW) {
[arr addObject:[[NSNumber numberWithInt:sqlite3_column_int(math_fact_statement, 0)] autorelease]];
}
return [arr autorelease];
}
do the same with -getAllMathFacts. You should still change the code to be more like above, except you don't have to release after doing the self.reviewArray, and you don't have to change the name of the methods. What you have to remember is that if even autoreleased objects can leak if you call retain on them and then forget about them. The nice thing about autorelease is that the object is kept around for the duration of a runloop, long enough to hand it to some other object and let them decide whether they want to retain it or let it expire.
I hope I haven't missed anything. Go through my logic, and feel free to poke holes in it or ask questions. I can easily have missed something.
By the way, self.reviewArr when you have:
#property (retain, nonatomic) NSMutableArray *reviewArr;
does the following:
- (void)setReviewArr:(NSMutableArray*)array
{
NSMutableArray* oldReviewArr = reviewArr;
reviewArr = [array retain];
[oldReviewArr release];
}
As of Xcode 3.2 there is support for running the Clang analyzer. To do this choose Build->Build & Analyze. This will run the analyzer which is really a wonderful tool for finding reference counting issues.
For Xcode prior to 3.2, this might help: Using Clang Static Analyzer from within XCode