Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error trying to assigning __block ALAsset from inside assetForURL:resultBlock:

I am trying to create a method that will return me a ALAsset for a given asset url. (I need upload the asset later and want to do it outside the result block with the result.)

+ (ALAsset*) assetForPhoto:(Photo*)photo
{
    ALAssetsLibrary* library = [[[ALAssetsLibrary alloc] init] autorelease];
    __block ALAsset* assetToReturn = nil;

    NSURL* url = [NSURL URLWithString:photo.assetUrl];
    NSLog(@"assetForPhoto: %@[", url);

    [library assetForURL:url resultBlock:^(ALAsset *asset) 
    {
        NSLog(@"asset: %@", asset);
        assetToReturn = asset;
        NSLog(@"asset: %@ %d", assetToReturn, [assetToReturn retainCount]);        

    } failureBlock:^(NSError *error) 
    {
        assetToReturn = nil;
    }];

    NSLog(@"assetForPhoto: %@]", url);
    NSLog(@"assetToReturn: %@", assetToReturn); // Invalid access exception coming here.

    return assetToReturn;
}

The problem is assetToReturn gives an EXC_BAD_ACCESS.

Is there some problem if I try to assign pointers from inside the block? I saw some examples of blocks but they are always with simple types like integers etc.

like image 924
mithuntnt Avatar asked Oct 02 '11 08:10

mithuntnt


2 Answers

A few things:

  1. You must keep the ALAssetsLibrary instance around that created the ALAsset for as long as you use the asset.
  2. You must register an observer for the ALAssetsLibraryChangedNotification, when that is received any ALAssets you have and any other AssetsLibrary objects will need to be refetched as they will no longer be valid. This can happen at any time.
  3. You shouldn't expect the -assetForURL:resultBlock:failureBlock:, or any of the AssetsLibrary methods with a failureBlock: to be synchronous. They may need to prompt the user for access to the library and will not always have their blocks executed immediately. It's better to put actions that need to happen on success in the success block itself.
  4. Only if you absolutely must make this method synchronous in your app (which I'd advise you to not do), you'll need to wait on a semaphore after calling assetForURL:resultBlock:failureBlock: and optionally spin the runloop if you end up blocking the main thread.

The following implementation should satisfy as a synchronous call under all situations, but really, you should try very hard to make your code asynchronous instead.

- (ALAsset *)assetForURL:(NSURL *)url {
    __block ALAsset *result = nil;
    __block NSError *assetError = nil;
    dispatch_semaphore_t sema = dispatch_semaphore_create(0);

    [[self assetsLibrary] assetForURL:url resultBlock:^(ALAsset *asset) {
        result = [asset retain];
        dispatch_semaphore_signal(sema);
    } failureBlock:^(NSError *error) {
        assetError = [error retain];
        dispatch_semaphore_signal(sema);
    }];


    if ([NSThread isMainThread]) {
        while (!result && !assetError) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
    }
    else {
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    }

    dispatch_release(sema);
    [assetError release];

    return [result autorelease];
}
like image 159
Ashley Clark Avatar answered Sep 23 '22 09:09

Ashley Clark


You should retain and autorelease the asset:

// ...
assetToReturn = [asset retain];
// ...

return [assetToReturn autorelease];
like image 34
hypercrypt Avatar answered Sep 24 '22 09:09

hypercrypt