Unwanted variable release (iOS) - iphone

I have UIViewController with strong DataController containing and managing a list of items (itemsList). In the initialization method of the latter, I read items from a file, get additional information for each of these via web services (and ASIHTTPRequest) and put the items in a list which is then assigned to the itemsList property.
With synchronous requests everything works fine but I need an asynchronous implementation. I did it and now my items get deallocated (the data controller not), following the delegates for the requests etc disappear as well. How can I keep the items alive?
In my data controller:
-(id)initDataController
{
self = [super init];
if (self)
{
NSMutableArray *myList = [[NSMutableArray alloc] init];
// Read items from file ...
for (NSString *itemName in items) {
MyItem *item = [[MyItem alloc] initWithParam:itemName];
// Here the item has already been deallocated?!
if (item) {
[myList addObject:item];
}
}
_itemsList = myList;
return self;
}
return nil;
}
In MyItem class there is just standard request to the server and initialization. I think the problem should be in some missing strong/retain but I have no idea where I should put it.
EDIT:
The definition of the list:
#property (nonatomic, copy) NSMutableArray *itemsList;
What I am wondering is that the items are nil even before I can put them into the list... So I tried making the property strong instead of copy but nothing changes.

MyItem *item = ...
This local variable forms a strong reference. If initWithParam: returns an object (not nil), there'w no way it can be deallocated before being added to the list (which creates another strong reference).
Please take note that your property declaration is flawed:
#property (nonatomic, copy) NSMutableArray *itemsList;
The copy attribute does not go well with a mutable object type: When the property is assigned, the synthesized setter calls copy on it, which creates an immutable copy of the array and assigns that. This contradicts the type of the property.
Here's in fact a nice opportunity for another helpful clang compiler warning: Properties cannot be declared copy when their type conforms to NSMutableCopying. clang team, are you hearing?.

Related

Potential leak of an object allocated on line ##

I am working on an iphone application, and although I thought I had a good understanding of memory management, I'm seeing some issues after using the Xcode Analyze function. I have looked at many of the existing questions I could find on here, but I couldn't find one that was similar to my situation.
CustomerDetailController.h
#interface CustomerDetailController : UITableViewController {
PTCustomer *customer;
}
#property (nonatomic, retain) PTCustomer *customer;
- (id)initWithCustomer:(PTCustomer *)aCustomer;
CustomerDetailController.m
#synthesize customer;
- (id)initWithCustomer:(PTCustomer *)aCustomer {
if ((self = [super initWithStyle:UITableViewStyleGrouped]))
{
if (aCustomer != nil)
self.customer = aCustomer;
else
self.customer = [[PTCustomer alloc] init]; // This line shows Potential leak of an object allocated on line ##
}
return self;
}
If I click on the item marked by the Analyzer, it then says:
Method returns an Objective-C object with a +1 retain count
Object leaked: allocated object is not referenced later in this execution path and has a retain count of +1
My reasoning behind this is that if a PTCustomer object was not passed in, I want to initialize a new one so that I have it available elsewhere within this class.
What is the correct way to do this?
Thanks.
self.customer is being over-retained.
+1 for alloc of customer
+1 when the property setter retains customer.
Do not retain customer, the property setter will retain it.
Just:
self.customer = [[[PTCustomer alloc] init] autorelease];
But given that this is an init method there is a strong argument that the ivar should be assigned directly:
customer = [[PTCustomer alloc] init];
The other option is to assign the retained object directly to customer rather than to self.customer. This bypasses the auto-retain logic in the setCustomer method. However, if you do that you must assure that any prior object referenced by customer is released (eg, by assigning nil to self.customer).
(Because bypassing the setter in this way is a somewhat irregular technique some folks frown on it.)
Are you releasing your customer ivar in the dealloc? If not, there's your leak.

creating a Mutable array that can be added to in later clicks of the same button?

