2

So in a similar vein to this recently posted question, I'm having problems integrating Amazon's AWS Obj-C library with my Swift app. I have an NSOperation that handles file uploading to S3 using their Transfer Utility library which includes support for background file transfers. Having recently released our app I've been seeing some crashes in the code that rewires up the progress handler when the app is brought back to the foreground. The code is adapted from their Obj-C example:

- (void)viewDidLoad {
    [super viewDidLoad];

    ...

    AWSS3TransferUtility *transferUtility = [AWSS3TransferUtility defaultS3TransferUtility];
    [transferUtility
     enumerateToAssignBlocksForUploadTask:^(AWSS3TransferUtilityUploadTask *uploadTask, __autoreleasing AWSS3TransferUtilityUploadProgressBlock *uploadProgressBlockReference, __autoreleasing AWSS3TransferUtilityUploadCompletionHandlerBlock *completionHandlerReference) {
         NSLog(@"%lu", (unsigned long)uploadTask.taskIdentifier);

         // Use `uploadTask.taskIdentifier` to determine what blocks to assign.

         *uploadProgressBlockReference = // Reassign your progress feedback block.
         *completionHandlerReference = // Reassign your completion handler.
     }
     downloadTask:^(AWSS3TransferUtilityDownloadTask *downloadTask, __autoreleasing AWSS3TransferUtilityDownloadProgressBlock *downloadProgressBlockReference, __autoreleasing AWSS3TransferUtilityDownloadCompletionHandlerBlock *completionHandlerReference) {
         NSLog(@"%lu", (unsigned long)downloadTask.taskIdentifier);

         // Use `downloadTask.taskIdentifier` to determine what blocks to assign.

         *downloadProgressBlockReference =  // Reassign your progress feedback block.
         *completionHandlerReference = // Reassign your completion handler.
     }];
}

to my Swift version, which crashes with an EXC_BAD_ACCESS KERN_INVALID_ADDRESS when attempting to dereference the newProgressPointer:

// Swift 2.3

class AttachmentQueue: NSOperationQueue {

    ...

    /**
     Recreates `UploadOperation` instances for any that were backgrounded by the user leaving the
     app.
     */
    func addBackgroundedOperations() {
        let transferUtility = AWSS3TransferUtility.defaultS3TransferUtility()
        transferUtility.enumerateToAssignBlocksForUploadTask({ (task, progress, completion) -> Void in
            guard let operation = UploadOperation(task: task, oldProgressPointer: progress, oldCompletionPointer: completion) else { return }
            self.addOperation(operation)
        }, downloadTask: nil)
    }

}
 /// An `UploadOperation` is an `NSOperation` that is responsible for uploading an attachment asset
 /// file (photo or video) to Amazon S3. It leans on `AWSS3TransferUtility` to get the actual
 /// uploading done.
 class UploadOperation: AttachmentOperation {

    ...

    /// An `AutoreleasingUnsafeMutablePointer` to the upload progress handler block.
    typealias UploadProgressPointer = AutoreleasingUnsafeMutablePointer<(@convention(block) (AWSS3TransferUtilityTask, NSProgress) -> Void)?>

    /// An `AutoreleasingUnsafeMutablePointer` to the upload completion handler block.
    typealias UploadCompletionPointer = AutoreleasingUnsafeMutablePointer<(@convention(block) (AWSS3TransferUtilityUploadTask, NSError?) -> Void)?>


     /**
      A convenience initializer to be used to re-constitute an `AWSS3TransferUtility` upload task that
      has been moved to the background. It should be called from `.enumerateToAssignBlocksForUploadTask()`
      when the app comes back to the foreground and is responsible for re-hooking-up its progress and
      completion handlers.

      - parameter task:                 The `AWSS3TransferUtilityTask` that needs re-hooking-up.
      - parameter oldProgressPointer:   An `AutoreleasingUnsafeMutablePointer` to the original progress handler.
      - parameter oldCompletionPointer: An `AutoreleasingUnsafeMutablePointer` to the original completion handler.
      */
     convenience init?(task: AWSS3TransferUtilityUploadTask, oldProgressPointer: UploadProgressPointer, oldCompletionPointer: UploadCompletionPointer) {

         self.init(attachment: nil) // Actual implementation finds attachment record

         // Re-connect progress handler
         var progressBlock: AWSS3TransferUtilityProgressBlock = self.uploadProgressHandler
         let newProgressPointer = UploadProgressPointer(&progressBlock)
         print("newProgressPointer", newProgressPointer)
         print("newProgressPointer.memory", newProgressPointer.memory) // Throws EXC_BAD_ACCESS KERN_INVALID_ADDRESS
         oldProgressPointer.memory = newProgressPointer.memory

         // Re-connect completion handler
         var completionBlock: AWSS3TransferUtilityUploadCompletionHandlerBlock = self.uploadCompletionHandler
         let newCompletionPointer = UploadCompletionPointer(&completionBlock)
         oldCompletionPointer.memory = newCompletionPointer.memory
     }

     /**
      Handles file upload progress. `AWSS3TransferUtility` calls this repeatedly while the file is
      uploading.

      - parameter task:     The `AWSS3TransferUtilityTask` for the current upload.
      - parameter progress: The `NSProgress` object for the current upload.
      */
     private func uploadProgressHandler(task: AWSS3TransferUtilityTask, progress: NSProgress) {

         // We copy the `completedUnitCount` to operation but it would be nicer if we could just
         // reference the one passed to us instead of having two separate instances
         self.progress.completedUnitCount = progress.completedUnitCount

         // Calculate file transfer rate using an exponential moving average, as per https://stackoverflow.com/a/3841706/171144
         let lastRate = self.transferRate
         let averageRate = Double(progress.completedUnitCount) / (NSDate.timeIntervalSinceReferenceDate() - self.uploadStartedAt!)
         self.transferRate = self.smoothingFactor * lastRate + (1 - self.smoothingFactor) * averageRate;
         progress.setUserInfoObject(self.transferRate, forKey: NSProgressThroughputKey)
     }

     /**
      Handles file upload completion. `AWSS3TransferUtility` calls this when the file has finished
      uploading or is aborted due to an error.

      - parameter task:  The `AWSS3TransferUtilityTask` for the current upload.
      - parameter error: An instance of `NSError` if the upload failed.
      */
     private func uploadCompletionHandler(task: AWSS3TransferUtilityUploadTask, error: NSError?) {

        ...

     }

     ...

 }

