Monday, October 24, 2011

Invalid attempt to access ALAssetPrivate past the lifetime of its owning ALAssetsLibrary

Trying to upload hundreds of images from device, but application occasionally crashes with this error note:
invalid attempt to access ALAssetPrivate past the lifetime of its owning ALAssetsLibrary
Basically it means what it says. Library was closed before reading the image data was completed. As result upload failed due missing or too short data. ALAssetsLibrary Class Reference says:
You create an instance of ALAssetsLibrary using alloc and init:

ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init];

The lifetimes of objects you get back from a library instance are tied to the lifetime of the library instance.
How to fix it? Here's one interesting singletonish way on DaveOnCode blog "Invalid attempt to access ALAssetPrivate past the lifetime of its owning ALAssetsLibrary… WTF?" with an exceptionally accurate title :)

How did I fix it? Just making sure library did stay open as long as needed. However that was not enough. After forcing the ALAssets library open revealed another bug: every 6th upload failed!

Normal operation was 1) open library 2) open file 3) open upload stream, but every now and then stream was opened BEFORE file was open. Since there was no data to send, upload failed. Here's a rough sketch how I fixed that problem:

This is the "main routine" to setup upload:
__block FileUploader *blockSelf = self;
ALAssetsLibraryAssetForURLResultBlock successBlock = ^(ALAsset *asset)
{
  blockSelf.fileStream =
    [blockSelf myStreamFromAsset:asset];
  [blockSelf.fileStream open];
  blockSelf.connection =
    [NSURLConnection connectionWithRequest:request
    delegate:blockSelf];
};

ALAssetsLibraryAccessFailureBlock failureBlock = ^(NSError *aError)
{
  NSLog(@"%@", aError);
};

self.library = [[ALAssetsLibrary alloc] init];
[self.library assetForURL:assetUrl
  resultBlock:successBlock
  failureBlock:failureBlock];
The NSStreamDelegate callback to handle upload:
#pragma mark - NSStreamDelegate
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
  switch (eventCode)
  {
    case NSStreamEventOpenCompleted:
    {
      NSLog(@"upload stream opened");
    } break;
    case NSStreamEventHasSpaceAvailable:
    {
      NSLog(@"upload stream opened");
      [self upload];
    } break;
}
Helper routine to wrap around a NSTimer, which delays start of upload in case opening file takes longer than opening upload stream. So far one 0.5 second delay has been enough, but I'm willing to try 5 times until giving up:
- (void)upload
{
  if (!self.fileStream)
  {
    if (self.sendCount++ < 5)
    {
      [self.sendTimer invalidate];
      self.sendTimer = [NSTimer scheduledTimerWithTimeInterval:.5f
      target:self
      selector:@selector(sendData)
      userInfo:nil
      repeats:NO];
    }
    else
    {
      NSLog(@"File open failed 5 times");
      [self myCancel];
    }
    return;
  }

  // Do the actual upload
  // Check e.g. SimpleURLConnections by Apple
}
Now I can upload at least 215 images with iOS5. Wonder what will be the next problem revealed by this fix :)

No comments:

Post a Comment