I'm developing an App that downloads and parses around 40k json files from an API. Due to the structure of my program, i don't parse them immediately, but save them in an NSMutable array. Each json is around 1KB, most of them even less. If my calculation is correct, that should produce around 40MB (+ some overhead) of allocated memory. But when i run the App, the memory usage climbs up to over 4GB.
Am i leaking something here? Since i use garbage collection, i should not need to dealloc anything, right? Or is my calculation simply wrong?
Here my code:
- (void) loadItemsForIds:(NSArray*)idList {
for (NSNumber* n in idList) {
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://someapi.com/somejson.json?id=%#", n]];
NSData* response = [NSData dataWithContentsOfURL:url];
NSDictionary *loadedData = [NSJSONSerialization JSONObjectWithData:response options:0 error:nil];
#synchronized(self.updateData) {
[self.updateData addObject:loadedData];
}
[self performSelectorOnMainThread:#selector(progressLoadingInterface:) withObject:[loadedData valueForKey:#"name"] waitUntilDone:NO];
};
}
Edit:
Found out that the problem even exists, if i don't save the data into the array. After some research i stumbled upon this question which deals with the same problem: iPhone - Memory Leak - NSData dataWithContentsOfUrl & UIWebView
My question might need to be deleted then.
[NSJSONSerialization JSONObjectWithData:response options:0 error:nil] parses the JSON data into objects. This will take up more memory than the 1K of JSON data.
In addition, NSString is likely storing all your string data as unichar, which is a 16-bit representation: double the size of the UTF-8 encoding assumed in the JSON data.
Depending on the contents of the JSON, I can believe that 1K of JSON data could become 10K of objects.
But this is all guess work. Use Instruments (Product > Profile) to check memory usage. That will give you a better idea what objects are taking up all the memory.
you never release memory between the 40k iterations.
=> You never give anybody the chance to do so either (arc/gc)
wrap the loop body in an #autoreleasepool {...} so memory is drained
for (NSNumber* n in idList) {
#autoreleasepool {
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://someapi.com/somejson.json?id=%#", n]];
NSData* response = [NSData dataWithContentsOfURL:url];
NSDictionary *loadedData = [NSJSONSerialization JSONObjectWithData:response options:0 error:nil];
#synchronized(self.updateData) {
[self.updateData addObject:loadedData];
}
[self performSelectorOnMainThread:#selector(progressLoadingInterface:) withObject:[loadedData valueForKey:#"name"] waitUntilDone:NO];
}
}
Related
I am using the following code to convert nsdata to bytearray. It works fine in simulator. On device, it allocates memory like crazy to 600 MB [on the 'addobject' line inside the loop] and crashes. The file size I am reading is 30 MB. The error I see in output windows is "memory issue". The file is a "zip" file
NSData *data = [[NSData alloc] initWithContentsOfFile:file];
const unsigned char *bytes = [data bytes];
NSUInteger length = [data length];
NSMutableArray *byteArray = [NSMutableArray array];
for (NSUInteger i = 0; i < length; i++) {
#autoreleasepool {
[byteArray addObject:[NSNumber numberWithUnsignedChar:bytes[i]]]; }
}
I am using this bytearray inside a NSDictionary and do a "dataWithJSONObject" on the dictionary to post the json to a REST web service.
For a more memory efficient way to convert NSData to byte array, see How to convert NSData to byte array in iPhone?
If your goal is ultimately to post the binary data to a web service in JSON, try the following:
Base64-encode the the data in a String, as described here. This can be passed in the JSON Body.
Alternatively, if you have flexibility on changing the server-side of this transfer, avoid base64-encoded data in a JSON and rather directly post the binary content using an HTTP POST or PUT. This will be more efficient.
For the second approach, I can think of two ways to do this:
Here is an example for the scenario of sending an image, but any file type will work so long as you can load it into an NSData object.
Or, something like this should work
NSData *data = [NSData dataWithContentsOfFile:file];
NSURL *url = [NSURL URLWithString:#"your_url_here"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:#"PUT"];
NSDictionary *headers = #{#"Content-Type": #"application/octet-stream"};
[request setAllHTTPHeaderFields:headers];
[request setHTTPBody:data]
// Use an URLSession object task to execute the request
Lastly, if the data can be compressed on the client before encoding and sending, that would be even better!
I hope you find this helpful.
I have a json file that looks like this:
{
"data":
{
"level": [
{
//bunch of stuff
}
]
}
}
Now I want to convert that into a array of levels that I can access. If I take away the {"data: part, then I can use this:
NSData *allLevelsData = [[NSData alloc] initWithContentsOfFile:fileLoc];
NSError *error = nil;
NSMutableDictionary *allLevels = [NSJSONSerialization JSONObjectWithData:allLevelsData options:kNilOptions error:&error];
if(!error){
NSMutableArray *level = allLevels[#"level"];
for (NSMutableDictionary *aLevel in level){
//do stuff with the level...
But I have to have the {"data: as part of the file, and I can't figure out how to get a NSData object out of the existing NSData object. Any ideas?
Don't you need to pull the level NSArray out of the data NSDictionary first?
NSData *allLevelsData = [[NSData alloc] initWithContentsOfFile:fileLoc];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:allLevelsData options:kNilOptions error:&error];
if(!error){
NSArray *levels = dataDictionary[#"data"][#"level"];
for (NSDictionary *aLevel in levels){
//do stuff with the level...
You won't get mutable objects back by default and declaring the variables as mutable doesn't make them so. Take a mutableCopy of the result instead (assuming you really do need mutability).
Why are you trying to prune ahead of time? If you decode the original JSON, you'll be able to extract the level array from the data dict in the decoded dict.
It's not clear what else you're trying to accomplish or why you are going the path you ask about. Note, this doesn't necessarily mean your path is wrong, just that without a clearer indication of what goal you're really trying to accomplish or what you've actually tried (and errored/failed, along with how it failed), you're likely only to get vague/general answers like this.
My application uses bookmarks to retain access to files in sandboxed environment.
Every time i use
NSData *bookmark = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:&error];
or
NSURL *resolvedURL = [NSURL URLByResolvingBookmarkData:storedBookmark options:nil relativeToURL:nil bookmarkDataIsStale:FALSE error:&resolveError ];
memory heap grows with non-objects allocations which are not deallocated later.
I am using ARC. #autorelease blocks makes no difference. Instruments showing no memory leaks.
How to get rid from these allocations? Thanks.
please use the pair of:
[url startAccessingSecurityScopedResource];
...
[url stopAccessingSecurityScopedResource];
I am developing application which required image caching. For doing this, I am using JMImageCache library. It is work fine for caching. But It can not release memory occupied by
following line.
[NSData dataWithContentsOfFile]
Here, is function which content code for cache image from disk.
- (UIImage *) imageFromDiskForURL:(NSString *)url {
NSData *data = [NSData dataWithContentsOfFile:cachePathForURL(url) options:0 error:NULL];
UIImage *i = [[[UIImage alloc] initWithData:data] autorelease];
data = nil;
[data release];
return i;
}
I have check it with instruments and it alloc 2.34 MB each time.
data = nil;
[data release];
Why do you expect this at all to work? Why should this release the original data? You're sending the release message to nil, which is a no-op.
Furthermore, if you don't create the object using alloc or copy, then it's autoreleased. That means if you release it once more, it will be overreleased and most likely your app is going to crash. What you need is:
One. Wrap the method call in an explicit autorelease pool:
- (UIImage *)imageFromDiskForURL:(NSString *)url
{
UIImage *i;
#autoreleasepool {
NSData *data = [NSData dataWithContentsOfFile:cachePathForURL(url) options:0 error:NULL];
i = [[UIImage alloc] initWithData:data];
}
return [i autorelease];
}
Two, alloc-init or manually release the data object:
- (UIImage *)imageFromDiskForURL:(NSString *)url
{
NSData *data = [[NSData alloc] initWithContentsOfFile:cachePathForURL(url) options:0 error:NULL];
UIImage *i = [[[UIImage alloc] initWithData:data] autorelease];
[data release];
return i;
}
alter the sequence nil and release
[data release];
data = nil;
and for clearing the cache use following delegate methods
[[JMImageCache sharedCache] removeAllObjects];
[[JMImageCache sharedCache] removeImageForURL:#"http://dundermifflin.com/i/MichaelScott.png"];
read the read me file of library
https://github.com/jakemarsh/JMImageCache/blob/master/README.markdown
What you are trying to do cannot work due to the way how UIImage uses a data you pass it. The data object is retained by the image, or more precisely by a CGImageSource that the UIImage has internally. From this data it is able to decompress and create the image any time. There is an option on CGImageSource to also keep around the decompressed data, but UIImage does not use that because it optimized for small UI graphics.
One thing that you can do to alleviate memory pressure is to not load the entire NSData into memory, but to memory-map it instead. This makes creation or recreation of the image a tad slower, but the created NSData object is very small in comparison.
In the method below I'm receiving "EXC_BAD_ACCESS" on the line containing the "urlString" variable. My research suggests that this error occurs when the program sends a message to a variable that has already been released. However since I'm using ARC I'm not manually releasing memory. How can I prevent ARC from releasing this variable too soon?
-(NSMutableArray *)fetchImages:(NSInteger *)count {
//prepare URL request
NSString *urlString = [NSString stringWithFormat:#"http://foo.example.com/image?quantity=%#", count];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
//Perform request and get JSON as a NSData object
NSData *response = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
//Parse the retrieved JSON to an NSArray
NSError *jsonParsingError = nil;
NSArray *imageFileData = [NSJSONSerialization JSONObjectWithData:response options:0 error:&jsonParsingError];
//Create an Array to store image names
NSMutableArray *imageFileNameArray;
//Iterate through the data
for(int i=0; i<[imageFileData count];i++)
{
[imageFileNameArray addObject:[imageFileData objectAtIndex:i]];
}
return imageFileNameArray;
}
Your problem has nothing to do with ARC. NSInteger isn't a class, so you don't want to be using the %# format. %# is going to send a description method to what the system thinks is an object, but when it turns out not to be one - CRASH. To solve your problem, you have two options:
You might want:
NSString *urlString =
[NSString stringWithFormat:#"http://foo.example.com/image?quantity=%d",
*count];
Make sure the count pointer is valid first!
You might need to change your method signature to be:
-(NSMutableArray *)fetchImages:(NSInteger)count;
and then change the urlString line as follows:
NSString *urlString =
[NSString stringWithFormat:#"http://foo.example.com/image?quantity=%d",
count];
You'll also need to fix all of the callers to match the new signature.
The second option seems more "normal" to me, but without more of your program it's impossible to be more specific.
you also may want to alloc and init the
NSMutableArray *imageFileNameArray;
before adding objects to it, otherwise you'll keep crashing. So you'd have
//Create an Array to store image names
NSMutableArray *imageFileNameArray = [[NSMutableArray alloc] init];