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)%")
}
}