2017-01-25 1 views
2

Итак, в том же духе к этому recently posted question, у меня возникли проблемы с интеграцией библиотеки Amazon AWS Obj-C с моим приложением Swift. У меня есть NSOperation, который обрабатывает загрузку файлов на S3, используя их Transfer Utility library, который включает в себя поддержку передачи фоновых файлов. Недавно выпустив наше приложение, я видел некоторые сбои в коде, который обновляет обработчик прогресса, когда приложение возвращается на передний план. Код адаптирован из their Obj-C example:Почему мой только что созданный указатель бросает KERN_INVALID_ADDRESS?

- (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. 
    }]; 
} 

моей Swift версии, которая завершает работу в аварийном с EXC_BAD_ACCESS KERN_INVALID_ADDRESS при попытке разыменовать 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?) { 

     ... 

    } 

    ... 

} 

Почему указатель memory ссылка недействительна сразу после создавая его?

Будучи новым для разработки iOS и не имея реального опыта работы с Obj-C (или другими языками, не поддерживающими память), я немного потерялся. Если кто-то может пролить некоторый свет, который будет очень признателен.

EDIT:

Swift метод подписи для 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)?) 

ответ

1

Я не думаю, что вы должны больше всего нужно (или любой) из этих указателей. Посмотрите на Swift example code from AWS и посмотрите, не делает ли он того, что вы ищете.

Создание указателей, как у вас есть, не безопасно в Swift. Вероятно, это намного больше информации, чем вам нужно (вам не придется так тяжело работать), но вот что может произойти (это объяснение в этом случае не совсем корректно, но это то, что может случиться, поэтому стоит):

  • Вы создаете указатель на локальную переменную (стек), progressBlock.
  • Система видит, что progressBlock больше не обращается нигде в сфере видимости.
  • Как это разрешено, ARC разрушает progressBlock.
  • Вы не можете получить доступ к указателю на уничтоженную переменную и сбой.

Я говорю, что это не так, потому что есть вторая ссылка на закрытие, которая должна поддерживать закрытие в любом случае, но, как правило, создание указателей с конструктором таким образом очень опасно.

(Возможно, это сбой, потому что вы не можете сделать print a @convention(block) закрытие, я никогда не пытался это сделать. Это не очень нормальная вещь, чтобы попытаться напечатать.)

В любом случае, если вы на самом деле нужно делать такого рода вещи (и я думаю, что вы не), вы должны сделать это вдоль этих линий:

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

Но, как правило, если вы конвертируете код ObjC (не чистый C, а просто ObjC) и обнаружите, что вам нужно создать много объектов Unsafe, вы можете идти по неправильному пути. Большинство объектов ObjC моста для Swift штрафа без Unsafe.

+0

Эй, Роб, большое спасибо за то, что нашли время, чтобы ответить на мой вопрос. К сожалению, проект примера Swift, на который вы ссылались, не включает пример кода для перенастройки обработчиков. Я посмотрю, смогу ли я отследить пример, который привел меня туда, где я сейчас. Я обновил свой вопрос, чтобы включить подпись метода для 'enumerateToAssignBlocksForUploadTask (...)', которую предоставляет библиотека. Насколько я бы хотел избежать использования указателей, так как вы можете видеть, что он передает блок «AutoreleasingUnsafeMutablePointer» в блок, поэтому я не уверен, как я могу обойти это без их использования. – fractious

+0

Я считаю, что все, что вам нужно сделать, это: 'oldProgressPointer.memory = self.uploadProgressHandler'. Нет причин создавать собственный 'AutoreleasingUnsafeMutablePointer'. –

+0

О, я вижу! Роб, ты действительно замечательный человек. Пусть ваш тост навсегда приземлится масло. Благодаря! – fractious

Смежные вопросы