I have a nested for loop in where I call a getSnapShotData method many times and write this data to disk. I've noticed I get too much memory build up doing this and I run out of memory, so I thought this would be a good use case for using dispatch semaphore.
I'm still running out of memory, so I'm not sure if I'm using the semaphore properly. Essentially I want the next loop to wait until the prior loop's data is written to disk, as I think that will free the memory. But I could be wrong. Thank you for your help.
Code:
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
for (NSDictionary *sub in self.array)
{
NSArray *lastArray = [sub objectForKey:#"LastArray"];
for (NSDictionary *dict in lastArray)
{
currentIndex ++;
NSData *frame = [NSData dataWithData:[self getSnapshotData]];
savePath = [NSString stringWithFormat:#"%#/%lu.png",frameSourcePath,(unsigned long)currentIndex];
BOOL nextLoop = [frame writeToFile:savePath options:0 error:nil];
frame = nil;
if (nextLoop)
{
dispatch_semaphore_signal(sema);
}
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
}
- (NSData *)getSnapshotData
{
UIGraphicsBeginImageContextWithOptions(self.containerView.bounds.size, NO, 0.0);
[self.containerView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *snapShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return [NSData dataWithData:UIImagePNGRepresentation(snapShot)];
}
You have too many autoreleased objects. Add an autorelease pool to improve the situation instead of using the semaphore.
for (NSDictionary *sub in self.array)
{
NSArray *lastArray = [sub objectForKey:#"LastArray"];
for (NSDictionary *dict in lastArray)
{
#autoreleasepool {
currentIndex ++;
NSData *frame = [NSData dataWithData:[self getSnapshotData]];
savePath = [NSString stringWithFormat:#"%#/%lu.png",frameSourcePath,(unsigned long)currentIndex];
BOOL nextLoop = [frame writeToFile:savePath options:0 error:nil];
}
}
}
Related
Following are the method an methods call tree that cause the memory leak
//get the exif info of image asset background
#autoreleasepool {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
self.dataSource = [NSMutableArray new];
__weak typeof(self) weakSelf = self;
dispatch_async(queue, ^{
for (int i = 0; i < self.selectImageList.count; i++) {
PFALAssetImageItemData *dataEntity = weakSelf.selectImageList[i];
//getting the object usering an image asset
ImageExifInfoEntity *imageExifEntity = [ImageExifInfoEntity getAlbumImageFromAsset:dataEntity.imageAsset imageOryder:i];
LOG(#"%#",imageExifEntity.description);
[weakSelf.dataSource addObject:imageExifEntity];
}
//back main thread update views
dispatch_async(dispatch_get_main_queue(), ^{
[self.collectionView reloadData];
[self hideHud];
});
});
}
In this code I want to creat ImageExifInfoEntity using a static method with an asset in a thread:
[ImageExifInfoEntity getAlbumImageFromAsset:dataEntity.imageAsset imageOryder:i];
In this method,it create an new object of ImageExifInfoEntity type,and get the exif Dictionary using a static method
+(ImageExifInfoEntity *)getAlbumImageFromAsset:(ALAsset *)asset category:(NSString *)category imageOryder:(NSInteger)imageOrder{
ImageExifInfoEntity *albumImage = [ImageExifInfoEntity new];
..........
albumImage.imageSize = [UIImage imageSizeWithAlasset:asset];
albumImage.exifDic = [ImageExifInfoEntity getExifInfoFromAsset:asset] == nil ? #{}:[ImageExifInfoEntity getExifInfoFromAsset:asset];
..........
}
Finally,I get an exif dictionary using this method where the memory leak happed
+(NSDictionary *)getExifInfoFromAsset:(ALAsset *)asset {
NSDictionary *_imageProperty;
__weak ALAsset *tempAsset = asset;
ALAssetRepresentation *representation = tempAsset.defaultRepresentation;
uint8_t *buffer = (uint8_t *)malloc(representation.size);
NSError *error;
NSUInteger length = [representation getBytes:buffer fromOffset:0 length:representation.size error:&error];
NSData *data = [NSData dataWithBytes:buffer length:length];
CGImageSourceRef cImageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
CFDictionaryRef imageProperties = CGImageSourceCopyPropertiesAtIndex(cImageSource, 0, NULL);
_imageProperty = (__bridge_transfer NSDictionary*)imageProperties;
free(buffer);
NSLog(#"image property: %#", _imageProperty);
return _imageProperty;
}
here is the instrument analyze result
call tree
the final method that cause memory leaks
CGImageSourceCreateWithData() is a creation function.
CGImageSourceRef cImageSource = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
According to the CF memory rules, you have to release it with CFRelease().
replace __bridge with __bridge_transfer so that ARC will be in charge of freeing the memory
I think you should call CFRelease(cImageSource); to release the image source. See document: https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CGImageSource/#//apple_ref/c/func/CGImageSourceCreateWithData
An image source. You are responsible for releasing this object using CFRelease.
I try to use panoramaGL framework to show panorama. The server-side returns several tile images for every side of cube panorama. So I need to load this images asyncroniously into the array and after this I need to make a big side texture from this images - the code for the first part of this task is:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
int columnCount;
int rowCount;
NSString *side;
if ([level isEqual: #"3"]) {
columnCount = 1;
rowCount = 1;
} else if ([level isEqual: #"2"]) {
columnCount = 2;
rowCount = 2;
... here I prepare the url string parameters
if (face == PLCubeFaceOrientationFront)
side = #"F";
else if (face == PLCubeFaceOrientationBack)
side = #"B";
... here I prepare the url string parameters
self.tileArray = [[NSMutableArray alloc] initWithCapacity:columnCount];
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {
NSString *tileUrl = [NSString stringWithFormat:#"%d_%d", columnIndex, rowIndex];
NSMutableArray *tileUrlComponents = [NSMutableArray array];
[tileUrlComponents addObject: panoramaData[#"id"]];
[tileUrlComponents addObject: side];
[tileUrlComponents addObject: level];
[tileUrlComponents addObject: tileUrl];
NSString *tileIdString = [tileUrlComponents componentsJoinedByString:#"/"];
NSString *panoramaIdUrlString = [NSString stringWithFormat:#"http://SOMEURL=%#", tileIdString];
NSURL *url = [NSURL URLWithString:panoramaIdUrlString];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:req queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *res, NSData *data, NSError *err) {
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
NSMutableArray *array = [self.tileArray objectAtIndex:columnIndex];
[array replaceObjectAtIndex:rowIndex withObject:image];
[self.tileArray addObject:array];
});
}];
NSLog(#"The URL for the %# tile is %#", tileUrl, tileIdString);
}
}
});
The main question - how can I understand, that my array is loaded - and I can now work with the images in it. The second question is that I get the empty array now unfortunately. Any help?
You can do something like this
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
dispatch_sync(dispatch_get_main_queue(), ^{
NSData *data = [[NSData alloc] initWithContentsOfURL: ImageURL];
UIImage *image = [[UIImage alloc] initWithData: data];
[UIImageJPEGRepresentation(image, 100) writeToFile: imagePath atomically: YES];
});
});
where imagePath ia a path in your applications directory..You can make a temporary directory and store the images there…and while retrieving you can just use the simple method as..
UIImage *image=[UIImage imageWithCOntentsOFFile:imagePath];.
It worked for me…just give it a try
I am not sure you considered using operations.
This is how I would do:
Create NSOperation for each request (make sure in your code the operation stays alive until it has finished loading)
Add each operation to NSOperationQueue. (By calling -operations you can see how many operations are in progress)
If you implement NSOperationQueue on back thread you can use -waitUntilAllOperationsAreFinished (this method blocks current thread until all your images have been downloaded) and then execute your code to display images. You can alternatively add all NSOperation's to NSArray and add to NSOperationQueue by using -(void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait
I am trying to store images from a remote location to an NSMutableArray using a GCD block. The following code is being called in viewDidLoad, and the images are to be populated in a UICollectionView:
dispatch_apply(self.count, dispatch_get_global_queue(0, 0), ^(size_t i){
NSString *strURL = [NSString stringWithFormat:#"%#%zu%#", #"http://theURL.com/popular/", i, #".jpg"];
NSURL *imageURL = [NSURL URLWithString:strURL];
NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
UIImage *oneImage =[UIImage imageWithData:imageData];
if(oneImage!=nil){
[self.imageArray addObject:oneImage];
}
});
PROBLEM: the images are not being linearly stored.
eg.
[self.imageArray objectAtIndex:2] is not 2.jpg
Even though it is setting first and last image correct, rest are all jumbled up.
Another way to do this (What I basically need, minus the time consumed and memory overhead):
for (int i=0; i<=[TMAPopularImageManager sharedInstance].numberOfImages-1; i++){
NSString *strURL = [NSString stringWithFormat:#"%#%d%#", #"http://theURL.com/popular/", i, #".jpg"];
NSURL *imageURL = [NSURL URLWithString:strURL];
NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
UIImage *oneImage =[UIImage imageWithData:imageData];
if(oneImage!=nil){
[self.imageArray addObject:oneImage];
}
}
Is there a better way to implement the GCD block in this case? I need the images in the array sequentially named.
I ran this about 10 times and it did not print in order once:
NSInteger iterations = 10;
dispatch_apply(iterations, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL), ^(size_t index) {
NSLog(#"%zu", index);
});
My suggestion, and what I have done in the past, is to just run a for loop inside the block of a background thread:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL);
dispatch_async(queue, ^{
int iterations = 0;
for (int x = 0; x < iterations; ++x) {
// Do stuff here
}
dispatch_async(dispatch_get_main_queue(), ^{
// Set content generated on background thread to main thread
});
});
When using background threads, it is important to make sure that either the the objects you initialized on the main thread are thread-safe or that you initialize objects in the background and then set the main thread's objects with the background-thread-created objects like in the above example. This is especially true with Core Data.
EDIT:
Seems like the iterations using dispatch_apply return immediately so they will probably execute out of order when doing anything meaningful. If you run these two, you will see that printf always runs in order but NSLog does not:
NSInteger iterations = 10;
dispatch_apply(iterations, the_queue, ^(size_t idx) {
printf("%zu\n", idx);
});
dispatch_apply(iterations, the_queue, ^(size_t idx) {
NSLog(#"%zu\n", idx);
});
In my opinion, it would be best to run a standard for statement in a background thread rather than dispatch_apply if the order is important.
EDIT 2:
This would be your implementation:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long) NULL);
dispatch_async(queue, ^{
int iterations = 0;
NSMutableArray *backgroundThreadImages = [NSMutableArray array];
for (int i = 0; i < iterations; ++i) {
NSString *strURL = [NSString stringWithFormat:#"%#%i%#", #"http://theURL.com/popular/", i, #".jpg"];
NSURL *imageURL = [NSURL URLWithString:strURL];
NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
UIImage *oneImage =[UIImage imageWithData:imageData];
if(oneImage!=nil){
[backgroundThreadImages addObject:oneImage];
}
}
dispatch_async(dispatch_get_main_queue(), ^{
self.imageArray = backgroundThreadImages;
});
});
Not sure if applicable to your situation, but if the images are going to be displayed in a UICollectionView, it is better to load them when required. That is, when the delegate methods (cellFor...) are invoked.
That way you only load what is needed, when it's needed.
If you need to have the images in an array for some other purpose, ignore this answer.
two ways: sort the array afterwards
or
// assume self.imageArray is empty
for (int i = 0; i < self.count; ++i) // fill array with NSNull object
[self.imageArray addObject:[NSNull null]]];
dispatch_apply(self.count, dispatch_get_global_queue(0, 0), ^(size_t i){
NSString *strURL = [NSString stringWithFormat:#"%#%zu%#", #"http://theURL.com/popular/", i, #".jpg"];
NSURL *imageURL = [NSURL URLWithString:strURL];
NSData *imageData = [NSData dataWithContentsOfURL: imageURL];
UIImage *oneImage =[UIImage imageWithData:imageData];
if(oneImage!=nil){
self.imageArray[i] = oneImage;
}
});
I am not sure it is thread-safe to operate NSMutableArray this way, you may need
#synchronized(self.imageArray) {
self.imageArray[i] = oneImage;
}
In my app, I want to Retrieve Photos and Videos from the Photo Library, and then save them into my app documents directory.
Following is my codes:
- (UIImage *)getImageFromAsset:(ALAsset *)asset type:(NSInteger)nType
{
ALAssetRepresentation *assetRepresentation = [asset defaultRepresentation];
CGImageRef imageReference = [assetRepresentation fullResolutionImage];
CGFloat imageScale = [assetRepresentation scale];
UIImageOrientation imageOrientation = (UIImageOrientation)[assetRepresentation orientation];
UIImage *iImage = [[UIImage alloc] initWithCGImage:imageReference scale:imageScale orientation:imageOrientation];
return iImage;
}
- (UIImage *)getImageAtIndex:(NSInteger)nIndex type:(NSInteger)nType
{
return [self getImageFromAsset:(ALAsset *)_assetPhotos[nIndex] type:nType];
}
......
for (NSIndexPath *index in _dSelected) {
DLog(#"the selected index is %#", index);
image = nil;
image = [ASSETHELPER getImageAtIndex:index.row type:ASSET_PHOTO_FULL_RESOLUTION];
NSString *name = [ASSETHELPER getImageNameAtIndex:index.row];
NSString *filepath = [files stringByAppendingPathComponent:name];
NSString *aliapath = [alias stringByAppendingPathComponent:name];
aliapath = [aliapath stringByAppendingString:THUMBNAIL];
DLog(#"the files is %# the alias is %#", filepath, aliapath);
image = nil;
}
If I retrieve just 20 or 30 photos, it would be ok, but if I retrieve too many photos(maybe 50 ones), the App will Terminate due to Memory Pressure. I think I have set the image to nil after every one image , so the ios system shoud get back the memory after each for loop. But why Memory leak happens?
ARC still needs to manage your memory. As long as you are in your for loop, ARC will never have the chnace to release your memory. You need to put the inside of your loop within an autorelase pool.
for (NSIndexPath *index in _dSelected) {
#autoreleasepool {
DLog(#"the selected index is %#", index);
image = nil;
image = [ASSETHELPER getImageAtIndex:index.row type:ASSET_PHOTO_FULL_RESOLUTION];
NSString *name = [ASSETHELPER getImageNameAtIndex:index.row];
NSString *filepath = [files stringByAppendingPathComponent:name];
NSString *aliapath = [alias stringByAppendingPathComponent:name];
aliapath = [aliapath stringByAppendingString:THUMBNAIL];
DLog(#"the files is %# the alias is %#", filepath, aliapath);
image = nil;
}
}
This will let your memory get freed as you deal with each image.
Hey I use this method to return NSData.
-(NSData*)getPersonPicture:(NSDictionary *)person {
NSData *imageData = nil;
if (![person valueForKey:FIELD_PERSON_IMAGEDATA]) {
return imageData;
}
if ([[[person valueForKey:FIELD_PERSON_IMAGEDATA] description]containSubString:#"http"]) {
return [NSData dataWithContentsOfURL:[NSURL URLWithString:[person valueForKey:FIELD_PERSON_IMAGEDATA]]];
} else {
ABRecordRef _person = ABAddressBookGetPersonWithRecordID(_aBook,[[person valueForKey:FIELD_PERSON_IMAGEDATA] integerValue]);
imageData = (__bridge NSData*)ABPersonCopyImageDataWithFormat(_person, kABPersonImageFormatThumbnail);
return imageData;
}
}
I can't figure when I need to release this imageData. I can't leave it like this , right?
If you are using ARC, then ARC need to take ownership of the Core Foundation object - which means, ARC will become responsible for it. You can accomplish this with macro CFBridgingRelease:
imageData = CFBridgingRelease(ABPersonCopyImageDataWithFormat(_person, kABPersonImageFormatThumbnail));
return imageData;
Using non-ARC:
(note: usually, we should leveraging ARC!)
-(NSData*)getPersonPicture:(NSDictionary *)person {
NSData *imageData = nil;
if (![person valueForKey:FIELD_PERSON_IMAGEDATA]) {
return imageData;
}
if ([[[person valueForKey:FIELD_PERSON_IMAGEDATA] description]containSubString:#"http"]) {
return [NSData dataWithContentsOfURL:[NSURL URLWithString:[person valueForKey:FIELD_PERSON_IMAGEDATA]]];
} else {
ABRecordRef _person = ABAddressBookGetPersonWithRecordID(_aBook,[[person valueForKey:FIELD_PERSON_IMAGEDATA] integerValue]);
CFDataRef cfData = ABPersonCopyImageDataWithFormat(_person, kABPersonImageFormatThumbnail);
imageData = [[(NSData*)cfData retain] autorelease];
return imageData;
}
}