iOS 内存缓存机制 - rjmsunglow/Wiki GitHub Wiki

这段时间由于项目的原因,使用MKNetWorkKit组件进行数据内存缓存的时候,经常发生数据重复的现象,所以对iOS中内存缓存机制做了一些了解,但对于所遇到的问题还需进行测试尝试,找到合理的借觉方法; 在实际业务中,iOS中的缓存大体可以分为两种,按需缓存和预缓存。 按需缓存是指把从服务器获取的内容以某种格式存放在本地文件系统,之后对于每次请求,检查缓存中是否存在这块数据,只有当数据不存在(或者过期)的情况下才从服务器获取。这样的话,缓存层就和处理器的高速缓存差不多。获取数据的速度比数据本身重要。而预缓存是把内容放在本地以备将来访问。对预缓存来说,数据丢失或者缓存不命中是不可接受的,比方用户下载了文章准备在地铁上看,但却发现设备上不存在这些文章。像Twitter、Facebook和Foursquare这样的应用属于按需缓存,而Instapaper和Google Reader等客户端则属于预缓存。 实现预缓存可能需要一个后台线程访问数据并以有意义的格式保存,以便本地缓存无需重新连接服务器即可被编辑。编辑可能是“标记记录为已读”或“加入收藏”,或其他类似的操作。这里有意义的格式是指可以用这种方式保存内容,不用和服务器通信就可以在本地作出上面提到的修改,并且一旦再次连上网就可以把变更发送回服务器。这种能力和Foursquare等应用不同,虽然使用后者你能在无网络连接的情况下看到自己是哪些地点的地主(Mayor),当然前提是进行了缓存,但无法成为某个地点的地主。Core Data(或者任何结构化存储)是实现这种缓存的一种方式。 按需缓存工作原理类似于浏览器缓存。它允许我们查看以前查看或者访问过的内容。按需缓存可以通过在打开一个视图控制器时按需地缓存数据模型(创建一个数据模型缓存)来实现,而不是在一个后台线程上做这件事。也可以在一个URL请求返回成功(200 OK)应答时实现按需缓存(创建一个URL缓存)。两种方法各有利弊,稍后我会在24.3节和24.6节中解释各个方法的优缺点。 选择使用按需缓存还是预缓存的一个简便方法是判断是否需要在下载数据之后处理数据。后期处理数据可能是以用户产生编辑的形式,也可能是更新下载的数据,比如重写HTML页面里的图片链接以指向本地缓存图片。如果一个应用需要做上面提到的任何后期处理,就必须实现预缓存。 应该用哪种缓存技术: 在众多可以本地保存数据的技术中,有三种脱颖而出:URL缓存、数据模型缓存(利用NSKeyedArchiver)和Core Data。 假设你正在开发一个应用,需要缓存数据以改善应用表现出的性能,你应该实现按需缓存(使用数据模型缓存或URL缓存)。另一方面,如果需要数据能够离线访问,而且具有合理的存储方式以便离线编辑,那么就用高级序列化技术(如Core Data)。 在viewWillAppear方法中,查看缓存中是否有显示这个视图所需的数据。如果有就获取数据,再用缓存数据更新用户界面。然后检查缓存中的数据是否已经过期。你的业务规则应该能够确定什么是新数据、什么是旧数据。如果内容是旧的,把数据显示在UI上,同时在后台从服务器获取数据并再次更新UI。如果缓存中没有数据,显示一个转动的圆圈表示正在加载,同时从服务器获取数据。得到数据后,更新UI。

在iHotelApp的MenuItem模型中实现NSCoding协议。NSKeyedArchiver需要模型实现这个协议。

MenuItem类的encodeWithCoder方法(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"];`

`}`

MenuItem类的initWithCoder方法(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;

`}`

如果MenuItems.archive不存在,视图控制器调用方法从服务器获取数据。 如果MenuItems.archive存在,视图控制器检查归档文件的修改时间以确认缓存数据有多旧。如果数据过期了,再从服务器获取一次数据。否则显示缓存的数据。