Why is the memory allocated from componentsSeparatedByString never being allocated - ios

I have a iOS app which does alot of calculation and is using standard ARC for memory management. After I run it for a few minutes it crashes due to being out out memory. I checked with Instruments and most of the memory is being eaten up by allocations from a call to NSString's commentsSeparatedByString.
I tried running it in a autorelease pool but that didn't help much. Since there are no references to that string outside of my function, I'm confused why the memory isn't being automatically deallocated. I also have another function which is having the same problem with commentsSeparatedByString.
Here is the code:
- (void) processWorkWithExtraData:(NSData *) extraData
{
#autoreleasepool {
NSString *string = [[NSString alloc] initWithData:extraData encoding:NSUTF8StringEncoding];
NSArray *dataArray = [string componentsSeparatedByString:#","]; // eats up memory like crazy!!!
NSMutableArray *objectArray = [[NSMutableArray alloc] init];
for (int i=0;i<[dataArray count];i += 1)
{
TestObject *p = [[TestObject alloc] initWithFloat:[[dataArray objectAtIndex:i] floatValue]];
[objectArray addObject:p];
}
[self processArray: objectArray]; // just performs math computations on the floats in the objects
}
}
If anyone can let me know why memory would not be freed here please let me know.

Figured out the problem, I thought I was using ARC but I wasn't (:
Good thing is this fixes my memory issues.
Bad thing is that it's much slower (50-70% slower).
I guess that's the price one has to pay for the magic that is ARC.

Related

Objective-C memory leak when returning NSString