Why is the pointer memory reference invalid straight after creating it?

Being new to iOS development and having no real experience with Obj-C (or other non-memory-managed languages) I'm a little lost. If anyone can shed some light that would be greatly appreciated.

EDIT:

Swift method signature for enumerateToAssignBlocksForUploadTask(…)

/**
 Assigns progress feedback and completion handler blocks. This method should be called when the app was suspended while the transfer is still happening.

 @param uploadBlocksAssigner   The block for assigning the upload pregree feedback and completion handler blocks.
 @param downloadBlocksAssigner The block for assigning the download pregree feedback and completion handler blocks.
 */
public func enumerateToAssignBlocksForUploadTask(uploadBlocksAssigner: ((AWSS3TransferUtilityUploadTask, AutoreleasingUnsafeMutablePointer<(@convention(block) (AWSS3TransferUtilityTask, NSProgress) -> Void)?>, AutoreleasingUnsafeMutablePointer<(@convention(block) (AWSS3TransferUtilityUploadTask, NSError?) -> Void)?>) -> Void)?, downloadTask downloadBlocksAssigner: ((AWSS3TransferUtilityDownloadTask, AutoreleasingUnsafeMutablePointer<(@convention(block) (AWSS3TransferUtilityTask, NSProgress) -> Void)?>, AutoreleasingUnsafeMutablePointer<(@convention(block) (AWSS3TransferUtilityDownloadTask, NSURL?, NSData?, NSError?) -> Void)?>) -> Void)?)
Community
  • 1
  • 1
fractious
  • 1,642
  • 16
  • 30

1 Answers1

1

I don't think you should need most (or any) of these pointers. Take a look at the Swift example code from AWS and see if it doesn't do what you're looking for.

Creating pointers the way you have isn't safe in Swift. This is probably way more information than you need (you shouldn't have to work this hard), but here's what may be happening (this explanation doesn't feel completely right in this case, but it's something that can happen, so it's worth knowing about):

  • You create a pointer to a local (stack) variable, progressBlock.
  • The system sees that progressBlock is no longer accessed anywhere else in scope.
  • As it's allowed to do, ARC destroys progressBlock.
  • You unsafely access the pointer to the destroyed variable, and crash.

I say this doesn't feel right, because there's a second reference to the closure that should keep the closure alive anyway, but as a rule, creating pointers with a constructor this way is very dangerous.

(It's possible this crashes because you can't print a @convention(block) closure; I've never tried to do that. It's not a very normal thing to try to print.)

In any case, if you really did need to do this kind of thing (and I think you don't), you'd need to do it along these lines:

withUnsafeMutablePointer(to: self.uploadProgressHandler) { newProgressPointer in 
    ... newProgressPointer is safe to use in this block ...
}

But as a rule, if you're converting ObjC code (not pure C, but just ObjC), and find you're having to create a lot of Unsafe objects, you may be walking down the wrong path. Most ObjC things bridge to Swift fine w/o Unsafe.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Hey Rob, many thanks for taking the time to answer my question. Unfortunately the Swift sample project you referenced doesn't include example code for rewiring up the handlers. I'll see if I can track down the example that led me to where I am now. I've updated my question to include the method signature for `enumerateToAssignBlocksForUploadTask(…)` that the library provides. As much as I'd like to avoid using pointers, as you can see it passes an `AutoreleasingUnsafeMutablePointer` to the block, so I'm not sure how I might go about this without using them. – fractious Jan 27 '17 at 10:28
  • I believe all you need to do here is: `oldProgressPointer.memory = self.uploadProgressHandler`. There's no reason to create your own `AutoreleasingUnsafeMutablePointer`. – Rob Napier Jan 27 '17 at 13:59
  • Oh I see! Rob, you truly are a wonderful human being. May your toast forever land butter side up. Thanks! – fractious Jan 27 '17 at 16:03