I'd like my iOS application (at least certain endpoints) to have the following network behavior:
Always use the cache, whenever it's available, no matter the age (draw the UI right away)
If the data is stale, also make a network request (the UI has stale data during this period, but it's still probably pretty close)
If network data returns, update the cache and make any UI updates that are required.
I prefer a behavior like this because I can then set my caching policy very aggressively (long cache times). For data that updates infrequently, this results in rapid UI returns in the common case and a model layer that is kept up to date essentially in the background (from the user's perspective)
I'm reading about NSURLCache, but I don't see a cache policy, or even a combination of two policies that I'm confident in.
Options:
Use ReturnCacheDataDontLoad to always get cache. If failure or old cache use ReloadIgnoringLocalCacheData for the HTTP fetch. (have to check myself? age is inspectable?)
Use ReturnCacheDataDontLoad to always get cache. Then use UseProtocolCachePolicy with the cache time set to very low and ignore the response if it returns from cache (can I tell if it returns from cache? this says not reliably)
Separate the two concerns. Use ReturnCacheDataDontLoad for all user-initiated requests, only firing a network request right away if there is no cache at all. Separately, have a worker that keeps an eye on stored models, updating them in the background whenever they appear old.
Extend NSURLCache
Use something OTS that already does this? (-AFNetworking just uses NSURLSession caching. +EVURLCache forces disk caching but expects the data to be seeded on app install.
Related
Currently what I want to achieve is download files from an array that download only one file at a time and it still performs download even the app goes to the background state.
I'm using Rob code as stated in here but he's using URLSessionConfiguration.default which I want to use URLSessionConfiguration.background(withIdentifier: "uniqueID") instead.
It did work in the first try but after It goes to background everything became chaos. operation starts to download more than one file at a time and not in order anymore.
Is there any solution to this or what should I use instead to achieve what I want. If in android we have service to handle that easily.
The whole idea of wrapping requests in operation is only applicable if the app is active/running. It’s great for things like constraining the degree of concurrency for foreground requests, managing dependencies, etc.
For background session that continues to proceed after the app has been suspended, though, none of that is relevant. You create your request, hand it to the background session to manage, and monitor the delegate methods called for your background session. No operations needed/desired. Remember, these requests will be handled by the background session daemon even if your app is suspended (or if it terminated in the course of its normal lifecycle, though not if you force quit it). So the whole idea of operations, operation queues, etc., just doesn’t make sense if the background URLSession daemon is handling the requests and your app isn’t active.
See https://stackoverflow.com/a/44140059/1271826 for example of background session.
By the way, true background sessions are really useful when download very large resources that might take a very long time. But it introduces all sorts of complexities (e.g., you often want to debug and diagnose when not connected to the Xcode debugger which changes your app lifecycle, so you have to resort to mechanisms like unified messaging; you need to figure out how to restore UI if the app was terminated between the time the requests were initiated and when they finished; etc.).
Because of this complexity, you might want to consider whether this is absolutely needed. Sometimes, if you only need less than 30 seconds to complete some requests, it’s easier to just ask the OS to keep your app running in the background for a little bit after the user leaves the app and just use standard URLSession. For more information, see Extending Your App's Background Execution Time. It’s a much easier solution, bypassing many background URLSession hassles. But it only works if you only need 30 seconds or less. For larger requests that might exceed this small window, a true background URLSession is needed.
Below, you asked:
There are some downside with [downloading multiple files in parallel] as I understanding.
No, it’s always better to allow downloads to progress asynchronously and in parallel. It’s much faster and is more efficient. The only time you want to do requests consecutively, one after another, is where you need the parse the response of one request in order to prepare the next request. But that is not the case here.
The exception here is with the default, foreground URLSession. In that case you have to worry about latter requests timing out waiting for earlier requests. In that scenario you might bump up the timeout interval. Or we might wrap our requests in Operation subclass, allowing us to constrain not only how many concurrent requests we will allow, but not start subsequent requests until earlier ones finish. But even in that case, we don’t usually do it serially, but rather use a maxConcurrentOperationCount of 4 or something like that.
But for background sessions, requests don’t time out just because the background daemon hasn’t gotten around to them yet. Just add your requests to the background URLSession and let the OS handle this for you. You definitely don’t want to download images one at a time, with the background daemon relaunching your app in the background when one download is done so you can initiate the next one. That would be very inefficient (both in terms of the user’s battery as well as speed).
You need to loop inside an array of files and then add to the session to make it download but It will be download asynchronously so it's hard to keeping track also since the files are a lot.
Sure, you can’t do a naive “add to the end of array” if the requests are running in parallel, because you’re not guaranteed the order that they will complete. But it’s not hard to capture these responses as they come in. Just use a dictionary for example, perhaps keyed by the URL of the original request. Then you can easily look up in that dictionary to find the response associated with a particular request URL.
It’s incredibly simple. And we now can perform requests in parallel, which is much faster and more efficient.
You go on to say:
[Downloading in parallel] could lead the battery to be high consumption with a lot of requests at the same time. that's why I tried to make it download each file one at a time.
No, you never need to perform downloads one at a time for the sake of power. If anything, downloading one at a time is slower, and will take more power.
Unrelated, if you’re downloading 800+ files, you might want to allow the user to not perform these requests when the user is in “low data mode”. In iOS 13, for example, you might set allowsExpensiveNetworkAccess and allowsConstrainedNetworkAccess.
Regardless (and especially if you are supporting older iOS versions), you might also want to consider the appropriate settings isDiscretionary and allowsCellularAccess.
Bottom line, you want to make sure that you are respectful of a user’s limited cellular data plan or if they’re on some expensive service (e.g. connecting on an airplane’s expensive data plan or tethered via some local hotspot).
For more information on these considerations, see WWDC 2019 Advances in Networking, Part 1.
I've started using Siesta while running in the background, using Apple's background fetch capabilities. One of the (many) difficult things to work with while running this way is that on some devices, the OS tends to kill my process frequently. I am trying to get my processing to be fast and as battery efficient as possible, so that the OS will choose to run it regularly.
As I understand it, if Siesta has no data in its in-memory cache (which is the case if the app is newly launched), then it makes both a network request and a persistent cache request. I often have perfectly good, non-stale data in the persistent cache in this scenario. Can I get Siesta to pre-load that data into the in-memory cache, before it makes the network request? Then my code uses less battery, it gets run regularly, every thing is great!
As it happens, I just hit this issue myself working on a prepackaged FileCache implementation for Siesta. I think it’s fair to call it a bug.
When you first bring a Resource into memory, Siesta fires off the cache check asynchronously. That’s as it should be — we don’t want expensive data loading to hold up the UI thread — but loadIfNeeded() doesn’t wait for the cache check to complete.
An improved design would have loadIfNeeded() still return a Request if there’s no data yet, but have that request be a chained one that first checks the cache and then checks the network.
In the meantime, a (very ugly) workaround for this is to delay your loadIfNeeded() call:
let resource = service.resource("whatever")
DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
resource.loadIfNeeded()
}
Update: I’ve filed an issue for this.
Is it possible to let user to choose when to update Service Worker?
Why? I want to add economy mode which means that user could choose to save a lot of bandwidth. This could be useful when user's limit is almost full or he/she is using expensive internet abroad.
That's because if Service Worker updates and there are new assets' versions, it will download all of them which could be several MB. If you're 3 days and 50MB away from new month, every MB counts.
Let's say that I can retrieve the setting from localStorage:
const economy = localStorage.getItem(economy) || false
How to let Service Worker know that it should only update itself if economy is true?
I kind of realize that it could be a problem in a long run (outdated versions) but Im planning to annoy the user often if he/she doesn't want to update. I just want to add the option for user to choose.
If you're willing to handle updating/deleting (perhaps just a subset of) cache entries outside of the install and activate events, you have more flexibility as to when they should be triggered. You actually don't have to perform the updates in the service worker at all, if it ends up being easier for you not to. Individual webpages have access to the exact same Cache Storage API instances that the service worker for a given origin uses. You can modify the caches directly from the page in response to whatever action makes the most sense for you, e.g.:
// Ensure we have access to the Cache Storage API.
if ('caches' in window) {
// Swap this out for whatever UI element will trigger the update.
const el = document.querySelector('#update-caches-button');
el.addEventListener('click', () => {
window.caches.open('my-cache').then(cache => {
// Add or delete entries from cache.
});
});
}
You could do something similar, but keep all the logic in the service worker, by using postMessage() from a client page to trigger a service worker's message event, and then update the caches in the message event handler.
There are some advantages to relying on the install/activate events for performing cache management. In particular, you can rely on the service worker staying in a "waiting" state while there are other active clients that rely on the previous cache state, and don't have to worry about throwing away entries that will be needed by those other clients or swapping out the expected version of a resource for a new version while it's still being used. But as a developer, ultimately you're responsible for understanding how your cached resources are being used, and if the assets you're managing in these caches aren't likely to cause problems if there's a version mismatch or if something that was deleted by one tab needs to be retrieved from the network later on by another tab, then you're free to implement that sort of approach.
For what it's worth, we've thought about similar issues when implementing precaching/updates in sw-precache. There's some background at https://github.com/GoogleChrome/sw-precache/issues/145 about trying to make use of standard signals exposed by browsers indicating that the user prefers to conserve data, rather than everyone coming up with their own heuristics.
I've noticed that every time we alter the response cookies through:
HttpContext.Response.Cookies.Add(myCookie)
Header becomes:
Cache-Control: public, no-cache="Set-Cookie"
and Output Cache is invalidated.
It is very annoying and I was wondering if any one noticed similiar issues while output caching.
You could always switch to using a server-side caching model, such as System.Web.Caching.Cache or System.Runtime.Caching.MemoryCache, which would share caching of objects between users while still allowing communication with the browser.
Frankly, this server-side is the first caching model I have used. I only recently started using output caching and I find it very limited by comparison. Its only advantages are that it caches the page on the client side under certain scenarios and that it caches content rather than the data that generates the content (saving some CPU cycles). Its main disadvantage is that you have to disable it under certain conditions, such as during authentication or writing cookies. You never have to disable server-side caching - not even for application pool recycles - because it doesn't hinder communication with the browser.
For the best of both worlds, you could combine both approaches so whatever backend process that you don't want executed multiple times provide cached data when the view is generated. Then you would have client-side caching in most cases, and would rely on the server side caching when updating cookies. It could take more memory to use this approach, but that tradeoff might be worth it in your case.
A bit of backstory: I am working on an web application that requires quite a bit of time to prep / crunch data before giving it to the user to edit / manipulate. The data request task ~ 15 / 20 secs to complete and a couple secs to process. Once there, the user can manipulate vaules on the fly. Any manipulation of values will require the data to be reprocessed completely.
Update: To avoid confusion, I am only making the data call 1 time (the 15 sec hit) and then wanting to keep the results in memory so that I will not have to call it again until the user is 100% done working with it. So, the first pull will take a while, but, using Ajax, I am going to hit the in-memory data to constantly update and keep the response time to around 2 secs or so (I hope).
In order to make this efficient, I am moving the intial data into memory and using Ajax calls back to the server so that I can reduce processing time to handle the recalculation that occurs w/ this user's updates.
Here is my question, with performance in mind, what would be the best way to storing this data, assuming that only 1 user will be working w/ this data at any given moment.
Also, the user could potentially be working in this process for a few hours. When the user is working w/ the data, I will need some kind of failsafe to save the user's current data (either in a db or in a serialized binary file) should their session be interrupted in some way. In other words, I will need a solution that has an appropriate hook to allow me to dump out the memory object's data in the case that the user gets disconnected / distracted for too long.
So far, here are my musings:
Session State - Pros: Locked to one user. Has the Session End event which will meet my failsafe requirements. Cons: Slowest perf of the my current options. The Session End event is sometimes tricky to ensure it fires properly.
Caching - Pros: Good Perf. Has access to dependencies which could be a bonus later down the line but not really useful in current scope. Cons: No easy failsafe step other than a write based on time intervals. Global in scope - will have to ensure that users do not collide w/ each other's work.
Static - Pros: Best Perf. Easies to maintain as I can directly leverage my current class structures. Cons: No easy failsafe step other than a write based on time intervals. Global in scope - will have to ensure that users do not collide w/ each other's work.
Does anyone have any suggestions / comments on what I option I should choose?
Thanks!
Update: Forgot to mention, I am using VB.Net, Asp.Net, and Sql Server 2005 to perform this task.
I'll vote for secret option #4: use the database for this. If you're talking about a 20+ second turnaround time on the data, you are not going to gain anything by trying to do this in-memory, given the limitations of the options you presented. You might as well set this up in the database (give it a table of its own, or even a separate database if the requirements are that large).
I'd go with the caching method of for storing the data across any page loads. You can name the cache you want to store the data in to avoid conflicts.
For tracking user-made changes, I'd go with a more old-school approach: append to a text file each time the user makes a change and then sweep that file at intervals to save changes back to DB. If you name the files based on the user/account or some other session-unique indicator then there's no issue with conflict and the app (or some other support app, which might be a better idea in general) can sweep through all such files and update the DB even if the session is over.
The first part of this can be adjusted to stagger the write out more: save changes to Session, then write that to file at intervals, then sweep the file at larger intervals. you can tune it to performance and choose what level of possible user-change loss will be possible.
Use the Session, but don't rely on it.
Simply, let the user "name" the dataset, and make a point of actively persisting it for the user, either automatically, or through something as simple as a "save" button.
You can not rely on the session simply because it is (typically) tied to the users browser instance. If they accidentally close the browser (click the X button, their PC crashes, etc.), then they lose all of their work. Which would be nasty.
Once the user has that kind of control over the "persistent" state of the data, you can rely on the Session to keep it in memory and leverage that as a cache.
I think you've pretty much just answered your question with the pros/cons. But if you are looking for some peer validation, my vote is for the Session. Although the performance is slower (do you know by how much slower?), your processing is going to take a long time regardless. Do you think the user will know the difference between 15 seconds and 17 seconds? Both are "forever" in web terms, so go with the one that seems easiest to implement.
perhaps a bit off topic. I'd recommend putting those long processing calls in asynchronous (not to be confused with AJAX's asynchronous) pages.
Take a look at this article and ping me back if it doesn't make sense.
http://msdn.microsoft.com/en-us/magazine/cc163725.aspx
I suggest to create a copy of the data in a new database table (let's call it EDIT) as you send the initial results to the user. If performance is an issue, do this in a background thread.
As the user edits the data, update the table (also in a background thread if performance becomes an issue). If you have to use threads, you must make sure that the first thread is finished before you start updating the rows.
This allows a user to walk away, come back, even restart the browser and commit whenever she feels satisfied with the result.
One possible alternative to what the others mentioned, is to store the data on the client.
Assuming the dataset is not too large, and the code that manipulates it can be handled client side. You could store the data as an XML data island or JSON object. This data could then be manipulated/processed and handled all client side with no round trips to the server. If you need to persist this data back to the server the end resulting data could be posted via an AJAX or standard postback.
If this does not work with your requirements I'd go with just storing it on the SQL server as the other comment suggested.