App Store中的App分析
App已經與我們形影不離了,不管在地鐵上、公交上還是在會場你總能看到很多人拿出來手機,刷一刷微博,看看新聞。
據不完全統計有近一半的用戶在非Wifi環境打開App,以下為一個典型iPhone和Android App(50W+用戶)的友盟后台數據:
3G、2G的數據連接往往不穩定(特別在公交或者地鐵上),這時打開一些App就會像這樣:
當然也會有一些體驗很好的App,在離線狀態下也能順暢使用:
甚至提供了離線閱讀功能:
如何做?
打開過的文章、下載過的音頻、查看過的圖片我們都希望Cache到本地,下次不用再向服務器請求。
首先,我們為了最快讓用戶看到內容,會在ViewDidLoad加載Cache數據,如:
- (void)viewDidLoad { [self getArticleList:0 length:SECTION_LENGTH useCacheFirst:YES]; }
然后在viewDidAppear中向服務器請求最新數據,如
- (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; //... [self getArticleList:0 length:SECTION_LENGTH useCacheFirst:NO] }
當然這里的getArticleList接口有useCacheFirst參數,我們需要網絡請求模塊能夠支持這一點,下面就介紹這些庫和工具。(借助一些工具很容易能做到這些,而不用自己造輪子。遵循“凡事都應該最簡單,而不過於簡陋”的原則,這里整理一下,方便項目中使用)。
1.NSMutableURLRequest
Sample(參考麒麟的文章《iOS開發之緩存(一):內存緩存》來使用NSURLCache):
NSString *paramURLAsString= @"http://www.baidu.com/"; if ([paramURLAsString length] == 0){ NSLog(@"Nil or empty URL is given"); return; } NSURLCache *urlCache = [NSURLCache sharedURLCache]; /* 設置緩存的大小為1M*/ [urlCache setMemoryCapacity:1*1024*1024]; //創建一個nsurl NSURL *url = [NSURL URLWithString:paramURLAsString]; //創建一個請求 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0f]; //從請求中獲取緩存輸出 NSCachedURLResponse *response = [urlCache cachedResponseForRequest:request]; //判斷是否有緩存 if (response != nil){ NSLog(@"如果有緩存輸出,從緩存中獲取數據"); [request setCachePolicy:NSURLRequestReturnCacheDataDontLoad]; } self.connection = nil; /* 創建NSURLConnection*/ NSURLConnection *newConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]; self.connection = newConnection; [newConnection release];
但是NSMutableURLRequest使用起來不夠簡便,在實際項目中我很少用它,而基本使用ASIHTTPRequest來代替。
2.ASIHTTPRequest
你可以從這里找到它的介紹:http://allseeing-i.com/ASIHTTPRequest/,在5.0/4.0及之前iOS版本,ASIHTTPRequest基本是主力的 HTTP requests library,它本身也是Github中的開源項目,但是從iOS 5.0之后逐漸停止維護了。未來的項目可以使用AFNetworking或者MKNetworkKit代替ASIHTTPRequest。
ASIHTTPRequest的簡介如下:
ASIHTTPRequest is an easy to use wrapper around the CFNetwork API that makes some of the more tedious aspects of communicating with web servers easier. It is written in Objective-C and works in both Mac OS X and iPhone applications.
It is suitable performing basic HTTP requests and interacting with REST-based services (GET / POST / PUT / DELETE). The included ASIFormDataRequest subclass makes it easy to submit POST data and files usingmultipart/form-data.
ASIHTTPRequest庫API設計的簡單易用,並且支持block、queue、gzip等豐富的功能,這是該開源項目如此受歡迎的主要原因。
ASIHTTPRequest庫中提供了ASIWebPageRequest組件用於請求網頁,並且能把網頁中的外部資源一並請求下來,但是我在實際項目中使用后發現有嚴重Bug,所以不建議使用。
ASIHTTPRequest庫的介紹中也提到了它可以支持REST-based service,但是與Restfull API打交道我們往往使用下面介紹的的RestKit。
Sample:
NSMutableString *requestedUrl = [[NSMutableString alloc] initWithString:self.url]; //如果優先使用本地數據 ASICachePolicy policy = _useCacheFirst ? ASIOnlyLoadIfNotCachedCachePolicy : (ASIAskServerIfModifiedCachePolicy | ASIFallbackToCacheIfLoadFailsCachePolicy); asiRequest = [ASIHTTPRequest requestWithURL: [NSURL URLWithString:[requestedUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]]; [asiRequest setDownloadCache:[ASIDownloadCache sharedCache]]; [asiRequest setCachePolicy:policy]; [asiRequest setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy]; // Connection if (_connectionType == ConnectionTypeAsynchronously) { [asiRequest setDelegate:self]; [asiRequest startAsynchronous]; // Tell we're receiving. if (!_canceled && [_delegate respondsToSelector:@selector(downloaderDidStart:)]) [_delegate downloaderDidStart:self]; } else { [asiRequest startSynchronous]; NSError *error = [asiRequest error]; if (!error) { [self requestFinished:asiRequest]; } else { [self requestFailed:asiRequest]; } } [requestedUrl release];
3.RestKit
官方網站:http://restkit.org/,Github開源項目,與 Restfull API 的 Web服務打交道,這個庫非常便捷,它也提供了很完整的Cache機制。
Sample:
+ (void)setCachePolicy:(BOOL)useCacheFirst { RKObjectManager* objectManager = [RKObjectManager sharedManager]; if (useCacheFirst) { objectManager.client.cachePolicy = RKRequestCachePolicyEnabled; //使用本地Cache,如果沒有Cache請求服務器 } else { objectManager.client.cachePolicy = RKRequestCachePolicyLoadIfOffline|RKRequestCachePolicyTimeout; //離線或者超時時使用本地Cache } }
+ (BOOL)getHomeTimeline:(NSInteger)maxId length:(NSInteger)length delegate:(id<RKObjectLoaderDelegate>)delegate useCacheFirst:(BOOL)useCacheFirst { if (delegate == nil) return NO; [iKnowAPI setCachePolicy:useCacheFirst]; //... }
Cache請求只是RestKit最基本的功能,RestKit真正強大的地方在於處理與RESTful web services交互時的相關工作非常簡便(https://github.com/RestKit/RestKit/wiki),RestKit還可以Cache data model到Core Data中:
Core Data support. Building on top of the object mapping layer, RestKit provides integration with Apple's Core Data framework. This support allows RestKit to persist remotely loaded objects directly back into a local store, either as a fast local cache or a primary data store that is periodically synced with the cloud. RestKit can populate Core Data associations for you, allowing natural property based traversal of your data model. It also provides a nice API on top of the Core Data primitives that simplifies configuration and querying use cases through an implementation of the Active Record access pattern.
但實際上RKRequestCachePolicy已經解決了大部分Cache需求。
4.SDWebImage
SDWebImage是Github開源項目:https://github.com/rs/SDWebImage,它用於方便的請求、Cache網絡圖片,並且請求完畢后交由UIImageView顯示。
Asynchronous image downloader with cache support with an UIImageView category.
SDWebImage作為UIImageView的一個Category提供的,所以使用起來非常簡單:
// Here we use the new provided setImageWithURL: method to load the web image [imageView setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]];
AFNetworking也提供了類似功能(UIImageView+AFNetworking):
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)]; [imageView setImageWithURL:[NSURL URLWithString:@"http://i.imgur.com/r4uwx.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder-avatar"]];
5.UIWebView中的圖片Cache
如果你使用UIWebView來展示內容,在離線情況下如果也想能顯示的話需要實現2點:
- Cache Html頁面
- Cache 圖片等元素
使用上面介紹的網絡組件來Cache Html頁面比較便捷,之后使用webView loadHTMLString即可加載本地Html頁面,而Cache圖片需要更換NSURLCache公共實例為自定義的NSURLCache(UIWebView使用的即是+[NSURLCache sharedURLCache]):
//設置使用自定義Cache機制 LocalSubstitutionCache *cache = [[[LocalSubstitutionCache alloc] init] autorelease]; [cache setMemoryCapacity:4 * 1024 * 1024]; [cache setDiskCapacity:10 * 1024 * 1024]; [NSURLCache setSharedURLCache:cache];
自定義NSURLCache:
#import <Foundation/Foundation.h> @interface LocalSubstitutionCache : NSURLCache { NSMutableDictionary *cachedResponses; } + (NSString *)pathForURL:(NSURL*)url; @end
詳細的見NewsReader中的LocalSubstitutionCache.h/.m和WebViewController.m中的viewDidLoad,News Reader開源項目這里參考的是:http://cocoawithlove.com/2010/09/substituting-local-data-for-remote.html
NewsReader中的介紹
《iOS News Reader開源項目》這篇文章介紹到的開源項目改進了離線使用體驗:
在沒有網絡的情況下使用已經Cache過的所有數據:文章、圖片、音頻等等,用到的主要方案已經在上面介紹了,詳細的請看源碼:https://github.com/cubewang/NewsReader。
NewsReader項目因為歷史演進的原因已經有些龐大了,需要進一步重構,在之后的項目中我們的客戶端結構更精簡。
另外歡迎加QQ群討論:161561752(已滿) 64084914