I'm almost there understanding simple reference counting / memory management in Objective-C, however I'm having a difficult time with the following code. I'm releasing mutableDict (commented in the code below) and it's causing detrimental behavior in my code. If I let the memory leak, it works as expected, but that's clearly not the answer here. ;-) Would any of you more experienced folks be kind enough to point me in the right direction as how I can re-write any of this method to better handle my memory footprint? Mainly with how I'm managing NSMutableDictionary *mutableDict, as that is the big culprit here. I'd like to understand the problem, and not just copy/paste code -- so some comments/feedback is ideal. Thanks all.
- (NSArray *)createArrayWithDictionaries:(NSString *)xmlDocument
withXPath:(NSString *)XPathStr {
NSError *theError = nil;
NSMutableArray *mutableArray = [[[NSMutableArray alloc] init] autorelease];
//NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init];
CXMLDocument *theXMLDocument = [[[CXMLDocument alloc] initWithXMLString:xmlDocument options:0 error:&theError] retain];
NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];
int i, j, cnt = [nodes count];
for(i=0; i < cnt; i++) {
CXMLElement *xmlElement = [nodes objectAtIndex:i];
if(nil != xmlElement) {
NSArray *attributes = [NSArray array];
attributes = [xmlElement attributes];
int attrCnt = [attributes count];
NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init];
for(j = 0; j < attrCnt; j++) {
if([[[attributes objectAtIndex:j] name] isKindOfClass:[NSString class]])
[mutableDict setValue:[[attributes objectAtIndex:j] stringValue] forKey:[[attributes objectAtIndex:j] name]];
else
continue;
}
if(nil != mutableDict) {
[mutableArray addObject:mutableDict];
}
[mutableDict release]; // This is causing bad things to happen.
}
}
return (NSArray *)mutableArray;
}
Here's an equivalent rewrite of your code:
- (NSArray *)attributeDictionaries:(NSString *)xmlDocument withXPath:(NSString *)XPathStr {
NSError *theError = nil;
NSMutableArray *dictionaries = [NSMutableArray array];
CXMLDocument *theXMLDocument = [[CXMLDocument alloc] initWithXMLString:xmlDocument options:0 error:&theError];
NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];
for (CXMLElement *xmlElement in nodes) {
NSArray *attributes = [xmlElement attributes];
NSMutableDictionary *attributeDictionary = [NSMutableDictionary dictionary];
for (CXMLNode *attribute in attributes) {
[attributeDictionary setObject:[attribute stringValue] forKey:[attribute name]];
}
[dictionaries addObject:attributeDictionary];
}
[theXMLDocument release];
return attributeDictionaries;
}
Notice I only did reference counting on theXMLDocument. That's because the arrays and dictionaries live beyond the scope of this method. The array and dictionary class methods create autoreleased instances of NSArray and NSMutableDictionary objects. If the caller doesn't explicitly retain them, they'll be automatically released on the next go-round of the application's event loop.
I also removed code that was never going to be executed. The CXMLNode name method says it returns a string, so that test will always be true.
If mutableDict is nil, you have bigger problems. It's better that it throws an exception than silently fail, so I did away with that test, too.
I also used the relatively new for enumeration syntax, which does away with your counter variables.
I renamed some variables and the method to be a little bit more Cocoa-ish. Cocoa is different from most languages in that it's generally considered incorrect to use a verb like "create" unless you specifically want to make the caller responsible for releasing whatever object you return.
You didn't do anything with theError. You should either check it and report the error, or else pass in nil if you're not going to check it. There's no sense in making the app build an error object you're not going to use.
I hope this helps get you pointed in the right direction.
Well, releasing mutableDict really shouldn't be causing any problems because the line above it (adding mutableDict to mutableArray) will retain it automatically. While I'm not sure what exactly is going wrong with your code (you didn't specify what "bad things" means), there's a few general things I would suggest:
Don't autorelease mutableArray right away. Let it be a regular alloc/init statement and autorelease it when you return it ("return [mutableArray autorelease];").
theXMLDocument is leaking, be sure to release that before returning. Also, you do not need to retain it like you are. alloc/init does the job by starting the object retain count at 1, retaining it again just ensures it leaks forever. Get rid of the retain and release it before returning and it won't leak.
Just a tip: be sure that you retain the return value of this method when using it elsewhere - the result has been autoreleased as isn't guaranteed to be around when you need it unless you explicitly retain/release it somewhere.
Otherwise, this code should work. If it still doesn't, one other thing I would try is maybe doing [mutableArray addObject:[mutableDict copy]] to ensure that mutableDict causes you no problems when it is released.
In Memory Management Programming Guide under the topic Returning Objects from Methods (scroll down a bit), there are a few simple examples on how to return objects from a method with the correct memory managment.
Related
I have a NSMutabelArray and I want to do some additions inside of it. I do this by calling a functions with then create a subarray with the items where the calculations have to be done on.
- (NSDecimalNumber *)calculate:(NSMutableArray *)arrayToCalculate {
while ([arrayToCalculate containsObject:(#"+")]) {
NSUInteger signeLocation = [arrayToCalculate indexOfObject:(#"+")];
[arrayToCalculate replaceObjectAtIndex:(signeLocation-1)
withObject:([[arrayToCalculate objectAtIndex:(signeLocation-1)]
decimalNumberByAdding:[arrayToCalculate objectAtIndex:(signeLocation+1)]])];
[arrayToCalculate removeObjectsAtIndexes:
[NSIndexSet indexSetWithIndexesInRange:NSMakeRange((signeLocation), 2)]];
}
return [arrayToCalculate lastObject];
}
I initialised the arrayToCalculate by:
NSMutableArray *subArray =
[inputArray subarrayWithRange:(rangeOfCalculationItems)];
Every time I run this code it crashes. I am pretty sure it is bc I used subarray on an NSMutableArray and initialised it as NSMutableArray even when the message gives me back a NSArray, but I don't know how I could fix it or it is even the problem.
I copied your method and tested it like this:
NSArray *items = #[
[[NSDecimalNumber alloc] initWithString:#"1"],
#"+",
[[NSDecimalNumber alloc] initWithString:#"2"]
];
NSLog(#"%g", [self calculate: [items mutableCopy]].floatValue);
The code works and the printed result was 3. Your issue must be somewhere else. Are you sure your array is in fact mutable? Note [items mutableCopy].
Point of my view, when we create object and then set it = nil, this object will be release. but I tried to insert a lot of data to NSMutableDictionary, and then i set NIL for each object. Memory still be there and did not decrease. Please see my code:
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableDictionary*frameAnotationLeft=[[NSMutableDictionary alloc] init];;
// Do any additional setup after loading the view, typically from a nib.
NSMutableDictionary * testDict = [[NSMutableDictionary alloc] init];
frameAnotationLeft = [[NSMutableDictionary alloc] init];
for ( int j=0; j<1000; j++) {
for (int i= 0; i<5000; i++) {
NSString* __weak test = #"dule.For the most part, Automatic Reference Counting lets you completely forget about memory management. The idea is to focus on high-level functionality instead of the underlying memory management. The only thing you need to worry about are retain cycles, which was covered in the Properties module.For the most part, Automatic Reference Counting lets you completely forget about memory management. The idea is to focus on high-level functionality instead of the underlying memory management. The only thing you need to worry about are retain cycles, which was covered in the Properties module.For the most part, Automatic Reference Counting lets you completely forget about memory management. The idea is to focus on high-level functionality instead of the underlying memory management. The only thing you need to worry about are retain cycles, which was covered in the Properties module.For the most part, Automatic Reference Counting lets you completely forget about memory management. The idea is to focus on high-level functionality instead of the underlying memory management. The only thing you need to worry about are retain cycles, which was covered in the Properties module.";
NSMutableArray * arrayTest = [[NSMutableArray alloc] init];
[arrayTest addObject:test];
test = nil;
[testDict setObject:arrayTest forKey:[NSString stringWithFormat:#"%d",i]];
arrayTest = nil;
}
[frameAnotationLeft setObject:testDict forKey:[NSString stringWithFormat:#"%d",j]];
}
testDict = nil;
// Begin to release memory
for (NSString* key in frameAnotationLeft) {
NSMutableDictionary *testDict2 = (NSMutableDictionary*)[frameAnotationLeft objectForKey:key];
for (NSString* key2 in testDict2) {
NSMutableArray * arrayTest = (NSMutableArray *)[testDict2 objectForKey:key2];
NSString* test = [arrayTest objectAtIndex:0];
[arrayTest removeAllObjects];
test = nil;
arrayTest = nil;
}
[testDict2 removeAllObjects];
testDict2 = nil;
}
[frameAnotationLeft removeAllObjects];
frameAnotationLeft = nil;
}
Memory when i run it is 218 mb. and it did not decrease. Someone can help me and give me solution? Thank so muuch
If you are using ARC, setting local variables to nil is unnecessary and will have no effect. Local variables are completely memory-managed for you. And setting a variable to nil does not, as you imagine, cause the memory itself to be cleared; it reduces the object's retain count, but that's all. If the object's retain count has fallen to zero, then the memory may be reclaimed at some future time, but you have no control over when that will happen. Thus, your nil setting does nothing whatever, because the same thing is being done for you by ARC anyway.
If you are concerned about accumulation of secondary autoreleased objects during a tight loop (as here), simply wrap the interior of the loop in an #autoreleasepool block.
So I am using restkit for asynchronous calls and such, and felt the one of the best ways to handle possible connection issues was to have a requestManager Which holds a single an array of requests and it will keep looping through it and remove whenever they are successful, this way the payload for the request can be created and added to the manager and the rest of the app can continue without having to worry about returns or not unless it is a GET... Now my issue is that some of the requests are using closures or code Blocks as you will.
And i am having trouble wrapping my head around the syntax and usage of it properly. Especially when it comes to blocks.
This is how I plan to implement it...
so I first Make this Call
GameInfo *game = [[GameInfo alloc] init];
game.myWTFID = [playerInfo _wtfID];
game.opponentWTFID = [gameInfoObj GetOpponentWTFID];
game.gameType = [NSNumber numberWithInt:[gameInfoObj gameType]];
game.turnNumber = [gameInfoObj GetTurnNumber];
game.lastMove = [gameInfoObj GetGameID];
[[RequestPayload sharedRequestPayload] addPutRequestForObject : game withSerializationMapping : serializedGameMap forClass : [Game class] withBlock :^(RKObjectLoader *loader)
{
[loader setObjectMapping:gameMap];
loader.delegate = self;
loader.onDidLoadResponse = ^(RKResponse *response)
{
[game release];
};
loader.targetObject = nil;
loader.backgroundPolicy = RKRequestBackgroundPolicyRequeue;
}
];
here Is the Implementation
- ( void ) addPutRequestForObject : (id) object withSerializationMapping : (RKObjectMapping*) serialMapping forClass : (Class) class withBlock : ( void(^)(RKObjectLoader*) ) block
{
NSMutableDictionary *dict = [NSMutableDictionary new];
NSNumber *postType = [[NSNumber alloc]initWithInt:1];
[dict setObject:postType forKey:#"request"];
[dict setObject:object forKey:#"data"];
[dict setObject:serialMapping forKey:#"serialMapping"];
[dict setObject:class forKey:#"class"];
void (^ copyBlockLoader)(id,RKObjectMapping*) = Block_copy(block);
[dict setObject:copyBlockLoader forKey:#"block"];
Block_release(copyBlockLoader);
[postType release];
postType = nil;
[_RequestsToInvoke addObject:dict];
}
and then within a for loop after going though each object in the array which will be a dictionary containing the needed information to do something like this.(apologies if this does not make sense it is highly contrived as the actual method is alot longer but the vital part i thought was here.)
[[RKObjectManager sharedManager].mappingProvider setSerializationMapping:[dict objectForKey:#"serialMapping"] forClass:[dict objectForKey:#"class"]];
void(^block)(RKObjectLoader *) = [dict objectForKey:#"block"];
[[RKObjectManager sharedManager] putObject:object usingBlock:block];
So my questions are
in the first snippet where i ask it to release the game object...Will that work? or will it result in a leak the game object is declared within the same scope as that call so the only pointer i have left is the one in the code block.
am i saving the block into the dictionary correctly?
and last does anyone have a better alternative? or spot something else with my implementation that needs correcting/modification?
Im setting up a map, with some MKAnnotation. When i add them to the map, I receive this message:
An instance 0xbe62850 of class ENTAnottation was deallocated while key value observers were still registered with it. Observation info was leaked, and may even become mistakenly attached to some other object. Set a breakpoint on NSKVODeallocateBreak to stop here in the debugger. Here's the current observation info:
(
Context: 0x0, Property: 0xbe66b30>
If i am not mistaken, that means that an object has been deallocated while their observer are still alive. How can I know wich are the observers? and, even if I find them... isn't explicit deallocation forbidden with ARC? if thats true, i could not deallocate them... so... what could i do?
Thank you.
-------EDIT------
By request, I post my code. I make a call to a web that returns me a JSON with the values that I need to set my Annotations:
- (void)requestFinished:(ASIHTTPRequest *)request
{
responseString = [request responseString];
id jsonObject = [responseString objectFromJSONString];
NSLog(#"From the JSON: %#", responseString);
NSMutableArray *lat = [jsonObject valueForKeyPath:#"latitud"];
NSMutableArray *lon = [jsonObject valueForKeyPath:#"longitud"];
NSMutableArray *azafatas = [jsonObject valueForKeyPath:#"azafata"];
NSMutableArray *usernames = [jsonObject valueForKeyPath:#"username"];
NSMutableArray *mapazafatas=[[NSMutableArray alloc]init];
for(int i=0;i<[lat count]; i++)
{
ENTAnottation *azafata=[[ENTAnottation alloc]init];
double latidouble=[[lat objectAtIndex:i]doubleValue];
double longdouble=[[lon objectAtIndex:i]doubleValue];
CLLocationDegrees lati=latidouble;
CLLocationDegrees longi=longdouble;
CLLocationCoordinate2D coords=CLLocationCoordinate2DMake(lati, longi);
NSString *nombre=[azafatas objectAtIndex:i];
NSString *email=[usernames objectAtIndex:i];
azafata.coordinate=coords;
azafata.title=nombre;
azafata.username=email;
[mapazafatas addObject:azafata];
//[mapa addAnnotation:azafata];
}
for (int i=0;i<[mapazafatas count];i++)
{
[mapa addAnnotation:[mapazafatas objectAtIndex:i]];
}
}
After that, the app crashes, despite it goes through my code without any problem.
Solved. Obviously, if you pass an impossible pair of coordinates it throws to your face that very clear, and impossible to confuse anybody, error message. Pity me...
I had another question about memory management in iOS 3.0+, I know if we did [varialbe release] will release its retain to an object, but code like this -
- (void) getPostsFromJson:(NSData *)data
{
NSError *theError = nil;
NSDictionary *dict = [[CJSONDeserializer deserializer] deserializeAsDictionary:data error:&theError];
if (dict == nil) {
[SystemStatusValues getInstance].isValidJson = NO;
[SystemStatusValues getInstance].httpStatus = HTTP_STATUS_FAILED;
NSLog(#"getPostsFromJson - %# %#",
[theError localizedDescription],
[[theError userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
} else {
NSArray *keys = [dict allKeys];
keys = [keys sortedArrayUsingFunction:keySortByInteger context:nil];
self.keys = keys;
self.posts = dict;
NSLog(#"Posts : getPostsFromJson = %#", self.posts);
if ([keys count] < TOTAL_SECTIONS) {
[SystemStatusValues getInstance].isValidJson = NO;
} else {
[SystemStatusValues getInstance].isValidJson = YES;
if (self.nextPosts == nil) {
}
}
// [keys release];
// [dict release];
return;
}
}
You can see there are two local variables - keys and dict, did I need to call [keys release] or [dict release] at the end of code? Actually I did it, but it brings crushing sometimes. So for local variables, we don't need to release it after it's used?
Both dict and keys are reference to autoreleased objects. You shouldn't release them, which will cause your program to terminate. You Don’t Own Objects Returned by Reference and You must not relinquish ownership of an object you do not own
So for local variables, we don't need to release it after it's used? If you own the objects you must release them when you no longer need them, doesn't matter if its local variable or ivar.
Read this - Advanced Memory Management Programming guide
It's simple:
If you alloc, copy or retain an object
you need to release it. Otherwise you don’t.
So you don't need to release either keys or dict (they're both autoreleased).
Don't release keys array. Because allKeys method probably returns autoreleased NSArray object. So it will be released later by system.
It seems like deserializeAsDictionary also returns an autoreleased NSDictionary, so you shouldn't release it too.
Release objects only created with alloc. In this case, you're using external methods. You need to follow that methods, and see if that object are created with alloc.