Demon 77: http://www.cnblogs.com/qiqibo/
Why should I have a cache
The main reason the application needs to work offline is to improve the performance that the application shows. Caching your app's content can support offline. We can use two different caches to make the app work offline. The first is * * on-demand caching * *, in which case the application cache responds in the same way that the Web browser works, and the second is * * Pre-cache * *, which caches the entire contents (or the last N records) for offline access.
Web Service applications developed in the 14th chapter utilize on-demand caching techniques to improve perceived performance rather than provide offline access. Offline access is just a result of unintentional insertion of willow. Twitter and Foursquare are good examples. The data obtained from such applications is often outdated quickly. How much interest do you have for a tweet a few days ago or where your friend was last week? In general, a tweet or a sign-in message only makes sense within a few hours, and 24 hours later becomes irrelevant. However, most Twitter clients still cache tweets, and Foursquare's official client opens without a network connection, showing the last state.
You can try it with your favorite Twitter client, Twitter for IPhone, Tweetbot or other apps: Open a friend's profile and browse his timeline. The app gets the timeline and fills the page. When you load the timeline, you see a rotation that represents the circle being loaded. Now go to another page and then come back to open the timeline. You will find that this is instantaneous loading. The app also refreshes the content in the background (based on the last open), but it shows the last cached content instead of the uninteresting spin, which looks much faster. If there is no cache, the user will see the circle rotate every time a page is opened. Whether the network connection is fast or slow, reducing the impact of slow network loading makes it look fast and is the responsibility of the iOS developer. This can greatly improve user satisfaction, thus improving the app's rating in the App Store.
Another cache attaches more importance to cached data and can quickly edit cached records without having to connect to the server. On behalf of the app include the Google Reader client, later read the class app Instapaper and so on.
Cached policies:
On-demand caching and pre-caching are discussed in the previous section, and they differ greatly in design and implementation. On-demand caching means that the content obtained from the server is stored in a format on the local file system, and then, for each request, checks for the presence of the data in the cache, which is obtained from the server only if the data does not exist (or expires). In this case, the cache layer is the same as the processor cache. The speed of getting data is more important than the data itself. The pre-cache, in turn, places the content locally for future access. For the pre-cache, data loss or cache misses is unacceptable, for example, users download the article ready to look on the subway, but found that there is no such articles on the device.
Apps like Twitter, Facebook, and Foursquare are on-demand caches, while clients such as Instapaper and Google Reader are pre-cached.
Implementing a pre-cache may require a background thread to access the data and save it in a meaningful format so that the local cache can be edited without reconnecting the server. The edit may be "mark record as read" or "Favorites", or other similar actions. Here * * a meaningful format * * refers to the ability to save content in this way, without communicating with the server can make the above mentioned changes, and once again connected to the Internet can be sent back to the server. This ability is different from applications such as Foursquare, although using the latter allows you to see where you are in the absence of an Internet connection (Mayor), which, of course, is cached but cannot be a landlord at a particular location. Core Data (or any structured storage) is one way to implement this cache.
On-demand caching works like a browser cache. It allows us to view previously viewed or accessed content. On-demand caching can be achieved by caching the data model on demand (creating a data Model cache) When a view controller is opened, rather than doing it on a background thread. You can also implement on-demand caching (creating a URL cache) when a URL request returns a successful (OK) answer. There are pros and cons to both approaches, and later I'll explain the pros and cons of each approach in sections 24.3 and 24.6.
An easy way to choose whether to use on-demand caching or pre-cache is to determine if data needs to be processed after the data is downloaded. Post-processing data may be in the form of user-generated edits or updates to downloaded data, such as rewriting the image link in an HTML page to point to a locally cached image. If an application needs to do any of the post-processing mentioned above, it must implement a pre-cache.
Storage cache:
Third-party apps can only store information in the application's sandbox. Because the cached data is not generated by the user, it should be saved in nscachesdirectory, not nsdocumentsdirectory. Creating a stand-alone directory for cached data is a good practice. In the following example, we will create a directory named Myappcache under the Library/caches folder. You can create this:
Nsarray *paths = Nssearchpathfordirectoriesindomains (Nscachesdirectory, Nsuserdomainmask, YES); NSString *cachesdirectory = [Paths objectatindex:0]; Cachesdirectory = [Cachesdirectory stringbyappendingpathcomponent:@ "Myappcache"];
The reason that the cache is stored under the cache folder is that backups of icloud (and itunes) do not include this directory. If you create large cache files in the documents directory, they will be uploaded to icloud at backup time and will soon run out of limited space (about 5 GB when writing this book). You wouldn't do that-who wouldn't want to be a good citizen on a user's iphone? Nscachesdirectory is the solution to this problem.
Pre-caches are implemented using advanced databases (such as original SQLite) or object serialization frameworks (such as core Data). We need to carefully choose different technologies according to our needs. The 5th of this section, "which caching technology should be used," gives some suggestions: When to use the URL cache or the data model cache, and when to use core data. Now let's look at the implementation details of the data model cache.
1. Implementing a data Model cache
You can use the Nskeyedarchiver class to implement a data model cache. In order to archive model objects with Nskeyedarchiver, model classes need to follow the Nscoding protocol.
nscoding protocol Method
-(void) Encodewithcoder: (Nscoder *) Acoder; -(ID) Initwithcoder: (Nscoder *) Adecoder;
When the model follows the Nscoding protocol, the archive object is simple, as long as one of the following methods is called:
[Nskeyedarchiver archiverootobject:objectforarchiving Tofile:archivefilepath]; [Nskeyedarchiver archiveddatawithrootobject:objectforarchiving];
The first method creates an archive file under the path specified by Archivefilepath. The second method returns a NSData object. NSData is usually faster because there is no file access overhead, but the object is saved in the app's memory, and memory is quickly exhausted if unchecked. The ability to periodically cache to flash memory on the iphone is also unwise because, unlike hard drives, Flash read and write life is limited. The developer should try to balance the relationship between the two. Section 24.3 describes the archive implementation cache in detail.
The Nskeyedunarchiver class is used to reverse-archive a model from a file (or NSData pointer). Depending on the location of the anti-archive, choose to use the following two class methods.
[Nskeyedunarchiver Unarchiveobjectwithdata:data]; [Nskeyedunarchiver Unarchiveobjectwithfile:archivefilepath];
These four methods can be useful for converting serialized data.
The premise of using any nskeyedarchiver/nskeyedunarchiver is that the model implements the Nscoding protocol. However, it is easy to do this, and the Nscoding protocol can be implemented automatically using the Accessorizer class tool. (Section 24.8 lists links to Accessorizer in the Mac App store.) )
The following section explains the pre-cache policy. We've just learned that the pre-cache requires a more structured data format, and then we'll look at core data and SQLite.
2. Core Data
As Marcus Zarra says, Core data is more like an object serialization framework than just a database API:
We mistakenly believe that core
Data is a Cocoa database API ... In fact, it is an object framework that can be persisted to disk (zarra,2009 years).
To get a deeper understanding of Core Data, take a look at Marcus S. Zarra *core Data:apple's API for persisting Data on Mac OS x* (Pragmatic Bookshelf, 2009. ISBN 9781934356326).
To save data in core database, first create a core data model file, create entities and relationships (relationship), and then write a way to save and get the data. Apps can get real offline access with core data, just like Apple's built-in mail and calendar apps. When you implement a pre-cache, you must periodically delete (stale) data that is no longer needed, or the cache will grow and affect the performance of your app. Synchronous local changes are implemented by tracing the changeset and sending it back to the server. There are many algorithms for tracking changesets, and I recommend the GIT version control system (this does not cover how to synchronize the cache with a remote server, which is not covered in this book).
3. Implement on-demand caching with core data
While it's technically possible to use core data for on-demand caching, I don't recommend it. The advantage of Core data is that you can independently access the properties of the model without having to archive the complete data. However, the complexity of implementing core data in the application offsets the benefits. Also, for on-demand cache implementations, we may not need to independently access the properties of the model.
4. The original SQLite
SQLite can be embedded in the app by linking Libsqlite3 's libraries authoring, but there's a big flaw in doing so. All Sqlite3 libraries and object-relational mapping (object relational Mapping,orm) mechanisms are almost always slower than core data. In addition, although the sqlite3 itself is thread-safe, the binary package on iOS is not. So unless a custom compiled Sqlite3 library (compiled with thread-safe compilation parameters) is used, it is the responsibility of the developer to ensure that data is read from Sqlite3 or written to Sqlite3 is thread-safe. Core data has so many features and built-in thread safety, so I recommend that you try to avoid using SQLite in iOS.
The only exception to using the original SQLite in an iOS app without core data is that the application-related data in the resource bundle needs to be shared on the third-party platform supported by all apps, such as running on iphone, Android, The location database for an app on the BlackBerry and Windows Phone. But it's not a cache either.
5. What kind of caching technology should be used
Among the many technologies that can store data locally, there are three of them: URL caching, data model caching (leveraging Nskeyedarchiver), and central data.
Suppose you are developing an application that needs to cache data to improve the performance of your application, you should implement on-demand caching (using a data model cache or a URL cache). On the other hand, if you need data to be accessed offline and have a reasonable way to store it for offline editing, use advanced serialization techniques such as core data.
6. Data model caching and URL caching
On-demand caching can be implemented with either a data model cache or a URL cache. Both ways have pros and cons, which depends on the implementation of the server. The implementation of the URL cache is similar to the browser cache or proxy server cache. This caching works best when the server is well-designed and adheres to the HTTP 1.1 cache specification. If the server is a SOAP server (or implements a similar RPC server or restful server), it needs to be cached with the data model. If the server follows the HTTP 1.1 cache specification, it is cached with a URL. The data model cache allows the client (iOS app) to take control of cache invalidation, and when the developer implements the URL cache, the server controls the cache invalidation via the HTTP 1.1 cache control header. While some programmers find this way counterintuitive and complex to implement (especially on the server side), this can be a good way to implement caching. In fact, Mknetworkkit provides native support for the HTTP 1.1 cache standard.
Data Model cache:
In this section we will add an on-demand cache implemented with the data model cache for the Ihotelapp in chapter 14th. On-demand caching is done when the view disappears from the view hierarchy (technically, in the Viewwilldisappear: method). The basic structure of the view controller that supports caching is shown in 24-1. The complete code for AppCache architecture can be found in the download source code in this chapter. What is explained later assumes that you have downloaded the code and can use it at any time.
Figure 24-1
Control flow for a view controller that implements on-demand caching
In the Viewwillappear method, look in the cache for the data that is required to display this view. If there is a data fetch, then update the user interface with cached data. Then check to see if the data in the cache has expired. Your business rules should be able to determine what the new data is and what the old data is. If the content is old, display the data on the UI, while retrieving the data from the server in the background and updating the UI again. If there is no data in the cache, a rotating circle indicates that the data is being loaded and fetched from the server. After you get the data, update the UI.
The previous flowchart assumes that the data displayed on the UI is a model that can be archived. The Nscoding protocol is implemented in the MenuItem model of Ihotelapp. Nskeyedarchiver requires the model to implement this Protocol, as shown in the following code snippet.
Encodewithcoder method for MenuItem class (MENUITEM.M)
- (void) Encodewithcoder: (nscoder *) encoder { [encoder encodeobject:self.itemid forkey:@ "ItemId"]; [encoder encodeobject:self.image forkey:@ "Image"]; [encoder encodeobject:self.name forkey:@ "Name"]; [encoder encodeobject:self.spicylevel forkey:@ "Spicylevel"]; [encoder encodeobject:self.rating forkey:@ "rating"]; [encoder encodeobject:self.itemdescription forkey:@ "ItemDescription"]; [encoder encodeobject:self.waitingtime forkey:@ " Waitingtime "]; [encoder encodeobject:self.reviewcount forkey:@ "Reviewcount"]; }
Initwithcoder method for MenuItem class (MENUITEM.M)
- (ID) Initwithcoder: (nscoder *) decoder {if ((self = [super init]) { self.itemid = [decoder decodeobjectforkey:@ "ItemId"]; self.image = [decoder decodeobjectforkey:@ "Image"]; self.name = [decoder decodeobjectforkey:@ "Name"]; self.spicylevel = [decoder decodeobjectforkey:@ "Spicylevel"]; self.rating = [decoder decodeobjectforkey:@ "rating"]; self.itemDescription = [decoder decodeobjectforkey:@ "ItemDescription"]; self.waitingtime = [decoder decodeobjectforkey:@ "WaitingTime"]; self.reviewcount = [decoder decodeobjectforkey:@ "Reviewcount"]; }return self; }
As mentioned earlier, the implementation of the Nscoding protocol can be generated using Accessorizer.
Based on the caching flowchart in Figure 24-1, we need to implement the actual caching logic in Viewwillappear:. Add the following code to Viewwillappear: it can be implemented.
View Controller viewwillappear: Code snippet for recovering data Model objects from cache in a method
Nsarray *paths = nssearchpathfordirectoriesindomains (nscachesdirectory, nsuserdomainmask, yes); nsstring *cachesdirectory = [paths objectatindex:0]; nsstring *archivepath = [ cachesdirectory stringbyappendingpathcomponent:@ "AppCache/ Menuitems.archive "]; nsmutablearray *cacheditems = [nskeyedunarchiver unarchiveobjectwithfile:archivepath];if (cachedItems == nil) self.menuItems = [AppDelegate.engine localmenuitems];else self.menuitems = cacheditems; NSTimeInterval stalenessLevel = [[[[NSFileManager defaultManager] attributesofitematpath:archivepath error:nil] filemodificationdate] Timeintervalsincenow];if (Stalenesslevel > threshold) self.menuitems = [appdelegate.engine localmenuitems]; [self UpdateUI];
The logical flow of the caching mechanism is as follows.
The view controller checks the previously cached entries in the archive file menuitems.archive and deserializes them.
If the menuitems.archive does not exist, the view controller invokes the method to fetch the data from the server.
If menuitems.archive exists, the view controller checks the archive for modification times to confirm how old the cached data is. If the data expires (as determined by the business requirements), then the data is fetched from the server once. Otherwise, the cached data is displayed.
Next, add the following code to the Viewdiddisappear method to save the model (in the form of Nskeyedarchiver) in the Library/caches directory.
Viewwilldisappear of view Controller: code snippet for cached data model in method
Nsarray *paths = Nssearchpathfordirectoriesindomains (Nscachesdirectory, Nsuserdomainmask, YES); NSString *cachesdirectory = [Paths objectatindex:0]; NSString *archivepath = [cachesdirectory stringbyappendingpathcomponent:@ "appcache/menuitems.archive"]; [Nskeyedarchiver ArchiveRootObject:self.menuItems Tofile:archivepath];
When the view disappears, the contents of the MenuItems array are saved in the archive file. Note that this situation cannot be cached if the data is not fetched from the server in the Viewwillappear: method.
Therefore, you can add cache support for your app by simply adding less than 10 lines of code to the view controller (and adding a few lines of code generated by Accessorizer to the model).
Refactoring
When a developer has multiple view controllers, the preceding code may be redundant. We can avoid redundancy by abstracting out common code and moving into a new class called AppCache. AppCache is the core of the application that handles caching. Abstracting public code into AppCache can avoid viewwillappear: and viewwilldisappear: Redundant code appears.
Refactor this part of the code so that the Viewwillappear/viewwilldisappear code block for the view controller looks like the following. The bold section shows the changes made during refactoring, which I'll explain later in the code.
Viewwillappear of the View controller: Refactoring code fragment (MENUITEMSVIEWCONTROLLER.M) that caches the data model with the AppCache class in the method
-(void) viewwillappear: (BOOL) animated { self.menuitems = [AppCache getCachedMenuItems]; [self.tableView reloadData]; if ([appcache ismenuitemsstale] | | !self.menuitems) { [AppDelegate.engine fetchmenuitemsonsucceeded:^ (nsmutablearray * listofmodelbaseobjects) { self.menuItems = listofmodelbaseobjects; [self.tableview reloaddata]; } onerror:^ (Nserror *engineerror) { [UIAlertView showWithError:engineError]; }]; } [super viewWillAppear:animated]; } -(void) viewwilldisappear: (BOOL) animated { [AppCache cacheMenuItems:self.menuItems]; [super viewwilldisappear:animated]; }
The AppCache class abstracts the logic from the view controller to determine whether the data is out of date, and also abstracts the location of the cache. Later in this chapter we will also modify the AppCache and introduce a layer of caching, which will be stored in memory.
Because AppCache abstracts out where the cache is saved, we don't have to worry about copying and pasting the code to get the app's cache directory. If the app is similar to Ihotelapp, developers can easily enhance the security of cached data by creating subdirectories for each user. Then we can modify the helper method in AppCache, and now it returns the cache directory, and we can get it back to the subdirectory of the currently logged in user. This way, the data cached by a user is not seen by the user who subsequently logs on.
The complete code can be obtained from the source code download of this chapter in this book's website.
Cached version control:
The AppCache class we wrote in the previous section abstracted the on-demand cache from the view controller. When the view appears and disappears, the cache works behind the scenes. However, when you update an app, the model class may change, which means that any data previously archived will not be restored to the new model. As previously mentioned, data is less important for on-demand caching, and developers can delete data and update apps. I'll show you a snippet of code that you can use to remove the cache directory when the version is upgraded.
Validating models in iOS:
The second is to validate the model, and the server typically sends a checksum (ETAG). All subsequent requests for resources from the cache should be re-verified with this checksum to the server * * * resources are changed. If the checksum matches, the server returns a status code of HTTP 304 not modified.
iOS Memory cache:
So far, all iOS devices have flash memory, and Flash has little problem: its read and write life is limited. Although this lifespan is much longer than the life of the device, it is still necessary to avoid reading and writing flash memory too frequently. In the previous example, the view was hidden when it was cached directly to disk, and the view was read directly from disk when it was displayed. This behavior can put a heavy load on the user's device's cache. To avoid this problem, we can introduce another layer of caching, leveraging the device's RAM instead of flash memory (with nsmutabledictionary). In section 24.2, "Implementing the Data Model cache", we describe two ways to create an archive: One is save to a file and the other is saved as a NSData object. This time the second method is used, and we get a nsdata pointer that saves the pointer to Nsmutabledictionary instead of the flat file in the file system. Another benefit of introducing a memory cache is that performance is slightly improved when archiving and deserializing content. It sounds complicated, it's not really complicated. This section describes how to add a layer of transparent, in-memory cache to the AppCache class. ("Transparent" refers to the calling code, the view controller, or even the existence of this layer of cache, and does not require any code changes.) We will also design an LRU (Least recently used, least recently used) algorithm to save the cached data to disk.
The following is a simple list of the steps required to create a memory cache. These steps will be explained in detail in the following sections.
Add variables to hold the memory cache data.
Limit the size of the memory cache and write the least recently used entries to the file and remove them from the memory cache. RAM is limited, and a memory warning is triggered when the usage limit is reached. Not releasing memory when a warning is received causes the app to crash. We certainly don't want this to happen, so set a maximum threshold value for the memory cache. When you add anything after the cache is full, the least recently used objects should be saved to the file (Flash memory).
Handles memory warnings and writes the memory cache as a file to the flash.
When the app shuts down, exits, or enters the background, the memory cache is written to the flash as a file.
An explanation of the "go" iOS cache mechanism