I've started writing my first serious attempt at a hybrid, Cordova/Objective-C program for iOS, and I'm currently hitting some stumbling blocks regarding memory allocations. I need to get the user's Album art to display within the web view. I got the art to display successfully, but now there's a ton of memory being allocated.
Using the "Instruments" tool and comparing Generational Snapshots, I've narrowed my guilty culprits down to these methods - which I all wrote from scratch. But I'm confused - since I'm using automatic reference counting, and that I have everything in autorelease pools, that there shouldn't be any wasted memory. Funny thing is, I see no "leaks" being reported - just a heap that gets bigger, with more and more memory allocated.
I've attached some screenshots of the Instruments tool:
Here are direct links to the images since there is so much text:
http://i.imgur.com/rkc5dhA.png
http://i.imgur.com/U2esgBT.png
http://i.imgur.com/fmt3Mv4.png
Here's the contents of the "BukketHelper.M" class that I made (matches the header, no strong properties or any other definitions of any sort):
-(NSString *) convertULLToNSString:(NSNumber* )guid
{
return [NSString stringWithFormat:#"%llu", [guid unsignedLongLongValue]];
}
-(NSNumber *) convertStringToULL:(NSString *) guid
{
//get string to number
//this causes memory to not be released
unsigned long long ullvalue = strtoull([guid UTF8String], NULL, 10);
NSNumber *numberID = [[NSNumber alloc] initWithUnsignedLongLong:ullvalue];
return numberID;
}
Here's the contents of the "MediaQuery.M" class that I made (this matches the header exactly, no strong properties or other definitions):
-(MPMediaItem*) getMediaItemULL:(NSNumber*)guid
{
#autoreleasepool {
//run the query on
MPMediaQuery *query = [[MPMediaQuery alloc] init];
[query addFilterPredicate:[MPMediaPropertyPredicate predicateWithValue:guid forProperty:MPMediaItemPropertyPersistentID]];
//get and return the item
NSArray *mediaResults = [query items];
return [mediaResults firstObject];
}
}
-(MPMediaItem*) getMediaItem:(NSString*)guid
{
#autoreleasepool {
BukketHelper* bh = [[BukketHelper alloc] init];
return [self getMediaItemULL:[bh convertStringToULL:guid]];
}
}
-(UIImage*) getMediaAlbumArtAsUIImage:(NSString*)guid withQuality:(NSNumber*)quality withLength:(NSNumber*)length subsituteImageName:(NSString* )filename
{
return [self getMediaAlbumArtFromMediaItemAsUIImage:[self getMediaItem:guid] withQuality:quality withLength:length subsituteImageName:filename];
}
-(NSString*) getMediaAlbumArtAsBase64:(NSString*)guid withQuality:(NSNumber*)quality withLength:(NSNumber*)length subsituteImageName:(NSString* )filename
{
NSString *base64 = nil;
#autoreleasepool {
UIImage* rawImage = [self getMediaAlbumArtAsUIImage:guid withQuality:quality withLength:length subsituteImageName:filename];
NSData *imageData = nil;
if (rawImage != nil)
{
#autoreleasepool {
//this causes memory to not be released
imageData = UIImageJPEGRepresentation(rawImage, [quality floatValue]);
base64 = [imageData base64EncodedStringWithOptions:0];
}
}
}
return base64;
}
-(UIImage*) getMediaAlbumArtFromMediaItemAsUIImage:(MPMediaItem*)item withQuality:(NSNumber*)quality withLength:(NSNumber*)length subsituteImageName:(NSString* )filename
{
UIImage *rawImage = nil;
#autoreleasepool {
bool successfulArt = NO;
if (item != nil)
{
MPMediaItemArtwork *albumArt = [item valueForProperty:MPMediaItemPropertyArtwork];
if (albumArt != nil) {
#autoreleasepool {
//this causes memory to not be released
rawImage = [albumArt imageWithSize:CGSizeMake([length doubleValue], [length doubleValue])];
successfulArt = YES;
}
}
}
if (successfulArt == NO)
{
rawImage = [UIImage imageNamed:filename];
}
}
return rawImage;
}
So yeah - my question is: What am I doing wrong when it comes to memory allocation and leaks? My current tests are exclusively using album art - so "UIImage imageNamed" shouldn't be the issue (from it's caching). In addition, I've read that ARC cannot release CoreGraphics objects, which could also be the problem.
I really could use some help with this! Thank you!
I ran into the same problem. I think there's a bug in the framework which doesn't release the memory. Basically there's a huge spike in allocated memory each time func imageWithSize(size: CGSize) -> UIImage? is called.
If it helps you any, I noticed that if you call this function passing in the artwork.bounds.size (instead of creating a new image size each time) then the memory gets allocated only once and calling imageWithSize again (with the same dimensions) does not reallocate new memory. This appears to only be true if you pass in the size of the original artwork image and don't return a custom size. If you need a custom size, perhaps you can then resize the UIImage on your own and not rely on this buggy method.
Related
I am facing a strange crash where an instance of NSNumber seems to be deallocated although it persists in array. I have created a system to download multiple files from remote server and have a block to indicate progress (an average progress really). And the computation of the progress produces a crash. The crash is not consistent and happens "usually" at can occur at any point. [_NSProgressFractionTuple floatValue]: unrecognized selector sent to instance 0x17042ab80 leads me to believe the NSNumber is somehow deallocated and I fail to see how this is possible.
To give the full method code:
- (void)downloadFilesFromURLs:(NSArray<NSString *> *)urlPaths withProgress:(void (^)(float progress))progressBlock completion:(void (^)(NSError *error))completionBlock {
NSMutableArray *toLoad = [[NSMutableArray alloc] init];
for(NSString *path in urlPaths) {
if([self fileForURL:path] == nil) {
[toLoad addObject:path];
}
}
NSInteger itemsToLoad = toLoad.count;
if(itemsToLoad <= 0) {
if(completionBlock) {
completionBlock(nil);
}
return;
}
// Set progresses to zero
__block NSMutableArray *progresses = [[NSMutableArray alloc] init];
for(int i=0; i<itemsToLoad; i++) [progresses addObject:[[NSNumber alloc] initWithFloat:.0f]];
__block NSInteger requestsOut = itemsToLoad;
__block NSError *latestError = nil;
for(int i=0; i<itemsToLoad; i++) {
NSInteger index = i;
[self downloadFileFromURL:toLoad[index] named:nil withProgress:^(float progress) {
progresses[index] = [[NSNumber alloc] initWithFloat:progress];
if(progressBlock) {
float overallProgress = .0f;
for(NSNumber *number in [progresses copy]) {
overallProgress += number.floatValue;
}
progressBlock(overallProgress/itemsToLoad);
}
} completion:^(NSString *filePath, NSError *error) {
if(error) latestError = error;
requestsOut -= 1;
if(requestsOut <= 0) {
if(completionBlock) {
completionBlock(latestError);
}
}
}];
}
}
Code explanation:
So this method accepts an array of URLs. It then checks if some of the files were already downloaded and creates a new array which only contains URLs that need to be downloaded. If all files exist or no URLs are provided then the completion is called and the operation breaks.
Next I create a mutable array and fill it with NSNumber instances all having a zero value. I remember how many requests will be made and I create a placeholder for an error. I iterate through all the URLs and initialize requests where each will report a progress and completion and both of these are on a main thread.
So in progress block I access the array of progresses to assign the new values through indexing. I then compute an average progress and report overall progress to an input block.
The request completion decreases the number of requests counter and when that one falls to zero the input completion is called.
The situation:
It all works as expected, the whole procedure is correct. The given values are all valid and all the files are there on the server and are accessible. When the app does not crash it all works as it should.
But when it crashes it crashes in
for(NSNumber *number in [progresses copy]) {
overallProgress += number.floatValue;
}
and the crash is random but in any case the number.floatValue seems to be accessing a memory that it shouldn't.
I now have a solution where I replaced the progresses array with pure C pointer float *progresses = malloc(sizeof(float)*itemsToLoad); which is freed on completion. It seems to work but still, what am I missing here? What could be the cause of array with NSNumbers not working here?
Some additional info:
Memory is OK, this is writing directly into files and even if it didn't the overall file size is relatively small
Disk space is OK
I was using #(progress) syntax but changed it to explicit allocation in hopes of removing the issue
progresses does not need __block, I added it just in case
Completion does not get called before all the progresses get called and even if it did I see no reason to crash the app
Thank you!
NSMutableArray is not thread safe. So even though there is no explicit memory management issue, if NSMutableArray is accessed at the same time by two different thread bad things can happen. I believe that dispatching the withProgress block in a serial queue would solve the issue.
I have a problem with memory full with RNDecryptor (+) in a cycle "for" i call this method es:
for (int i=0; i < [datasource fileCount]; i++) {
...
datacrypto = [RNDecryptor decryptData:datacrypto withSettings:kRNCryptorAES256Settings password:passcode error:nil];
....
}
RNDecryptor allocates memory but the calls do not empty and sooner or later there is no more free memory and CRASH ... its possible dealloc +[RNDecriptor...] between calls or otherwise as a solution ??
thank you.
Here's the implementation of the method.
+ (NSData *)decryptData:(NSData *)theCipherText withSettings:(RNCryptorSettings)settings password:(NSString *)aPassword error:(NSError **)anError
{
RNDecryptor *cryptor = [[self alloc] initWithPassword:aPassword
handler:^(RNCryptor *c, NSData *d) {}];
cryptor.settings = settings;
return [self synchronousResultForCryptor:cryptor data:theCipherText error:anError];
}
It's no a singleton or is there any other branch? If not, you better implement your own singleton pattern.
I came across a problem that only happened on iOS 8. I used the NSCache to store my images. After receiving memory warning, I would fetch images and store into cache again. However the cache can't store my images anymore after warning. It always returns nil to me by using objectForKey:.
Here's part of my code:
#interface ViewController ()
{
NSCache *imageCache;
}
#implementation ViewController
- (instancetype)init
{
self = [super init];
if (self) {
imageCache = [[NSCache alloc] init];
[imageCache setTotalCostLimit:1024 * 1024 * 1];
}
return self;
}
- (void)imageDownloadManager:(ImageDownloadManager *)manager didReceiveImage:(UIImage *)image forObjectID:(NSString *)objecID
{
NSUInteger cost = [UIImageJPEGRepresentation(image, 0) length];
image = [image smallImageWithCGSize:kImageThumbSize];
[self.imageCache setObject:image forKey:objectID cost:cost];
NSLog("image: %#",[self.imageCache objectForKey:objectID]); //return nil
}
#end
Thanks :)
SOLUTIONS
You have to set countLimit and the value must greater than 0.
Then you could use totalCostLimit as well.
I experienced the same problem (and only under iOS 8.1) and got it working by assigning a countLimit instead of a totalCostLimit.
// getter
- (NSCache *)cache
{
if (!_cache) {
_cache = [[NSCache alloc] init];
_cache.countLimit = aLimit;
}
return _cache;
}
I Created ViewController.Here two buttons are creating & releasing the NSObject.
// This Button for creating & assign the values to the NSObject.
- (IBAction)CreateObjectBtn:(id)sender
{
[cpFileObject methodForRetain];
// Here the tempFilePath = [[NSBundle mainBundle]pathForResource:#"Innum_Enna_Thozha-VmusiQ.Com" ofType:#"mp3"];
cpFileObject.fileData =[NSData dataWithContentsOfFile:tempFilePath];
cpFileObject.filePath = tempFilePath;
}
// This Button for **release** the NSObject from memory Pool.
- (IBAction)releaseObjectBtn:(id)sender
{
[cpFileObject methodForRelease];
}
The NSObject Name - CPFile.
// This Instance Method for **Increase** the allocation of NSObject.
-(void)methodForRetain
{
if (!fileData)
{
[fileData retain];
}
}
// This Instance Method for **Decrease** the allocation of NSObject.
- (void)methodForRelease
{
[fileData release];
NSLog(#"%lu Object released ",(unsigned long)self.retainCount);
}
The Problem is occurring in Creating & Releasing is working only two or three times. After if I click the CreateObjectBtn. The Error is showing like this.
If you see your Queue from Viewcontroller2 in
[Viewcontroller2 playBtn:]
you are trying to setFileData(setting property).
Just try to log your value what you are setting and FileData.
if (!fileData)
{
[fileData retain];
}
There is nothing to retain.
You should create an object first. Your [fileData retain] should be replaced by:
if (!fileData)
{
fileData =[[NSData alloc] initWithContentsOfFile:tempFilePath];
}
try this:
- (IBAction)CreateObjectBtn:(id)sender
{
cpFileObject.fileData =[NSData dataWithContentsOfFile:tempFilePath];
cpFileObject.filePath = tempFilePath;
[cpFileObject methodForRetain]; //move it down
}
-(void)methodForRetain
{
[fileData retain]; //it doesn't matter if fileData is nil;
}
Hi I have been trying to track down a very elusive memory leak in my application.
In an attempt to isolate the cause I have pared back the leaking code and even moved it to the app delegate for test purposes to try to eliminate as many factors as possible.
The following code is called from did finish launching with options.
- (void)loadDataAsTest {
NSLog(#"%s ", __PRETTY_FUNCTION__);
_library = [[ALAssetsLibrary alloc] init];
[_library enumerateGroupsWithTypes:ALAssetsGroupPhotoStream usingBlock:^(ALAssetsGroup *alAssetsGroup, BOOL *stop) {
#autoreleasepool {
if (alAssetsGroup) {
[alAssetsGroup enumerateAssetsUsingBlock:^(ALAsset *alAsset, NSUInteger index, BOOL *st) {
#autoreleasepool {
if (alAsset) {
NSLog(#"index = %u", index);
UIImage *image = [UIImage imageWithCGImage:[alAsset thumbnail]];
}
else {
}
}
}];
}
}
} failureBlock:^(NSError *error) {
}];
}
In my application this code leaks about 2000 CGImage enumerating over the 1000 photos in the photo stream. All 2000 UIImages that are created are released ok.
If I create a new empty project for testing purposes and put the same code in the app delegate and call it in exactly the same way it does not leak. All CGimage are released and all UIImage are released again about 2000 instances of each.
Both projects are using ARC.
Why would the same code called in exactly the same way behave differently between two projects?