Post

Replies

Boosts

Views

Activity

UIApplication.State of a sceneless app launched to the background by the system due to a content-available push notification
When iOS receives a content-available: 1 push notification, it might decide to launch the target app in the background. In the case of a sceneless, UIApplicationDelegate -based app, what should be the value of UIApplication.shared.applicationState immediatelly after the app launches? Apple's documentation seems to be conflicting on this: A. applicationState documentation states: The app is inactive at launch, and then is generally in either an active or background state. B. Managing your app’s life cycle states: After launch, the system puts the app in the inactive or background state, depending on whether the UI is about to appear onscreen. So, according on A, the app is launched to inactive state and then transitions to background state. According on B, the app can be launched directly to background state (which is also confirmed by the state transition graph in that document). From my local debugging in Xcode, when system launches the app to background, B seems to be true and A false. Is this just an inaccuracy in documentation, or are there e.g. differences between iOS versions in how these state transitions are expected to work?
0
0
519
Feb ’23
Using URLCache subclasses with URLSession
I have an app which uses URLSession-based networking and URLCache for storing network requests on disk. I noticed that when the storage size of URLCache reaches the diskCapacity, the eviction strategy seems to be to remove all entries, which is a problem in my use case. So I decided to write an URLCache subclass which would provide a custom storage for cached responses and which would implement LRU eviction strategy with better control. As URLCache's documentation states, subclassing for this purpose should be supported: The URLCache class is meant to be used as-is, but you can subclass it when you have specific needs. For example, you might want to screen which responses are cached, or reimplement the storage mechanism for security or other reasons. However, I ran into problems with trying to use this new URLCache subclass with URLSession networking. I have a test resource which I fetch using HTTP GET. The response headers contain: Cache-Control: public, max-age=30 Etag: <some-value> When using the standard, non-subclassed URLCache, the first request loads the data from network as expected (verified with HTTP proxy). The second request doesn't go to the network, if done within first 30 seconds, as expected. Subsequent requests after 30 seconds cause conditional GETs with Etag, as expected. When using a URLCache subclass, all requests load the data from network - max-age doesn't seem to matter, and no conditional GETs are made. It seems that the URLCache does something special to the CachedURLResponse instances after they're loaded from its internal storage, and this something affects how URLSession handles the HTTP caching logic. What am I missing here? I've written a very minimal URLCache implementation to demonstrate this problem. This class stores and loads CachedURLResponse instances using NSKeyedArchiver / NSKeyedUnarchiver, and it supports only zero or one response. Note that there are no calls to super - this is by design, since I want to use my own storage. Here's the implementation: class CustomURLCache: URLCache { let cachedResponseFileURL = URL(filePath: NSTemporaryDirectory().appending("entry.data")) // MARK: Internal storage func read() -> CachedURLResponse? { guard let data = try? Data(contentsOf: cachedResponseFileURL) else { return nil } return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! CachedURLResponse } func store(_ cachedResponse: CachedURLResponse) { try! (try! NSKeyedArchiver.archivedData(withRootObject: cachedResponse, requiringSecureCoding: false)).write(to: cachedResponseFileURL) } // MARK: URLCache Overrides override func cachedResponse(for request: URLRequest) -> CachedURLResponse? { read() } override func getCachedResponse(for dataTask: URLSessionDataTask, completionHandler: @escaping (CachedURLResponse?) -> Void) { guard let response = read() else { completionHandler(nil) return } completionHandler(response) } override func storeCachedResponse(_ cachedResponse: CachedURLResponse, for request: URLRequest) { store(cachedResponse) } override func storeCachedResponse(_ cachedResponse: CachedURLResponse, for dataTask: URLSessionDataTask) { store(cachedResponse) } } My test case: func test() { let useEvictingCache = false let config = URLSessionConfiguration.default if useEvictingCache { config.urlCache = CustomURLCache() } else { config.urlCache = URLCache(memoryCapacity: 0, diskCapacity: 1024 * 1024 * 100) } self.urlSession = URLSession(configuration: config) let url = URL(string: "https://example.com/my-test-resource")! self.urlSession?.dataTask(with: URLRequest(url: url), completionHandler: { data, response, error in if let data { print("GOT DATA with \(data.count) bytes") } else if let error { print("GOT ERROR \(error)") } }).resume() }
4
0
1.7k
Feb ’23