ios – URLSession backgroundSession not invoking urlSessionDidFinishEvents or handleEventsForBackgroundURLSession

ios – URLSession backgroundSession not invoking urlSessionDidFinishEvents or handleEventsForBackgroundURLSession


I’m scuffling with backgroundSession.Set capabilities to background fetch and processing, set backgroundSession with URLSessionConfiguration.background.backgroundSession?.uploadTask is nice, and

func urlSession(_ session: URLSession, process: URLSessionTask, didCompleteWithError error: Error?)` is invoked in the long run with out error.

When making an attempt to check background (placing the app within the background), urlSessionDidFinishEvents(forBackgroundURLSession shouldn’t be triggered nor handleEventsForBackgroundURLSession in appDelegate.
(checked additionally getting back from background to foreground)

What am I lacking?

class AppDelegate: NSObject, UIApplicationDelegate {
    let gcmMessageIDKey = "gcm.message_id"
    var backgroundCompletionHandler: (() -> Void)?
    var userService: UserService?

    func software(_ software: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        FirebaseApp.configure()
        FirebaseConfiguration.shared.setLoggerLevel(FirebaseLoggerLevel.debug)

        Messaging.messaging().delegate = self
        UNUserNotificationCenter.present().delegate = self
        
        AnalyticsManager.startMonitoringNetwork()

        return true
    }
    
    func software(_ software: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                       fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
          
          if let messageID = userInfo[gcmMessageIDKey] {
              print("Message ID software: (messageID)")
          }
          
          print(userInfo)
          
          completionHandler(UIBackgroundFetchResult.newData)
      }
    
    func software(_ app: UIApplication,
                     open url: URL,
                     choices: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
        return GIDSignIn.sharedInstance.deal with(url)
    }
    
    func software(_ software: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
        print("☠️ handleEventsForBackgroundURLSession")
        userService?.apiClient.backgroundSessionCompletionHandler = completionHandler
    }
}
@major
struct WhisperApp: App {
    var userService = UserService()
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    @StateObject var appCoordinator = AppCoordinator()
    @ObservedObject var loginViewModel = LogInViewModel()

    var physique: some Scene {
        WindowGroup {
            if appCoordinator.isLoggedIn {
                CustomTabBarView(userService: userService)
                    .environmentObject(appCoordinator)
                
            } else {
                LoginView(viewModel: loginViewModel)
                    .environmentObject(appCoordinator)
                    .onAppear {
                       delegate.userService = userService
              }
            }
            
        }
        
    }
}
ultimate class URLSessionAPIClient: NSObject, ServiceProtocol {
    personal let progress: PassthroughSubject<(id: Int, progress: Double), By no means> = .init()
    personal var session: URLSession
    personal var baseUrl: String
    personal var decoder: JSONDecoder
    personal let tokenExpiredSubject = PassthroughSubject()
    personal var cancellables = Set()
    personal var retryCount = 0
    personal var fireBaseManager: FireBaseManager
    personal var backgroundSession: URLSession?
    personal var tempFileURL: URL?
    personal var topic = PassthroughSubject()
    
    var tokenExpiredPublisher: AnyPublisher {
        tokenExpiredSubject.eraseToAnyPublisher()
    }
    
    var backgroundSessionCompletionHandler: (() -> Void)?
    
    
    init(
        baseUrl: String = GlobalConstants.baseURL,
        sessionConfiguration: URLSessionConfiguration = .default,
        decoder: JSONDecoder = JSONDecoder(),
        firebaseManager: FireBaseManager = FireBaseManager()
    ) {
        
        self.baseUrl = baseUrl
        self.session = URLSession(configuration: sessionConfiguration)
        self.decoder = decoder
        self.fireBaseManager = firebaseManager
        tremendous.init()
        
        let backgroundConfig = URLSessionConfiguration.background(withIdentifier: "backgroundUpload")
        
        self.backgroundSession = URLSession(configuration: backgroundConfig, delegate: self, delegateQueue: nil)
        
    }
    
    personal func saveRequestBodyToTemporaryFile(_ physique: Information) throws -> URL {
        let tempDirectory = FileManager.default.temporaryDirectory
        let tempFileURL = tempDirectory.appendingPathComponent(UUID().uuidString)
        attempt physique.write(to: tempFileURL)
        
        self.tempFileURL = tempFileURL
        return tempFileURL
    }
    
    personal func uploadLargeFile2(request: URLRequest, fileURL: URL) -> AnyPublisher {
        print("☠️ inside uploadLargeFile with native URLSession")
        
        let topic = PassthroughSubject()
        
        
        guard let httpBody = request.httpBody else {
            return Fail(error: NSError(area: "URLError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]))
                .eraseToAnyPublisher()
        }
        
        guard let temporaryFileURL = attempt? saveRequestBodyToTemporaryFile(httpBody) else {
            return Fail(error: NSError(area: "URLError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"]))
                .eraseToAnyPublisher()
        }
        var requestTest = request
        requestTest.httpBody = nil
        
        let process = backgroundSession?.uploadTask(with: requestTest, fromFile: temporaryFileURL)
        
        
        process?.resume()
        
        return topic
            .decode(kind: R.self, decoder: decoder)
            .eraseToAnyPublisher()
    }
    
    personal func sendDataTask(request: URLRequest) -> AnyPublisher {
        return session.dataTaskPublisher(for: request)
            .subscribe(on: DispatchQueue.world(qos: .background))
            .tryMap { [weak self] knowledge, response -> R in
                guard let self, let httpResponse = response as? HTTPURLResponse else {
                    throw APIError.invalidResponse
                }
                
                if httpResponse.statusCode == 401 {
                    throw APIError.expiredToken
                }
                
                guard (200...299).incorporates(httpResponse.statusCode) else {
                    throw APIError.invalidResponse
                }
                
                retryCount = 0
                return attempt self.decoder.decode(R.self, from: knowledge)
            }
            .tryCatch { [weak self] error -> AnyPublisher in
                guard let self else { throw error }
                
                if error as? APIError == .expiredToken, retryCount < 3 {
                    retryCount += 1
                    return self.refreshTokenAndRetryLarge(request: request)
                }
                
                throw error
            }
            .eraseToAnyPublisher()
    }
    
    func sendRequest(_ request: T) -> AnyPublisher {
        guard let urlRequest = urlRequest(from: request, baseURL: baseUrl) else {
            return Fail(error: APIError.invalidData)
                .eraseToAnyPublisher()
        }
        
        
        let isLargeFileR = request.urlPath.flatMap { isLargeFile(url: $0) } ?? false
        
        
        if isLargeFileR {
            // Use the trail because the file URL
            guard URL(string: request.path) != nil else {
                return Fail(error: APIError.invalidData).eraseToAnyPublisher()
            }
            guard let file = request.urlPath else { return Fail(error: APIError.invalidData)
                .eraseToAnyPublisher()}
            print("☠️ uploadLargeFile")
            
            return uploadLargeFile2(request: urlRequest, fileURL: file)
            // return uploadLargeFile(request: urlRequest, fileURL: file)
        } else {
            print("☠️ sendDataTask")
            
            return sendDataTask(request: urlRequest)
        }
    }
    
    personal func refreshTokenAndRetryLarge(request: URLRequest) -> AnyPublisher {
        print("☠️ refreshTokenAndRetryLarge refreshTokenAndRetry")
        
        // Try and refresh the token
        return refreshToken()
            .flatMap { [weak self] _ -> AnyPublisher in
                guard let self else {
                    return Fail(error: APIError.invalidData).eraseToAnyPublisher()
                }
                
                // As soon as token is refreshed, retry the unique request
                return self.sendDataTask(request: request)
            }
            .eraseToAnyPublisher()
    }
    
    personal func refreshTokenAndRetry(request: T) -> AnyPublisher {
        print("☠️ refreshTokenAndRetry")
        
        return refreshToken()
            .flatMap { [weak self] _ -> AnyPublisher in
                guard let self else {
                    return Fail(error: APIError.invalidData).eraseToAnyPublisher()
                }
                
                return self.sendRequest(request)
            }
            .eraseToAnyPublisher()
    }
    
    personal func refreshToken() -> AnyPublisher {
        return Future { promise in
            let currentUser = Auth.auth().currentUser
            print("☠️ refreshToken")
            
            currentUser?.getIDTokenForcingRefresh(true) { idToken, error in
                if error != nil {
                    promise(.failure(APIError.invalidData))
                } else if let token = idToken {
                    CredentialManager.shared.setValue(token, kind: .token)
                    promise(.success(()))
                } else {
                    promise(.failure(APIError.invalidData))
                }
            }
        }
        .eraseToAnyPublisher()
    }
    
    
}
personal extension URLSessionAPIClient {
    func urlRequest(from request: any HTTPRequest, baseURL: String) -> URLRequest? {
        guard let url = URL(string: baseURL + request.path) else {
            return nil
        }
        var urlRequest = URLRequest(url: url)
        urlRequest.timeoutInterval = GlobalConstants.requestTimeoutInterval
        urlRequest.httpMethod = request.methodology.rawValue
        request.headers?.forEach { urlRequest.addValue($0.worth, forHTTPHeaderField: $0.key) }
        
        if request.authRequirement == .requiresAuth {
            urlRequest.addValue(CredentialManager.shared.getValue(.token), forHTTPHeaderField: GlobalConstants.googleTokenHeaderKey)
            urlRequest.addValue(CredentialManager.shared.getValue(.machine), forHTTPHeaderField: GlobalConstants.deviceTokenHeaderKey)
            urlRequest.addValue(CredentialManager.shared.getValue(.e mail), forHTTPHeaderField: GlobalConstants.userEmailKey)
        }
        
        urlRequest.httpBody = request.physique
        if let httpBody = urlRequest.httpBody {
            urlRequest.setValue("(String(describing: httpBody.depend))", forHTTPHeaderField: "Content material-Size")
        }
        
        return urlRequest
    }
}
extension URLSessionAPIClient {
    func isLargeFile(url: URL) -> Bool {
        // Examine if the request incorporates a sound file URL within the path
        
        // In any other case, examine the file dimension for big information
        do {
            let fileSize = attempt FileManager.default.attributesOfItem(atPath: url.path)[.size] as? NSNumber
            if let dimension = fileSize, dimension.intValue > 200 * 1024 * 1024 { // 200MB
                return true
            }
        } catch {
            print("Error checking file dimension: (error)")
        }
        
        return false
    }
}
extension URLSessionAPIClient: URLSessionDelegate, URLSessionTaskDelegate, 
     URLSessionDataDelegate {
    // Deal with the completion of the duty
    func urlSession(_ session: URLSession, process: URLSessionTask, didCompleteWithError 
        error: Error?) {
        if let error = error {
            print("☠️ Add failed with error: (error.localizedDescription)")
            // Deal with the error right here (e.g., notify the person, retry the add, and so on.)
        } else {
            print("☠️ Add accomplished efficiently.")
            // You'll be able to deal with the profitable add right here
        }
        
        if let tempFileURL = self.tempFileURL {
            attempt? FileManager.default.removeItem(at: tempFileURL)
        }
        
        // Name the background session completion handler to complete the duty
        if let completionHandler = backgroundSessionCompletionHandler {
            completionHandler()
        }
    }
    
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
        print("☠️ Background duties completed")
        self.backgroundSessionCompletionHandler?()
        self.backgroundSessionCompletionHandler = nil
        
    }
    
    func urlSession(_ session: URLSession, uploadTask: URLSessionUploadTask, didSendBodyData bytesSent: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
        let progress = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
        print("☠️ Add Progress: (progress * 100)%")
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *