【问题标题】:Why is my just created pointer throwing a KERN_INVALID_ADDRESS?为什么我刚刚创建的指针会抛出 KERN_INVALID_ADDRESS?
【发布时间】:2017-01-25 14:37:05
【问题描述】:

因此,与recently posted question 类似,我在将 Amazon 的 AWS Obj-C 库与我的 Swift 应用程序集成时遇到了问题。我有一个NSOperation,它使用他们的Transfer Utility library 处理文件上传到S3,其中包括对后台文件传输的支持。最近发布了我们的应用程序后,当应用程序回到前台时,我发现代码中出现了一些崩溃,这些崩溃会重新连接进度处理程序。代码改编自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.
     }];
}

到我的 Swift 版本,它在尝试取消引用 newProgressPointer 时崩溃并出现 EXC_BAD_ACCESS KERN_INVALID_ADDRESS

// 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(或其他非内存管理语言)的实际经验,我有点迷茫。如果有人能提供一些启示,将不胜感激。

编辑:

enumerateToAssignBlocksForUploadTask(…) 的 Swift 方法签名

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

【问题讨论】:

    标签: ios objective-c swift


    【解决方案1】:

    我认为您不需要大多数(或任何)这些指针。看看Swift example code from AWS,看看它是否符合您的要求。

    在 Swift 中以现有方式创建指针并不安全。这可能比您需要的信息多得多(您不应该如此努力),但这就是可能发生的事情(在这种情况下,这种解释感觉并不完全正确,但这是可能发生的事情,所以值得知道):

    • 您创建一个指向本地(堆栈)变量的指针,progressBlock
    • 系统发现progressBlock 不再在范围内的其他任何地方访问。
    • 在允许的情况下,ARC 会销毁 progressBlock
    • 您不安全地访问指向已破坏变量的指针,然后崩溃。

    我说这感觉不对,因为有第二个对闭包的引用应该使闭包保持活动状态,但通常,以这种方式使用构造函数创建指针是非常危险的。

    (这可能会崩溃,因为你不能 print@convention(block) 关闭;我从未尝试过这样做。尝试打印不是一件很正常的事情。)

    无论如何,如果您确实需要做这种事情(我认为您不需要),您需要按照以下方式进行:

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

    但作为一项规则,如果您正在转换 ObjC 代码(不是纯 C,而只是 ObjC),并且发现您必须创建很多 Unsafe 对象,那么您可能走错了路。大多数 ObjC 东西都可以在没有 Unsafe 的情况下连接到 Swift。

    【讨论】:

    • 嗨,Rob,非常感谢您抽出宝贵时间回答我的问题。不幸的是,您引用的 Swift 示例项目不包含用于重新连接处理程序的示例代码。我会看看我是否可以找到导致我现在所处位置的示例。我已经更新了我的问题以包含库提供的 enumerateToAssignBlocksForUploadTask(…) 的方法签名。尽管我想避免使用指针,但您可以看到它将AutoreleasingUnsafeMutablePointer 传递给块,所以我不确定如果不使用它们我该怎么做。
    • 我相信您在这里需要做的就是:oldProgressPointer.memory = self.uploadProgressHandler。没有理由创建自己的AutoreleasingUnsafeMutablePointer
    • 哦,我明白了!罗布,你真的是一个了不起的人。愿你的吐司永远让黄油面朝上。谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-04
    • 2012-02-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多