From b597244be41a7923741bbd5eccbe54bc104cb55c Mon Sep 17 00:00:00 2001 From: Amir Abbas Date: Sat, 18 Aug 2018 21:31:37 +0430 Subject: [PATCH] Fix #112 (crash on progress report) --- Sources/HTTPFileProvider.swift | 11 ++--- Sources/OneDriveHelper.swift | 2 +- Sources/RemoteSession.swift | 77 +++++++++++++++++++--------------- 3 files changed, 48 insertions(+), 42 deletions(-) diff --git a/Sources/HTTPFileProvider.swift b/Sources/HTTPFileProvider.swift index 8950014..31086ff 100644 --- a/Sources/HTTPFileProvider.swift +++ b/Sources/HTTPFileProvider.swift @@ -525,7 +525,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper self?.delegateNotify(operation, error: responseError ?? error) } task.taskDescription = operation.json - task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress) + sessionDelegate?.observerProgress(of: task, using: progress, kind: .upload) progress.cancellationHandler = { [weak task] in task?.cancel() } @@ -627,8 +627,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper } task.taskDescription = operation.json - task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress) - task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress) + sessionDelegate?.observerProgress(of: task, using: progress, kind: .download) progress.cancellationHandler = { [weak task] in task?.cancel() } @@ -667,8 +666,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper } task.taskDescription = operation.json - task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress) - task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress) + sessionDelegate?.observerProgress(of: task, using: progress, kind: .download) progress.cancellationHandler = { [weak task] in task?.cancel() } @@ -708,8 +706,7 @@ open class HTTPFileProvider: NSObject, FileProviderBasicRemote, FileProviderOper completionHandler(tempURL, nil) } task.taskDescription = operation.json - task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: &progress) - task.addObserver(sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: &progress) + sessionDelegate?.observerProgress(of: task, using: progress, kind: .download) progress.cancellationHandler = { [weak task] in task?.cancel() } diff --git a/Sources/OneDriveHelper.swift b/Sources/OneDriveHelper.swift index f12f273..3b33d0e 100644 --- a/Sources/OneDriveHelper.swift +++ b/Sources/OneDriveHelper.swift @@ -258,7 +258,7 @@ internal extension OneDriveFileProvider { dictionary["uploadedBytes"] = NSNumber(value: uploadedSoFar) dictionary["totalBytes"] = NSNumber(value: data.count) task.taskDescription = String(jsonDictionary: dictionary) - task.addObserver(self.sessionDelegate!, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: &progress) + sessionDelegate?.observerProgress(of: task, using: progress, kind: .upload) progress.cancellationHandler = { [weak task, weak self] in task?.cancel() var deleteRequest = URLRequest(url: url) diff --git a/Sources/RemoteSession.swift b/Sources/RemoteSession.swift index 6ee0047..785e8d5 100755 --- a/Sources/RemoteSession.swift +++ b/Sources/RemoteSession.swift @@ -61,10 +61,49 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession self.credential = fileProvider.credential } + public enum ObserveKind { + case upload + case download + } + + private let observeProgressesLock = NSLock() + private var observeProgresses = [(task: URLSessionTask, progress: Progress, kind: ObserveKind)]() + + public func observerProgress(of task: URLSessionTask, using: Progress, kind: ObserveKind) { + switch kind { + case .upload: + task.addObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent), options: .new, context: nil) + case .download: + task.addObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived), options: .new, context: nil) + task.addObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive), options: .new, context: nil) + } + observeProgressesLock.lock() + observeProgresses.append((task, using, kind)) + observeProgressesLock.unlock() + } + + func removeObservers(for task: URLSessionTask) { + observeProgressesLock.lock() + observeProgresses = observeProgresses.filter { (item) -> Bool in + if item.task == task { + switch item.kind { + case .upload: + task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent)) + case .download: + task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived)) + task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive)) + } + return false + } else { + return true + } + } + observeProgressesLock.unlock() + } + public override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { - guard let context = context, let keyPath = keyPath else { return } - let progress = Unmanaged.fromOpaque(context).takeRetainedValue() - guard progress.responds(to: #selector(Progress.becomeCurrent(withPendingUnitCount:))) else { return } + guard let context = context, let keyPath = keyPath, keyPath.contains("countOfBytes") else { return } + let progress = context.assumingMemoryBound(to: Progress.self).pointee guard let newVal = change?[.newKey] as? Int64 else { return } switch keyPath { @@ -108,13 +147,7 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession // codebeat:disable[ARITY] public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { - if task is URLSessionUploadTask { - task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesSent)) - //task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToSend)) - } else if task is URLSessionDownloadTask { - task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived)) - task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive)) - } + self.removeObservers(for: task) _ = dataCompletionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: task.taskIdentifier) if !(error == nil && task is URLSessionDownloadTask) { @@ -122,30 +155,6 @@ final public class SessionDelegate: NSObject, URLSessionDataDelegate, URLSession completionHandler?(error) _ = completionHandlersForTasks[session.sessionDescription!]?.removeValue(forKey: task.taskIdentifier) } - - guard let json = task.taskDescription?.deserializeJSON(), - let op = FileOperationType(json: json) else { - return - } - - switch op { - case .fetch: - if task is URLSessionDataTask { - task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesReceived)) - task.removeObserver(self, forKeyPath: #keyPath(URLSessionTask.countOfBytesExpectedToReceive)) - } - default: - break - } - - if !(task is URLSessionDownloadTask), case FileOperationType.fetch = op { - return - } - if #available(iOS 9.0, macOS 10.11, *) { - if task is URLSessionStreamTask { - return - } - } } public func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {