Skip to content
This repository has been archived by the owner on Feb 27, 2024. It is now read-only.

Commit

Permalink
Change protocols used to allow mocking the URLSession to generics
Browse files Browse the repository at this point in the history
  • Loading branch information
syoung-smallwisdom committed Jul 31, 2023
1 parent c8dee2e commit 1019974
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
}()

/// The primary background URLSession.
var primaryBackgroundSession: BridgeURLSession? = nil
var primaryBackgroundSession: (any BridgeURLSession)? = nil

/// A map of pending background URLSession completion handlers that have been passed in from the app delegate and not yet called.
var backgroundSessionCompletionHandlers = [String : () -> Void]()

/// A map of the restored background sessions.
var restoredSessions = [String : BridgeURLSession]()
var restoredSessions = [String : any BridgeURLSession]()

func isRunningInAppExtension() -> Bool {
// "An app extension target’s Info.plist file identifies the extension point and may specify some details
Expand Down Expand Up @@ -91,7 +91,7 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR

/// Access (and if necessary, create) the singleton background URLSession used by the singleton BackgroundNetworkManager.
/// Make sure it only gets created once, regardless of threading.
func backgroundSession() -> BridgeURLSession {
func backgroundSession() -> any BridgeURLSession {
if primaryBackgroundSession == nil {
// If it doesn't yet exist, queue up a block of code to create it.
let createSessionOperation = BlockOperation {
Expand Down Expand Up @@ -120,7 +120,7 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
}

// internal-use-only method, must always be called on the session delegate queue
fileprivate func createBackgroundSession(with sessionIdentifier: String) -> BridgeURLSession {
fileprivate func createBackgroundSession(with sessionIdentifier: String) -> any BridgeURLSession {
let config = URLSessionConfiguration.background(withIdentifier: sessionIdentifier)
if let appGroupIdentifier = IOSBridgeConfig().appGroupIdentifier, !appGroupIdentifier.isEmpty {
config.sharedContainerIdentifier = appGroupIdentifier
Expand Down Expand Up @@ -221,7 +221,7 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
}

private func downloadFile(with request: URLRequest, taskDescription: String) -> BridgeURLSessionDownloadTask {
let task = self.backgroundSession().downloadBridgeTask(with: request)
let task = self.backgroundSession().downloadTask(with: request)
task.taskDescription = taskDescription
task.resume()
return task
Expand All @@ -237,7 +237,7 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
var request = URLRequest(url: url)
request.allHTTPHeaderFields = httpHeaders
request.httpMethod = HTTPMethod.PUT.rawValue
let task = backgroundSession().uploadBridgeTask(with: request, fromFile: fileURL)
let task = backgroundSession().uploadTask(with: request, fromFile: fileURL)
task.taskDescription = taskDescription
task.resume()
return task
Expand All @@ -259,26 +259,26 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR

// MARK: URLSessionDownloadDelegate
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
self.urlSession(session as BridgeURLSession, downloadTask: downloadTask, didFinishDownloadingTo: location)
self.bridgeUrlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
}
func urlSession(_ session: BridgeURLSession, downloadTask: BridgeURLSessionDownloadTask, didFinishDownloadingTo location: URL) {
self.backgroundTransferDelegate?.urlSession?(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
func bridgeUrlSession(_ session: any BridgeURLSession, downloadTask: BridgeURLSessionDownloadTask, didFinishDownloadingTo location: URL) {
self.backgroundTransferDelegate?.bridgeUrlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
}

// MARK: URLSessionTaskDelegate

func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
self.urlSession(session as BridgeURLSession, task: task, didCompleteWithError: error)
self.bridgeUrlSession(session, task: task, didCompleteWithError: error)
}
func urlSession(_ session: BridgeURLSession, task: BridgeURLSessionTask, didCompleteWithError error: Error?) {
self.backgroundTransferDelegate?.urlSession(session, task: task, didCompleteWithError: error)
func bridgeUrlSession(_ session: any BridgeURLSession, task: BridgeURLSessionTask, didCompleteWithError error: Error?) {
self.backgroundTransferDelegate?.bridgeUrlSession(session, task: task, didCompleteWithError: error)
}

// MARK: URLSessionDelegate
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
self.urlSessionDidFinishEvents(forBackgroundURLSession: session as BridgeURLSession)
self.bridgeUrlSessionDidFinishEvents(forBackgroundURLSession: session)
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: BridgeURLSession) {
func bridgeUrlSessionDidFinishEvents(forBackgroundURLSession session: any BridgeURLSession) {
guard let identifier = session.identifier else { return }
if let completion = self.backgroundSessionCompletionHandlers[identifier] {
OperationQueue.main.addOperation {
Expand All @@ -292,13 +292,12 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
self.restoredSessions[identifier] = nil
}
}
self.backgroundTransferDelegate?.urlSessionDidFinishEvents?(forBackgroundURLSession: session)
}

func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
self.urlSession(session as BridgeURLSession, didBecomeInvalidWithError: error)
self.bridgeUrlSession(session, didBecomeInvalidWithError: error)
}
func urlSession(_ session: BridgeURLSession, didBecomeInvalidWithError error: Error?) {
func bridgeUrlSession(_ session: any BridgeURLSession, didBecomeInvalidWithError error: Error?) {
if error != nil,
let identifier = session.identifier {
// if it became invalid unintentionally (i.e. due to an error), re-create the session:
Expand All @@ -316,24 +315,26 @@ class BackgroundNetworkManager: NSObject, URLSessionBackgroundDelegate, BridgeUR
}
}
}
self.backgroundTransferDelegate?.urlSession?(session, didBecomeInvalidWithError: error)
self.backgroundTransferDelegate?.bridgeUrlSession(session, didBecomeInvalidWithError: error)
}
}

// MARK: URLSession Mocking

@objc
protocol BridgeURLSession : NSObjectProtocol {
associatedtype UploadTask : BridgeURLSessionUploadTask
associatedtype DownloadTask : BridgeURLSessionDownloadTask
associatedtype SessionTask : BridgeURLSessionTask

var identifier: String? { get }
var bridgeDelegate: BridgeURLSessionDelegate? { get }
var delegateQueue: OperationQueue { get }
func uploadBridgeTask(with request: URLRequest, fromFile fileURL: URL) -> BridgeURLSessionUploadTask
func downloadBridgeTask(with request: URLRequest) -> BridgeURLSessionDownloadTask
func downloadBridgeTask(withResumeData data: Data) -> BridgeURLSessionDownloadTask
func getAllBridgeTasks(completionHandler: @escaping ([BridgeURLSessionTask]) -> Void)
func uploadTask(with request: URLRequest, fromFile fileURL: URL) -> UploadTask
func downloadTask(with request: URLRequest) -> DownloadTask
func downloadTask(withResumeData data: Data) -> DownloadTask
func getAllTasks(completionHandler: @escaping @Sendable ([SessionTask]) -> Void)
}

@objc
protocol BridgeURLSessionTask : NSObjectProtocol {
var taskDescription: String? { get set }
var originalRequest: URLRequest? { get }
Expand All @@ -342,23 +343,32 @@ protocol BridgeURLSessionTask : NSObjectProtocol {
func cancel()
}

@objc
protocol BridgeURLSessionDownloadTask : BridgeURLSessionTask {
}

@objc
protocol BridgeURLSessionUploadTask : BridgeURLSessionTask {
}

@objc
protocol BridgeURLSessionDelegate : NSObjectProtocol {
func urlSession(_ session: BridgeURLSession, task: BridgeURLSessionTask, didCompleteWithError error: Error?)
@objc optional func urlSession(_ session: BridgeURLSession, downloadTask: BridgeURLSessionDownloadTask, didFinishDownloadingTo location: URL)
@objc optional func urlSession(_ session: BridgeURLSession, didBecomeInvalidWithError error: Error?)
@objc optional func urlSessionDidFinishEvents(forBackgroundURLSession session: BridgeURLSession)
protocol BridgeURLSessionUploadDelegate : NSObjectProtocol {
func bridgeUrlSession(_ session: any BridgeURLSession, task: BridgeURLSessionTask, didCompleteWithError error: Error?)
func bridgeUrlSession(_ session: any BridgeURLSession, didBecomeInvalidWithError error: Error?)
}

protocol BridgeURLSessionDelegate : BridgeURLSessionUploadDelegate {
func bridgeUrlSession(_ session: any BridgeURLSession, downloadTask: BridgeURLSessionDownloadTask, didFinishDownloadingTo location: URL)
}

protocol BridgeURLSessionBackgroundDelegate : BridgeURLSessionDelegate {
func bridgeUrlSessionDidFinishEvents(forBackgroundURLSession session: any BridgeURLSession)
}

extension URLSession : BridgeURLSession {
typealias UploadTask = URLSessionUploadTask

typealias DownloadTask = URLSessionDownloadTask

typealias SessionTask = URLSessionTask


var identifier: String? {
configuration.identifier
Expand All @@ -368,23 +378,7 @@ extension URLSession : BridgeURLSession {
delegate as? BridgeURLSessionDelegate
}

func uploadBridgeTask(with request: URLRequest, fromFile fileURL: URL) -> BridgeURLSessionUploadTask {
uploadTask(with: request, fromFile: fileURL)
}

func downloadBridgeTask(with request: URLRequest) -> BridgeURLSessionDownloadTask {
downloadTask(with: request)
}

func downloadBridgeTask(withResumeData data: Data) -> BridgeURLSessionDownloadTask {
downloadTask(withResumeData: data)
}

func getAllBridgeTasks(completionHandler: @escaping ([BridgeURLSessionTask]) -> Void) {
getAllTasks {
completionHandler($0 as [BridgeURLSessionTask])
}
}
}

extension URLSessionTask : BridgeURLSessionTask {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -764,7 +764,7 @@ class BridgeFileUploadManager: SandboxFileManager, BridgeURLSessionDelegate {
}

/// Download delegate method.
func urlSession(_ session: BridgeURLSession, downloadTask: BridgeURLSessionDownloadTask, didFinishDownloadingTo location: URL) {
func bridgeUrlSession(_ session: any BridgeURLSession, downloadTask: BridgeURLSessionDownloadTask, didFinishDownloadingTo location: URL) {
// get the sandbox-relative path to the temp copy of the participant file
guard let invariantFilePath = downloadTask.taskDescription else {
let message = "Finished a download task with no taskDescription set"
Expand Down Expand Up @@ -924,7 +924,7 @@ class BridgeFileUploadManager: SandboxFileManager, BridgeURLSessionDelegate {
// Do not attempt to check and retry orphaned uploads if the session token isn't set up.
guard self.appManager?.sessionToken != nil else { return }

self.netManager.backgroundSession().getAllBridgeTasks { tasks in
self.netManager.backgroundSession().getAllTasks { tasks in
var tasks = tasks
let defaults = self.userDefaults

Expand Down Expand Up @@ -1211,7 +1211,7 @@ class BridgeFileUploadManager: SandboxFileManager, BridgeURLSessionDelegate {
}

/// Task delegate method.
private func _urlSession(_ session: BridgeURLSession, task: BridgeURLSessionTask, didCompleteWithError error: Error?) {
private func _urlSession(_ session: any BridgeURLSession, task: BridgeURLSessionTask, didCompleteWithError error: Error?) {

// get the sandbox-relative path to the temp copy of the participant file
guard let invariantFilePath = task.taskDescription else {
Expand Down Expand Up @@ -1307,7 +1307,7 @@ class BridgeFileUploadManager: SandboxFileManager, BridgeURLSessionDelegate {
}

/// Session delegate method.
func urlSession(_ session: BridgeURLSession, didBecomeInvalidWithError error: Error?) {
func bridgeUrlSession(_ session: any BridgeURLSession, didBecomeInvalidWithError error: Error?) {
guard error != nil else {
// If the URLSession was deliberately invalidated (i.e., error is nil) then we assume
// the intention is to cancel and forget all incomplete uploads, including retries.
Expand Down Expand Up @@ -1410,7 +1410,7 @@ class BridgeFileUploadManager: SandboxFileManager, BridgeURLSessionDelegate {
/// This sets the maximum number of times we will retry a request before giving up.
let maxRetries = 5

func urlSession(_ session: BridgeURLSession, task: BridgeURLSessionTask, didCompleteWithError error: Error?) {
func bridgeUrlSession(_ session: any BridgeURLSession, task: BridgeURLSessionTask, didCompleteWithError error: Error?) {
if let nsError = error as NSError?,
let downloadTask = task as? BridgeURLSessionDownloadTask,
let resumeData = nsError.userInfo[NSURLSessionDownloadTaskResumeData] as? Data {
Expand All @@ -1436,8 +1436,8 @@ class BridgeFileUploadManager: SandboxFileManager, BridgeURLSessionDelegate {
}
}

fileprivate func retryFailedDownload(_ task: BridgeURLSessionDownloadTask, for session: BridgeURLSession, resumeData: Data) {
let resumeTask = session.downloadBridgeTask(withResumeData: resumeData)
fileprivate func retryFailedDownload(_ task: BridgeURLSessionDownloadTask, for session: any BridgeURLSession, resumeData: Data) {
let resumeTask = session.downloadTask(withResumeData: resumeData)
resumeTask.taskDescription = task.taskDescription
resumeTask.resume()
}
Expand Down Expand Up @@ -1473,7 +1473,7 @@ class BridgeFileUploadManager: SandboxFileManager, BridgeURLSessionDelegate {
request.setValue("\(retry)", forHTTPHeaderField: retryCountHeader)
request.setValue(sessionToken, forHTTPHeaderField: "Bridge-Session")

let newTask = netManager.backgroundSession().downloadBridgeTask(with: request)
let newTask = netManager.backgroundSession().downloadTask(with: request)
newTask.taskDescription = task.taskDescription
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + pow(2.0, Double(retry))) {
newTask.resume()
Expand All @@ -1482,7 +1482,7 @@ class BridgeFileUploadManager: SandboxFileManager, BridgeURLSessionDelegate {
return true
}

func handleError(_ error: NSError, session: BridgeURLSession, task: BridgeURLSessionDownloadTask) -> Bool {
func handleError(_ error: NSError, session: any BridgeURLSession, task: BridgeURLSessionDownloadTask) -> Bool {
if isTemporaryError(errorCode: error.code) {
// Retry, and let the caller know we're retrying.
return retry(task: task)
Expand All @@ -1499,7 +1499,7 @@ class BridgeFileUploadManager: SandboxFileManager, BridgeURLSessionDelegate {
self.appManager.notifyUIOfBridgeError(412, description: "User not consented")
}

func handleHTTPErrorResponse(_ response: HTTPURLResponse, session: BridgeURLSession, task: BridgeURLSessionDownloadTask) -> Bool {
func handleHTTPErrorResponse(_ response: HTTPURLResponse, session: any BridgeURLSession, task: BridgeURLSessionDownloadTask) -> Bool {
switch response.statusCode {
case 401:
self.appManager.reauthenticate { success in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ protocol BridgeFileUploadManagerTestCase : XCTWaiterDelegate {
var mockAppManager: MockBridgeClientAppManager { get }
var testFileId: String { get }

var savedSession: BridgeURLSession? { get set }
var savedSession: (any BridgeURLSession)? { get set }
var savedDelay: TimeInterval? { get set }
var savedAppManager: UploadAppManager? { get set }

Expand Down
Loading

0 comments on commit 1019974

Please sign in to comment.