Memory leaks when using ALAssetsLibrary - iphone

I'm developing an iPhone app using SDK 4.1 targeting iOS 4.1 or newer.
Instruments report memory leaks for the code below.
void (^resultBlock)(ALAsset *) = ^(ALAsset *asset) {
NSLog(#"resultBlock");
};
void (^failureBlock)(NSError *) = ^(NSError *error) {
NSLog(#"error");
};
NSURL *url = [NSURL URLWithString:#"assets-library://asset/asset.JPG?id=1000000176&ext=JPG"];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:url resultBlock:resultBlock failureBlock:failureBlock];
[library release];
I just put this code in viewDidLoad of my TestApp for test.(I'm using with UIImagePicker in my actual project.)
When I run the TestApp using Instrument(leak), it report memory leaks about 10 seconds after launch.
Can anybody tell me what's wrong with this code or is there something else that I should do?
Thanks.

There’s nothing wrong with your memory management in the code you’ve provided. If there is a leak, it’s in Apple’s frameworks or another part of your code. Just make sure you’re testing on the device—some frameworks have much more “polish” on the device.

Related

Iphone: unable to show photos using AlAssetsLibrary

I currently sending ipa to friends for testing. Funny thing is, one of my tester able view her photos stored on her phone which was running IOS 5 using iPhone 4.
Another 2 testers: one has iPhone 4 (IOS 4.3.3) , and iPhone 3GS (IOS 5.0.1) both of them can't see photos stored on their phone.
These are the code I have used:
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
void (^assetEnumerator)(ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *result, NSUInteger index, BOOL *stop) {
if(result != NULL) {
//NSLog(#"See Asset: %#", #"ggg");
[assets addObject:result];
}
};
NSLog(#"location = %i length = %i ", range->location, range->length );
void (^assetGroupEnumerator)(ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
NSRange *datarange = malloc(sizeof(NSRange));
range->total = [group numberOfAssets];
datarange->location = [group numberOfAssets] - range->location - range->length;
datarange->length = range->length;
NSLog(#" total = %i", range->total);
int location = [group numberOfAssets] - range->location - range->length;
if (location < 0)
{
datarange->location = 0;
datarange->length = [group numberOfAssets] - range->location;
}
NSIndexSet *indexset = [ [NSIndexSet alloc] initWithIndexesInRange:*datarange];
[group enumerateAssetsAtIndexes:indexset options:NULL
usingBlock:assetEnumerator];
[indexset release];
free(datarange);
[self loadAssetToScrollView:assets];
}
};
[assets release];
assets = [[NSMutableArray alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupSavedPhotos
usingBlock:assetGroupEnumerator
failureBlock: ^(NSError *error) {
NSLog(#"Failure");
}];
[library release];
I saw somebody say about asynchronous thing in some other threads but don't know is it the case. He say put dispatch_async in the enumerate group block.
Does anyone know what is wrong.
Additionally, one of tester with iOS 4.3.3 can show his photos after enabling location services under General->Setting. Why we have to enable it? Can we enabled it on code since it will be quite disturbing to the user who using our application.
Also on iOS 5.x you must retain your ALAssetsLibrary instance so long as you need to work with the collected assets. When you release your ALAssetsLibrary instance like in your code just after calling [library enumerateGroupsWithTypes:…] all the collected assets will be invalid.
See also the ALAssetsLibrary doc - overview:
"… The lifetimes of objects you get back from a library instance are tied to the lifetime of the library instance. …"
Yes, it is incredibly frustrating, but that is how it is, and you cannot enable location services in code (that is a good thing though).
I would move the first block ^assetGroupEnumerator to the heap by [[<#block#> copy] autorelease]. Why? Because this block would be autoreleased by the runloop, if there are many assets need to be enumerated through.
One more thing: don't use [self loadAssetToScrollView:assets]; inside the block but get the weak reference of self before the block like this:
__block YourExampleClassInstance *weakSelf = self;
and further use this weakSelf instance inside the block:
[weakSelf loadAssetToScrollView:assets];
void (^assetGroupEnumerator)… = ^(ALAssetsGroup *group, BOOL *stop) {
…
};
Why? To avoid retain cycles.

assetForURL with ios 5.0 doesn't work with device

- (void)thumbnail:(NSNumber *)index{
__block NSNumber *number = [NSNumber numberWithInt:[index intValue]];
ALAssetsLibrary *library = [ALAssetsLibrary sharedALAssetsLibrary];
ALAssetsLibraryAssetForURLResultBlock resultblock = ^(ALAsset *myasset)
{
CGImageRef iref = [myasset thumbnail];
if (iref) {
[delegate thumbnailDidLoad:[UIImage imageWithCGImage:iref] withIndex:number];
}
NSLog(#"RESSSSSSSSSSSSSSSSSSSSSSSSSSSSSULT");
};
ALAssetsLibraryAccessFailureBlock failureblock = ^(NSError *myerror)
{
NSLog(#"Error, can't get image - %#",[myerror localizedDescription]);
};
NSString *mediaurl = #"assets-library://asset/asset.JPG?id=5AF4118C-947D-4097-910E-47E19553039C&ext=JPG";
NSURL *asseturl = [NSURL URLWithString:mediaurl];
[library assetForURL:asseturl resultBlock:resultblock failureBlock:failureblock];
NSLog(#"asseturl %#",asseturl);
}
Here is my code and i have issue with my blocks - they works under simulator 5.0 but they don't work under device at all, it doesn't stop on break points and NSLogs don't work. With simulator all work correctly. Notice: CLAuthorizationStatus == kCLAuthorizationStatusAuthorized
Make sure that this whole function - (void)thumbnail:(NSNumber *)index... is either executed from the main thread or you are sure that the user has authorized your app to use location services. If you call it in the background and you don't yet have authorization, then the user will never be prompted for approval and neither the result nor failure blocks will be called.
as of iOS5 assetForURL works async. Make sure you call
[delegate thumbnailDidLoad:[UIImage imageWithCGImage:iref] withIndex:number];
on the main thread. This is easiest accomplished by using dispatch_async on the main queue.
Cheers,
Hendrik

How to get photos from camera roll?

I tried to use ALAssetLibrary to get albums and photos. This is my code:
void (^assetEnumerator)(struct ALAsset *, NSUInteger, BOOL *) = ^(ALAsset *result, NSUInteger index, BOOL *stop) {
if(result != NULL) {
NSLog(#"See Asset: %#", result);
}
};
void (^assetGroupEnumerator)(struct ALAssetsGroup *, BOOL *) = ^(ALAssetsGroup *group, BOOL *stop) {
if(group != nil) {
[group enumerateAssetsUsingBlock:assetEnumerator];
}
};
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupAll
usingBlock:assetGroupEnumerator
failureBlock: ^(NSError *error) {
NSLog(#"Failure");
}];
I'm getting exc_bad_access on : [group enumarateAssetsUsingBlock:assetEnumerator] and the group is not nil.
The strange thing is that this code is working if I create a new project, but in my old project it's not working.
SDK version is iOS 4.3
Tested on iPhoneSimulator 4.3
Can anyone could give me an ideea of what's happening?
Thanks.
As you crash on the one project but not on the other, are you sure the settings and configurations are appropriate?
In particular:
Check that the TARGET_DEPLOYMENT_OS is set to the minimum version the ALAsset framework is available.
Check that you have included all the requested frameworks (even if the linker should warn about this if you forgot to include it)
Moreover, the details of the crash (crashlog, exception details, ...) would be helpful if any.
Also are you sure the ALAssetLibrary isn't released before the enumeration (which is probably done asynchnously) ends? There is no release in your code in your question but maybe there is one in your real code?
AFAIK, enumerateGroupsWithTypes: executes its block on a secondary thread in an asynchrnous way (see this other question on SO), so that's probably your problem (you are trying to use a group that has been released from memory since you start your enumeration, you have to be sure the ALAssetLibrary is still in memory until the enumeration is done)

Different behavior between iPhone and iPad with the Assets Library

I'm using the Assets Library on an app to enumerate the Photos Events of a device.
My code works fine when i test it on my iPad. The Photos Events are enumerated and i can handle them perfectly. When I try the very same code on my iPhone, nothing happens (and I have Photos Events on this device too). It looks as if the enumeration code was not even called (ie no log appears in the console, cf. code).
Here is the code :
- (void)loadEvents {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library enumerateGroupsWithTypes:ALAssetsGroupEvent
usingBlock:^(ALAssetsGroup *group, BOOL *stop) {
if (group) {
[photosEventsArray addObject:group];
NSLog(#"Adding group");
} else {
NSLog(#"End of the enumeration");
}
}
failureBlock: ^(NSError *error) {
NSLog(#"Failure while enumerating assets: %#", error);
}];
[library release];
NSLog(#"Found %d events", photosEventsFound);
[self performSelectorOnMainThread:#selector(stopSpinner) withObject:nil waitUntilDone:YES];
[pool drain];
}
My deployment target is iOS 4.1.
Any idea of what is going wrong here?
After more investigation, it seems that on iOS 4.3.5, the enumerateGroupsWithTypes method HAS to be called from the main thread.
I've patched my code this way (setting NO from iPhone and iPod Touch, and YES from iPad):
if (scanAssetsInBackground) {
[self performSelectorInBackground:#selector(loadEvents) withObject:nil];
} else {
[self performSelectorOnMainThread:#selector(loadEvents) withObject:nil waitUntilDone:YES];
}
Works fine with that patch.
There's not much information about this in Apple docs and there's no way to know which way (background or main thread) is the right way to scan assets libraries.

using UISaveVideoAtPathToSavedPhotosAlbum

On simulator UISaveVideoAtPathToSavedPhotosAlbum cannot save movie into photo album unless photo album is open. Can it? How can I check if Photo Album is running and run it silently from my app? Chances are that on a real iPhone/Pad it will be already running but just in case...?
My movie is in Resources of my app. I want to copy it to album. Here is the methods that I put in my AppDelegate.m to accomplish that:
-(void)downloadVideo:(NSString *)sampleMoviePath{
if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(sampleMoviePath)){
UISaveVideoAtPathToSavedPhotosAlbum(sampleMoviePath, self, #selector(video:didFinishSavingWithError: contextInfo:), sampleMoviePath);
}
}
-(void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
NSLog(#"Finished with error: %#", error);
}
I call it like this:
NSString *sampleMediaPathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"movie_01.m4v"];
[self downloadVideo:sampleMediaPathFromApp];
Error message that I get:
Finished saving video with error: Error Domain=ALAssetsLibraryErrorDomain Code=-3310 "Data unavailable" UserInfo=0x564ab10 {NSLocalizedRecoverySuggestion=Launch the Photos application, NSLocalizedDescription=Data unavailable}
Naturally, if I open Photo album and then run my app it works fine.
Thanks a lot for your input.
BG
----edits----
I have edited the code so it is more readable - sorry I do not have an answer, but it is a good question - I had to put this here or else I could not submit the changes. --jwk82
You can save it as..
ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init];
[assetLibrary writeVideoAtPathToSavedPhotosAlbum:url completionBlock:^(NSURL *assetURL, NSError *error){