I want to be sure that my code is not leaking, since this small snippet is called thousand times in my app. I run the app through Instruments and the initWithBytes seems to be problematic. Is anything wrong in this code?
First [reader readString] is called.
case FirstCase:
{
NSString *string = [reader readString];
[self setPropertyByName:propertyName value:string];
break;
}
...
readString is returns the strings which is autoreleased.
- (NSString*) readString
{
...
NSString *string = [[[[NSString alloc] initWithBytes:cursor length:stringLength encoding:NSUTF8StringEncoding] autorelease];
return string;
}
Is the code OK? Any other better approach to avoid autorelease?
I cannot change my code to ARC. Plain old non-ARC memory management.
What you posted is OK. The only rule at this point is that methods contain "create" or "alloc" will return an object that needs to be explicitly released. In your case that is the string returned in the readString method.
Since the object will be returned you need to retain it till the end of the run loop cycle which the autorelease pool will do. What that means for instance is if this method will be called in a for loop the objects will not be deallocated before the loop has exited.
If you want or need to avoid that I suggest you to do the same pattern with "create" or "alloc" and return an object not being autoreleased:
case FirstCase:
{
NSString *string = [reader createReadString];
[self setPropertyByName:propertyName value:string];
[string release];
break;
}
...
- (NSString*) createReadString
{
...
NSString *string = [[[NSString alloc] initWithBytes:cursor length:stringLength encoding:NSUTF8StringEncoding];
return string;
}

Need assistance regarding NSString

In NSString NSString Class Reference what this means
Distributed objects:
Over distributed-object connections, mutable string objects are passed by-reference and immutable string objects are passed by-copy.
And NSString can't be changed, so what happening when I am changing str in this code
NSString *str = #"";
for (int i=0; i<1000; i++) {
str = [str stringByAppendingFormat:#"%d", i];
}
will I get memory leak? Or what?
What your code is doing:
NSString *str = #""; // pointer str points at memory address 123 for example
for (int i=0; i<1000; i++) {
// now you don't change the value to which the pointer str points
// instead you create a new string located at address, lets say, 900 and let the pointer str know to point at address 900 instead of 123
str = [str stringByAppendingFormat:#"%d", i]; // this method creates a new string and returns a pointer to the new string!
// you can't do this because str is immutable
// [str appendString:#"mmmm"];
}
Mutable means you can change the NSString. For example with appendString.
pass by copy means that you get a copy of NSString and you can do whatever you want; it does not change the original NSString
- (void)magic:(NSString *)string
{
string = #"LOL";
NSLog(#"%#", string);
}
// somewhere in your code
NSString *s = #"Hello";
NSLog(#"%#", s); // prints hello
[self magic:s]; // prints LOL
NSLog(#"%#", s); // prints hello not lol
But imagine you get a mutable NSString.
- (void)magic2:(NSMutableString *)string
{
[string appendString:#".COM"];
}
// somewhere in your code
NSString *s = #"Hello";
NSMutableString *m = [s mutableCopy];
NSLog(#"%#", m); // prints hello
[self magic2:m];
NSLog(#"%#", m); // prints hello.COM
Because you pass a reference you can actually change the "value" of your string object since you are working with the original version and not a duplicate.
NOTE
String literals live as long as your app lives. In your exmaple it means that your NSString *str = #""; never gets deallocated. So in the end after you have looped through your for loop there are two string objects living in your memory. Its #"" which you cannot access anymore since you have no pointer to it but it is still there! And your new string str=123456....1000; But this is not a memory leak.
more information
No, you will not get memory leak with your code, as you are not retaining those objects in the loop, they're created with convenience method, you don't own them, and they will be released on next cycle of autorelease pool. And, it's doesn't matter if you are using ARC or not, objects created with convenience methods and not retained are released wherever they are out of their context.
In will not leak memory, but will get more memory allocation, due to making new copy of immutable copy as many time loop triggers [str stringByAppendingFormat:#"%d", i];.
Memory leak will get performed when, you put your data unreferenced, or orphan, this will not make your last copy of string orphan every time when loops, but will clear all copies of NSString when operation get complete, or viewDidUnload.
You will not get a memory leak in the example code because Automatic Reference Counting will detect the assignment to str and (automatically) release the old str.
But it would be much better coding style (and almost certainly better performance) to do this:
NSMutableString* mstr = [NSMutableString new];
for(int i = 0; i < 1000; ++i){
[mstr appendFormat:#"%d",i];
}
NSString* str = mstr;
...
As to the first question, I think it means that a change made to a mutable string by a remote process will be reflected in the originating process's object.

iOS Memory Issue With No Leaks

I have a problem with my app, where it consumes a lot of memory and crashes after about 40 minutes. I have run instruments to see if there were any leaks, but none appeared. Inside the app, I am using Mapbox to display maps and drawing a line annotation wherever the user has travelled. I am storing these locations in an array, removing the current annotation and presenting a new one whenever the user has moved.
In my MapViewController.m
- (void)drawCurrentPolyline
{
[self removePolylineForTrip:locationServices.trip forClass:nil];
[self addPolylineForTrip:locationServices.trip forClass:nil];
globalData.locationData = [[NSMutableArray alloc] init];
colour = [globalData convertColourFromString:[globalData.currentTrip objectForKey:#"colour"]];
[colourArray addObject:colour];
if ([currentLocations count] >= 2)
{
for (int i = [currentLocations count] - 2; i <= [currentLocations count] - 1; i++)
{
CLLocation *locationCoordinate = [[CLLocation alloc] initWithLatitude:[[currentLocations[i] objectForKey:#"latitude"] doubleValue] longitude:[[currentLocations[i] objectForKey:#"longitude"] doubleValue]];
[globalData.locationData addObject:locationCoordinate];
}
}
else
{
for (NSDictionary *location in currentLocations)
{
CLLocation *locationCoordinate = [[CLLocation alloc] initWithLatitude:[[location objectForKey:#"latitude"] doubleValue] longitude:[[location objectForKey:#"longitude"] doubleValue]];
[globalData.locationData addObject:locationCoordinate];
}
}
}
- (void)removePolylineForTrip:(int)trip forClass:(MapPreferencesTableViewController *)mapPreferencesTableViewController
{
NSMutableDictionary *dictionary = [tripAnnotions objectForKey:[NSString stringWithFormat:#"trip%d", trip]];
NSMutableArray *annotationArray = [dictionary objectForKey:#"annotions"];
for (RMAnnotation *annotation in annotationArray)
{
if ([[annotation.userInfo objectForKey:#"type"] isEqualToString:#"line"])
{
[mapView removeAnnotation:annotation];
}
}
}
- (void)addPolylineForTrip:(int)trip forClass:(MapPreferencesTableViewController *)mapPreferencesTableViewController
{
globalData.locationData = [[NSMutableArray alloc] init];
globalData.tripData = [globalData.trips objectForKey:[NSString stringWithFormat:#"trip%d", trip]];
NSMutableArray *locationArray = [globalData.tripData objectForKey:#"locationData"];
colour = [globalData convertColourFromString:[globalData.tripData objectForKey:#"colour"]];
[colourArray addObject:colour];
double lineWidth = 5.0;
NSString *type = #"line";
for (NSDictionary *location in locationArray)
{
CLLocation *locationCoordinate = [[CLLocation alloc] initWithLatitude:[[location objectForKey:#"latitude"] doubleValue] longitude:[[location objectForKey:#"longitude"] doubleValue]];
[globalData.locationData addObject:locationCoordinate];
}
if ([locationArray count] > 1)
{
RMAnnotation *annotation = [[RMAnnotation alloc] initWithMapView:mapView
coordinate:((CLLocation *)[globalData.locationData objectAtIndex:0]).coordinate
andTitle:[NSString stringWithFormat:#"Trip %d", trip]];
NSMutableDictionary *annotationData = [[NSMutableDictionary alloc] init];
[annotationData setObject:globalData.locationData forKey:#"locations"];
[annotationData setObject:colour forKey:#"colour"];
[annotationData setObject:[NSString stringWithFormat:#"%f", lineWidth] forKey:#"width"];
[annotationData setObject:type forKey:#"type"];
[annotationData setObject:[NSString stringWithFormat:#"%d", trip] forKey:#"trip"];
annotation.userInfo = annotationData;
// NSLog(#"User Info For Annotation:\n%#", annotation.userInfo);
//annotation.layer = [NSString stringWithFormat:#"%d", trip];
[annotation setBoundingBoxFromLocations:globalData.locationData];
// Add annotation for tracking
[self addAnnotationToTripArray:annotation forTrip:trip];
[mapView addAnnotation:annotation];
}
}
I think the problem might be the fact that I am storing each of the user locations in a mutable array. Hence, I told the app to remove everything in the array once a memory warning has appeared. However, the app still crashes. I cannot seem to find the source of the memory consumption. There are no leaks (except for some small core graphics ones).
Could someone please guide me from where to go from here, or help me find the source of this issue? The simulator used over 1GB of data, and the app became very slow.
Remember, it's not a leak if it's not memory that's been orphaned somehow. Pay attention to your memory usage over time. Instruments will tell you the number of objects living and what kind they are. Things to look out for are things like large arrays of things growing over time and UIView's not getting removed from the superViews.
I had a similar issue and it was because I had MBProgressHUD instances that were hidden, but never removed from their superView.
As #InkGolem states, increasing memory is not necessarily a leak.
Use instruments to check for leaks and memory loss due to retained but not leaked memory. The latter is unused memory that is still pointed to. Use Mark Generation (Heapshot) in the Allocations instrument on Instruments.
For HowTo use Heapshot to find memory creap, see: bbum blog
Basically there method is to run Instruments allocate tool, take a heapshot, run an intuition of your code and another heapshot repeating 3 or 4 times. This will indicate memory that is allocated and not released during the iterations.
To figure out the results disclose to see the individual allocations.
If you need to see where retains, releases and autoreleases occur for an object use instruments:
Run in instruments, in Allocations set "Record reference counts" on on (you have to stop recording to set the option). Cause the picker to run, stop recording, search for there ivar (datePickerView), drill down and you will be able to see where all retains, releases and autoreleases occurred.

multiple assignments of objects to variable under ARC

There's a point of memory management I'm not 100% clear on, suppose there is the following code:
{
NSString *string = [[NSString alloc] init];
string = [[NSString alloc] init];
}
Does this cause a memory leak of the first allocation? If not why not?
Under ARC, this does not leak memory. This is because any time a strong object pointer is changed, the compiler automatically sends a release to the old object. Local variables, like NSString *string, are strong by default.
So your code above gets compiled to something more like this:
{
NSString *string = [[NSString alloc] init];
// Oh, we're changing what `string` points to. Gotta release the old value.
[string release];
string = [[NSString alloc] init];
}
Conceptually, BJ is correct, but the generated code is slightly different. It goes something like this:
NSString *string = [[NSString alloc] init];
// Oh, we're changing what `string` points to. Gotta release the old value.
NSString *tmpString = string;
string = [[NSString alloc] init];
[tmpString release];
[string release]; // string goes out of scope at this point in your code
This order of operation is usually not that critical (and if you care too much about it, you are probably coding incorrectly). But understanding it explains why the objects are destroyed exactly when they are.
No it does not cause a leak. ARC will release the first string before it sets the second string. This is the truly amazing power of ARC!

iOS: How to avoid autoreleased copies when manipulating large NSString instance?

I have a scenario in an iOS application where manipulating a very large NSString instance (an HTTP response, upwards of 11MB) results in multiple large intermediaries being in memory at once, since the SDK methods I am calling return new autoreleased instances. What is the best approach to take here?
For example, assuming that largeString is an autoreleased NSString instance:
NSArray *partsOfLargeString = [largeString componentsSeparatedByString:separator];
for (NSString *part in partsOfLargeString) {
NSString *trimmedPart = [part stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSData *data = [trimmedPart dataUsingEncoding:NSUTF8StringEncoding];
}
It would be great if there were non-autoreleased equivalents to componentsSeparatedByString or stringByTrimmingCharactersInSet, but I'm not looking to implement these myself.
To my knowledge, there isn't a way to "force" release an object that has already been added to an autorelease pool. I know that I can create and use my own autorelease pool here, but I'd like to be extremely granular and having autorelease pools around individual statements definitely isn't a very scalable approach.
Any suggestions are much appreciated.
As Bill said, I’d first try to have an autorelease pool for each loop iteration, e.g.:
for (NSString *part in partsOfLargeString) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSString *trimmedPart = [part stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSData *data = [trimmedPart dataUsingEncoding:NSUTF8StringEncoding];
…
[pool drain];
}
or, if you’re using a recent enough compiler:
for (NSString *part in partsOfLargeString) {
#autoreleasepool {
NSString *trimmedPart = [part stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSData *data = [trimmedPart dataUsingEncoding:NSUTF8StringEncoding];
…
}
}
If that’s still not acceptable and you do need to release objects in a more granular fashion, you could use something like:
static inline __attribute__((ns_returns_retained))
id BICreateDrainedPoolObject(id (^expression)(void)) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
id object = expression();
[object retain];
[pool drain];
return object;
}
#define BIOBJ(expression) BICreateDrainedPoolObject(^{return (expression);})
which evaluates the expression, retains its result, releases any ancillary autoreleased objects and returns the result; and then:
for (NSString *part in partsOfLargeString) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSString *trimmedPart = BIOBJ([part stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]);
NSData *data = BIOBJ([trimmedPart dataUsingEncoding:NSUTF8StringEncoding]);
[trimmedPart release];
// do something with data
[data release];
…
[pool drain];
}
Note that, since the function returns a retained object, you’re responsible for releasing it. You’ll have control over when to do that.
Feel free to choose better names for the function and macro. There might be some corner cases that should be handled but it should work for your particular example. Suggestions are welcome!
First, you shouldn't need to parse responses from an HTTP server in this fashion. Parsing HTTP responses (including parsing HTML) is a solved problem and attempting to parse it using raw string manipulation will lead to fragile code that can easily be crashed with seemingly innocuous server side changes.
Autorelease pools are pretty cheap. You could surround the body [inside] of the for with #autoreleasepool {... that code ...} and it'll probably both fix your high-water issue and have negligible performance impact [compared to raw string manipulation].
Beyond that, your summary is correct -- if there isn't a non-autoreleasing variant in the 'kit, then you'd have to re-invent the wheel. With that said, it is fairly typical that the lack of a non-autoreleasing variant is not an oversight on the part of the designer. Instead, it is likely because there are better tools available for achieving the sort of high-volume solution that would also require finer grained memory management.

Resources