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)?)