General noob questions:
(1) How can I create an NSMutable array in a buttonClicked action that I can add more entries to during subsequent clicks of the same button? I always seem to start over with a new array at every click (the array prints with only 1 entry which is the most recent button's tag in an NSLog statement).
I have about 100 buttons (one for each character in my string called "list") generated by a for-loop earlier in my code, and each has been assigned a tag. They are in a scrollview within the view of my ViewController.
I wish to keep track of how many (and which ones) of the buttons have been clicked with the option of removing those entries if they are clicked a second time.
This is what I have so far:
-(void) buttonClicked:(UIButton *)sender
NSMutableArray * theseButtonsHaveBeenClicked = [[NSMutableArray alloc] initWithCapacity: list.length];
NSNumber *sendNum = [NSNumber numberWithInt:sender.tag];
[theseButtonsHaveBeenClicked addObject:sendNum at index:sender.tag];
NSLog(#"%#",theseButtonsHaveBeenClicked);
}
(2) I have read that I may be able to use a plist dictionary but I don't really understand how I would accomplish that in code since I cant type out the items in the dictionary manually (since I don't know which buttons the user will click). Would this be easier if I somehow loaded and replaced the dictionary in a plist file? And how would I do that?
(3) I also have no idea how I should memory manage this since I need to keep updating the array. autorelease?
Thanks for any help you can provide!
Okay, firstly you are creating a locally scoped array that is being re-initialised on every call to buttonClicked:. The variable should be part of the class init cycle.
You will also be better off with an NSMutableDictionary instead of an NSMutableArray. With a dictionary we don't have to specify capacity and we can use the button's tags as dictionary keys.
Here's what you need to do, these three steps always go together: property/synthesize/release. A good one to remember.
//Add property declaration to .h file
#property (nonatomic, retain) NSMutableDictionary * theseButtonsHaveBeenClicked;
//Add the synthesize directive to the top of .m file
#synthesize theseButtonsHaveBeenClicked;
// Add release call to the dealloc method at the bottom of .m file
- (void) dealloc {
self.theseButtonsHaveBeenClicked = nil; // syntactically equiv to [theseButtonsHaveBeenClicked release] but also nulls the pointer
[super dealloc];
}
Next we create a storage object when the class instance is initialised. Add this to your class's init or viewDidLoad method.
self.theseButtonsHaveBeenClicked = [[NSMutableDictionary alloc] dictionary]; // convenience method for creating a dictionary
And your updated buttonClicked: method should look more like this.
-(void) buttonClicked:(UIButton *)sender {
NSNumber *senderTagAsNum = [NSNumber numberWithInt:sender.tag];
NSString *senderTagAsString = [[NSString alloc] initWithFormat:#"%#",senderTagAsNum];
// this block adds to dict on first click, removes if already in dict
if(![self.theseButtonsHaveBeenClicked objectForKey:senderTagAsString]) {
[self.theseButtonsHaveBeenClicked setValue:senderTagAsNum forKey:senderTagAsString];
} else {
[self.theseButtonsHaveBeenClicked removeObjectForKey:senderTagAsString]; }
[senderTagAsString release];
NSLog(#"%#", self.theseButtonsHaveBeenClicked);
}

Objective-C method to nullify object

i have some trouble writing a method in Objective-C to make an object nil. Here is some example :
#interface testA : NSObject
{
NSString *a;
}
#property (nonatomic, retain) NSString *a;
+(testA*)initWithA:(NSString *)aString;
-(void)displayA;
-(void)nillify;
#end
#implementation testA
#synthesize a;
+(testA*)initWithA:(NSString *)aString{
testA *tst=[[testA alloc] init];
tst.a=aString;
return [tst autorelease];
}
-(void)displayA{
NSLog(#"%#",self.a);
}
-(void)nillify{
self=nil;
}
- (void)dealloc {
[a release];
[super dealloc];
}
#end
int main(int argc, char **argv){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
testA *test=[testA initWithA:#"some test"];
[test displayA];
test=nil;
//[test nillify];
NSLog(#"after setting to nil");
[test displayA];
[pool release];
return 0;
}
Apparently , when I set test object to nil and then call some method on it nothing happens , but if i call nillify instead of directly setting it to nil , displayA method works normally like test object is still there. Is there a workaround for nillify method to function properly ?
Your help is much appreciated !
You can't actually do something like this, because setting 'self' to nil only has any effect within the scope of that method (in your case, 'nilify'). You don't have any actual way to effect the values of pointers located on other parts of the stack or in random places in the heap, for example.
Basically any code that holds a reference to some object is responsible for maintaining and clearing those references itself. If you have some use case where random sections of code may need references to "live" objects of some kind, but where you'd want those object references to go away in response to some external event (maybe a user tracking system or something), you could do something with notifications, but the various modules tracking those "live" objects would still be responsible for listening for notifications and cleaning up references when they received them.
The 'nilify' thing, however, can't possibly work.
You cannot do what you're trying to do. self is just a local reference to an object that actually exists elsewhere. Setting it to nil doesn't mean anything. An object doesn't, in general, own itself, and it certainly doesn't control other objects' references to it. It's up to the owning objects to manage its lifetime.
There are a few things wrong with your code.
First, by convention, class names start with an uppercase letter. Please stick to these naming conventions as it will make it harder for other developers to work with your code (and even confuse you).
Next, your initWithName:... According to the naming conventions, a method with init in its name should be an instance method, not a class method. So either name it newWithName: or turn it into an instance method like this:
-(testA*)initWithA:(NSString *)aString{
self = [super init];
if (!self) return nil;
tst.a=aString;
return self;
}
If you keep it as class method (and name it newWithName:) you should not return a autoreleased object since according to the naming conventions method that start with init... or new... return a retained object. If you do not follow these conventions, the static analyzer will give you "false" warnings and it will become useless for you.
Now for the reason your nillify doesn't work: the self is in fact an argument to a method. Under the hood, your nillify method actually has two arguments that you do not see: the self pointer and the selector pointer. This means, self is actually a variable on the stack. And if you overwrite it, you only overwrite that stack variable but that doesn't influence your test variable which is somewhere else.
As an example, consider a method - (void)foo:(NSString *)bar;. The compiler turns it into the equivalent of the C function (void) foo(id self, SEL _cmd, NSString *bar).

Filling an NSMutableArray with a Set of Classes and Then Getting them Back

Hopefully I can make this clear, but I am new to Objective-C and to be honest not great with Arrays in the first place.
So, I have a Singleton class (called SingletonState) that I am using to pass variables around my app (please can we leave the whether I should use Singleton classes out of this - I will fix that later). In this class, I have an NSMutableArray (called arrayMyEvents). I have also created a class that I am going to store a list of events (called EventClass). When the user logs in, I call a web service and get back 3 strings. The 3rd string is a comma separated list of value. I parse the data and populate the custom class EventClass. I then add this class to the SingletonState.arrayMyEvents.
I have all of this working. I can go to another ViewController and access the "count" of items in arrayMyEvents.
PROBLEM: I now want to edit one of the ScheduledEventsClass"es" in my array. How do I get access to it and edit some of the properties and update the one in my SingletonState class?
Here is some of the code, that I've tried:
NSString *sWebServiceEvents = [[NSString alloc] initWithFormat:#"%#", [result objectAtIndex:2]];
if ( [ sWebServiceEvents isEqualToString:#"NULL" ] != true ) {
NSArray *arrayEvents = [sWebServiceEvents componentsSeparatedByString:#","];
// If the array has not been initialized they initialize it.
if (sharedState.arrayMyEvents == nil) {
sharedState.arrayMyEvents = [[NSMutableArray alloc ] init ];
}
for (NSString * sEvent in arrayEvents) {
// Set equal to the value of the array (the Event Number) at the same
// position as the row that we are being asked to return a cell/row for.
EventClass *eventClass = [[EventClass alloc] retain];
eventClass.sEvent = sEvent;
[ sharedState.arrayEvents addObject:eventClass ];
}
NSLog(#"LoginView - sharedState.arrayMyEvents Count: %d", [sharedState.arrayMyEvents count]);
}
Here is me trying to access it in another ViewController:
EventClass *eventClass =
[sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"eventClass.sEventNumber: ", eventClass.sEventNumber);
eventClass.sLocation = #"Jason's Big Location";
You're going to have some memory leaks from the sEvent loop. [[EventClass alloc]retain] leaves you an uninitialized EventClass object with a reference count of 2. You'll need to change that to [[[EventClass alloc] init] autorelease] to keep it from leaking. The arrayEvents NSMutableArray will retain it during the addObject: call. (Shouldn't that be [sharedState.arrayMyEvents addObject: eventClass] in the loop?)
After that, all you have to do to edit the EventClass object in the second block of code is edit it. The eventClass variable is a pointer to an object in the array. Anything done to that object doesn't affect the pointer referencing it, it affects data referenced by it. The code you have in the second block should change the sLocation of the selected object as you intend.
You have a few more memory leaks in there, too. Use Cmd-Shift-A to build with the static analyzer and it'll tell you where.
Maybe the problem is that you put them in sharedState.arrayEvents but try to take them out of sharedState.arrayMyEvents. Different variables.
Also, lots of memory leaks.
Thanks John and St3fan, your answers and time are appreciated!!!
I think that I figured out my issue:
Basically, the class that I created (EventClass) had the properties setup like this:
#property (nonatomic, assign) NSString *sStudyNumber;
#property (nonatomic, assign) NSString *sTheater;
but, they should be (or at least I got it to work like this):
#property (nonatomic, retain) NSString *sStudyNumber;
#property (nonatomic, retain) NSString *sTheater;
Then, in my second view I was able to do this:
EventClass *eventClass = [sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"MyEvents: %#", eventClass.sEventNumber);
eventClass.sLocation = #"Jason's Big Location";
I then checked it in another method of the view using this and it was still there:
EventClass *eventClass = [sharedState.arrayMyEvents objectAtIndex:row ];
NSLog(#"MyEvents: %#", eventClass.sEventNumber);
NSLog(#"MyEvents: %#", eventClass.sLocation);
I also, checked it in yet another view and the value was maintained in the SharedState.arrayMyEvents without issue. :)
In the end, I believe that I boiled down to the difference between "assign" and "retain".
Now, on to the memory leaks :(
Please, let me know if you see any other issues with this.
Thanks,
Jason

Can't add or remove object from NSMutableSet

Check it:
- (IBAction)toggleFavorite {
DataManager *data = [DataManager sharedDataManager];
NSMutableSet *favorites = data.favorites;
if (thisEvent.isFavorite == YES) {
NSLog(#"Toggling off");
thisEvent.isFavorite = NO;
[favorites removeObject:thisEvent.guid];
[favoriteIcon setImage:[UIImage imageNamed:#"notFavorite.png"] forState:UIControlStateNormal];
}
else {
NSLog(#"Toggling on, adding %#", thisEvent.guid);
thisEvent.isFavorite = YES;
[favorites addObject:thisEvent.guid];
[favoriteIcon setImage:[UIImage imageNamed:#"isFavorite.png"] forState:UIControlStateNormal];
}
NSLog(#"favorites array now contains %d members", [favorites count]);
}
This is fired from a custom UIButton. The UI part works great--toggles the image used for the button, and I can see from other stuff that the thisEvent.isFavorite BOOL is toggling happily. I can also see in the debugger that I'm getting my DataManager singleton.
But here's my NSLog:
2010-05-13 08:24:32.946 MyApp[924:207] Toggling on, adding 05db685f65e2
2010-05-13 08:24:32.947 MyApp[924:207] favorites array now contains 0 members
2010-05-13 08:24:33.666 MyApp[924:207] Toggling off
2010-05-13 08:24:33.666 MyApp[924:207] favorites array now contains 0 members
2010-05-13 08:24:34.060 MyApp[924:207] Toggling on, adding 05db685f65e2
2010-05-13 08:24:34.061 MyApp[924:207] favorites array now contains 0 members
2010-05-13 08:24:34.296 MyApp[924:207] Toggling off
2010-05-13 08:24:34.297 MyApp[924:207] favorites array now contains 0 members
Worst part is, this USED to work, and I don't know what I did to break it.
--EDIT: By request, my shared data singleton code:
.h
#import <Foundation/Foundation.h>
#interface DataManager : NSObject {
NSMutableArray *eventList;
NSMutableSet *favorites;
}
#property (nonatomic, retain) NSMutableArray *eventList;
#property (nonatomic, retain) NSMutableSet *favorites;
+(DataManager*)sharedDataManager;
#end
.m:
#import "DataManager.h"
static DataManager *singletonDataManager = nil;
#implementation DataManager
#synthesize eventList;
#synthesize favorites;
+(DataManager*)sharedDataManager {
#synchronized(self) {
if (!singletonDataManager) {
singletonDataManager = [[DataManager alloc] init];
}
}
return singletonDataManager;
}
- (DataManager*)init {
if (self = [super init]) {
eventList = [[NSMutableArray alloc] init];
favorites = [[NSMutableSet alloc] init];
}
return self;
}
------EDIT EDIT EDIT EDIT------
At #TechZen's suggestion, I moved my accessor methods into the data manager singleton. Here's what it now looks like:
#import "DataManager.h"
static DataManager *singletonDataManager = nil;
#implementation DataManager
#synthesize eventList;
#synthesize favorites;
+(DataManager*)sharedDataManager {
#synchronized(self) {
if (!singletonDataManager) {
singletonDataManager = [[DataManager alloc] init];
}
}
return singletonDataManager;
}
- (DataManager*)init {
if (self = [super init]) {
eventList = [[NSMutableArray alloc] init];
favorites = [[NSMutableSet alloc] init];
}
return self;
}
#pragma mark -
#pragma mark Data management functions
- (void)addToFavorites:(NSString *)guid
{
[self.favorites addObject:guid];
NSLog(#"Item added--we now have %d faves.", [favorites count]);
}
- (void)removeFromFavorites:(NSString *)guid
{
[favorites removeObject:guid];
NSLog(!"Item removed--we now have %d faves.", [self.favorites count]);
}
#end
I made my viewcontroller where this is happening call [[DataManager sharedManager] addToFavorites:Event.guid] instead of adding the item right to the favorites set itself, but I left the logging stuff that was there in place.
Here's my log:
2010-05-13 13:25:52.396 EverWondr[8895:207] Toggling on, adding 05db685f65e2
2010-05-13 13:25:52.397 EverWondr[8895:207] Item added--we now have 0 faves.
2010-05-13 13:25:52.398 EverWondr[8895:207] favorites array now contains 0 members
2010-05-13 13:25:53.578 EverWondr[8895:207] Toggling off
2010-05-13 13:25:53.579 EverWondr[8895:207] favorites array now contains 0 members
So.... the DataManager object can't even add anything to its own property! And it doesn't throw an exception like it would if it was a non-mutable type, it just silently fails!
Just for fun, I went through and changed it to an NSMutableArray, which I'm more familiar with. Same behavior.
As phellicks suggest above, you might not be returning a mutable set from data.favorites. Although, you should be getting a compiler warning if that is the case.
This 05db685f65e2 is not a real guid. It looks more like the address of an object. You should check the type on 'thisEvent.guid` to make sure your got an object and the correct type of object.
Unrelated to your main problem, I would add that (1) this:
NSMutableSet *favorites = data.favorites;
... is rather pointless and just adds another possible source of error. There is no reason not to just use data.favorites directly in the code. (see (3) below)
(2) When accessing an external object, even a singleton, it is good practice to make the reference to the external object a property of the class especially in the case of a critical object like a data model. This lets you control and track access to the external object.
(3) Don't treat singletons as naked global variables. This will lead to grief. Instead, wrap access to the data models internal data in specific methods. For example, instead of accessing the data.favorites directly create a method like:
- (void) addToFavoritesGuid:(id) aGuid;
or
- (void) addToFavoritesGuid:(GuidClass *) aGuid;
This will give your data model control over its internals and give it the ability to refuse to add objects that shouldn't belong there.
Edit
From comments:
Okay, re what I'm actually
returning... I just used debug to step
through my singleton's initializer.
Examining the ivars of my DataManager
object, I see that my favorites, which
is initialized in init with favorites
= [[NSMutableSet alloc] init]; is actually getting created as a NSCFSet,
and I don't know what that is nor what
to make of it..
NSSet like all the collections and strings is actually a class cluster i.e. a collection of subclasses that all share the same interface. When you create a set the actual class you get back maybe different depending on how it was created. In this case, you're getting back NS-Core-Foundation-Set which is the standard core class for NSSet.
Therefore, your problem is that favorites is initialized as a mutable set but is being assigned to a immutable set. This is why you can't add anything to it.
This initialization:
favorites = [[NSMutableSet alloc] init];
... is being disposed of by:
NSMutableSet *favorites = data.favorites;
If you have an instance variable and you create a local variable of the same name, the local symbol will dominate in the scope it was created in. This appears to work because as a subclass of NSSet, NSMutableSet responds to all the methods and attributes of NSSet.
However, you must be getting a spate of warnings from your linker when you build. You shouldn't ignore those errors. You should treat them as fatal errors because that's what they will be at runtime.
To resolve your problem:
(1) Declare data.favorites as a mutable array and just access it directly. Having another local variable assigned to the same address buys you nothing.
(2) Declare favorites as mutable array property of the current object. Initialize it from data.favorites like:
self.favorites=[NSMutableSet setWithCapacity:[data.favorites count]];
[self.favorites setSet:data.favorites];
// ... add or remove items
data.favorites = self.favorites;
(3) Move all the logic for adding or removing objects in data.favorites to custom methods in the data model object (see above)
Three is the best choice.
Edit02
It looks like the class clusters are hiding the true classes of all classes in the cluster. I ran the following test code:
- (void)applicationDidFinishLaunching:(UIApplication *)application {
NSSet *s=[NSSet setWithObject:#"setWithObject"];
NSMutableSet *m=[NSMutableSet setWithCapacity:1];
[m addObject:#"Added String"];
NSMutableSet *n = [[NSMutableSet alloc] initWithCapacity:1];
[self showSuperClasses:s];
[self showSuperClasses:m];
[self showSuperClasses:n];
[self showSuperClasses:#"Steve"];
}
- (void) showSuperClasses:(id) anObject{
Class cl = [anObject class];
NSString *classDescription = [cl description];
while ([cl superclass])
{
cl = [cl superclass];
classDescription = [classDescription stringByAppendingFormat:#":%#", [cl description]];
}
NSLog(#"%# classes=%#",[anObject class], classDescription);
}
... and got this output:
NSCFSet classes=NSCFSet:NSMutableSet:NSSet:NSObject
NSCFSet classes=NSCFSet:NSMutableSet:NSSet:NSObject
NSCFSet classes=NSCFSet:NSMutableSet:NSSet:NSObject
NSCFString classes=NSCFString:NSMutableString:NSString:NSObject
Clearly, the report from the debugger and the class function are useless in figuring out the true class of any instance that belongs to cluster. It didn't used to be this way. This is a recent change. I presume its part of the "toll-free bridging" from Core Foundation.
You can add items to favorites because all definitions of favorites in both classes are NSMutableSet.
In any case, your problem is that you have two separate definitions of favorites in the same class. You are getting a warning from the linker saying:
Local declaration of "favorites" hides instance variable
I think the problem can be explained by the runtime confusing the two favorites. You add objects to one favorites but you log the other one.
The local redefinition of favorites serves absolutely no purpose. Remove it and see if the problem persist.