做过iOS开发的人员都知道,iOS应用需要通过某种与远程Web服务器通信。有些应用可以在没有网络连接的情况下使用,只在网络连接可用时才与服务器同步数据的应用就是这样。当然还有一类应用需要在几乎连续的网络连接下才能为用户提供有效的价值。这类应用通常作为Web服务的移动客户端。
大部分iOS应用都要用到这种功能,折让iPhone成为有史以来最好的互联网连接设备。不过,由于设备总是在移动,连接和接收信号可能都很差。作为iOS开发者得确保应用的感知时间差不多恒定,就好像全部内容存储在本地一样。所以本地缓存数据就是来解决这个问题的。今天我们来看看iOS如何用缓存技术来结局很差的网络连接甚至无网络连接带来的问题。
REST
REST服务有三大特征:无状态,统一资源定位和可缓存。
无状态:每次API调用都被视作新的请求,服务器不会记录客户端上下文。客户端需要维护服务器的状态。
统一资源定位:URL是REST的参数。REST使用这种资源定位,而且不维护客户端状态。
可缓存:REST的响应是以一种统一的,双方一致同意的格式返回给客户端,这样能够更好地解耦。目前,最常用的是XML和JSON。
XML解析
XML解析最常见的就是DOM和SAX解析器。两者的区别就是SAX是一串流解析器,它逐句便利整个XML文档,通过回掉函数返回解析结果。大部分SAX解析器接受一个URL作为参数,解析完目标数据就将数据返回。之前说过NSXMLParser,通过代理的方式回调。
parserDidStartDocument
parserDidEndDocument
parser:didiStartElement:namespaceURL:qualifiedName:attributes:
parser:didiEndElement:namespaceURL:qualifiedName:
parser:foundCharacters:
DOM解析
需要先把XML整个文档加载到内存中才开始解析。其优势在于可以使用Xpath查询访问随机数据,不需要像SAX使用委托。对于iOS开发没有内置的DOM解析器。一般使用第三方包装器,我再网上看到有:基于libxml2的KissXML,TouchXML和GDataXML。
使用DOM解析可以让代码更加简洁。虽然在处理请求时会花费更多的执行时间,但是跟网络操作消耗的时间相比,简直微不足道。
不过,我在项目中用的是SAX解析。
JSON解析
相信做客户端开发的,一定都逃不过Json吧。苹果有自己的解析JSON框架,也是我们常用的NSJSONSerialization。还有很多不错的第三方框架:SBJson,JSONKit,TouchJSON。基本上都是提供基于NSString,NSArray,NSDictionary的分类扩展。我在项目中用的是苹果自带的。
// 将字典或者数组转化为JSON串
- (NSData *)toJSONData:(id)theData{
NSError *error = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:theData
options:NSJSONWritingPrettyPrinted
error:&error];
if ([jsonData length] > 0 && error == nil){
return jsonData;
}else{
return nil;
}
}
使用这个方法的返回,我们就可以得到想要的JSON串
NSString *jsonString = [[NSString alloc] initWithData:jsonData
encoding:NSUTF8StringEncoding];
// 将JSON串转化为字典或者数组
- (id)toArrayOrNSDictionary:(NSData *)jsonData{
NSError *error = nil;
id jsonObject = [NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingAllowFragments error:&error];
if (jsonObject != nil && error == nil){
return jsonObject;
}else{
// 解析错误
return nil;
}
}
//将JSON串与NSArray和NSDictionary的操作进行封装
1.将NSString转化为NSArray或者NSDictionary
#import "NSString+JSONCategories.h"
@implementation NSString(JSONCategories)
-(id)JSONValue;
{
NSData* data = [self dataUsingEncoding:NSUTF8StringEncoding];
__autoreleasing NSError* error = nil;
id result = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (error != nil) return nil;
return result;
}
@end
2.将NSArray或者NSDictionary转化为NSString
#import "NSObject+JSONCategories.h"
@implementation NSObject (JSONCategories)
-(NSData*)JSONString;
{
NSError* error = nil;
id result = [NSJSONSerialization dataWithJSONObject:self
options:kNilOptionserror:&error];
if (error != nil) return nil;
return result;
}
@end
之前说过NSURLConnection。但是,为了开发REST服务,用MKNetworkKit更加方便。封装了很多常用的功能:基本认证/摘要认证,表单发布,上传/下载文件。还可以使用队列来管理网络请求。
Note:与浏览器不同的是,大部分运营商网络都会限制并发数据连接的数量,在EDGE连接中不要进行1个以上的网络操作,在3G网络中不要使用2个以上的并发网络操作,在Wi-Fi连接中的并发网络操作数量不要超过6个。
缓存
为了改善性能,提高用户的体验质量,离线工作是重点。一般有两种缓存技术支持离线:
按需缓存:应用缓存起请求应答,和Web浏览器的工作原理一样
预缓存:缓存全部内容以便离线访问
按需缓存思想:把从服务器获取的内容以某种格式存放在本地文件系统,之后对于每次请求,检查缓存中是否存在这块数据,只有当数据不存在(或者过期)的情况下才从服务器获取。这样,缓存层就和处理器的高速缓存差不多。获取数据的速度比数据本身重要。按需缓存的工作原理类似浏览器,允许我们查看以前查看或者访问过的内容。我们开发中,通讯录,消息等这些都是采用的按需缓存,不需要后台线程做这件事,也可以在一个URL请求返回成功实现按需缓存。
预缓存思想:需要一个后台县城访问数据并以有意义的格式保存,本地缓存无需重新连接服务器就可以被编辑。对预缓存来说,数据丢失或者缓存不命中是不可接受的。CoreData (结构化数据)试试先这种缓存的一种方式。
沙盒文件:
每个ios应用都有自己的应用沙盒,应用沙盒就是文件系统目录,与其他应用的文件系统隔离,ios系统不允许访问其他应用的应用沙盒。在ios8中已经开放访问。
应用沙盒一般包括以下几个文件目录:应用程序包、Documents、Libaray(下面有Caches和Preferences目录)、tmp。
应用程序包:包含所有的资源文件和可执行文件。
Documents:保存应用运行时生成的需要持久化的数据,iTunes会自动备份该目录。苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目录
tmp:保存应用运行时所需的临时数据,使用完毕后再将相应的文件从该目录删除。应用没有运行时,系统也有可能会清除该目录下的文件,iTunes不会同步该目录。iphone重启时,该目录下的文件会丢失。
Library:存储程序的默认设置和其他状态信息,iTunes会自动备份该目录。
Libaray/Caches:存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出删除。一般存放体积比较大,不是特别重要的资源。
Libaray/Preferences:保存应用的所有偏好设置,ios的Settings(设置)应用会在该目录中查找应用的设置信息,iTunes会自动备份该目录。
沙盒文件目录获取代码:
//Home目录NSString *homeDirectory = NSHomeDirectory();
//Document目录NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
//Cache目录NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
//Libaray目录NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
NSString *path = [paths objectAtIndex:0];
//tmp目录
NSString *tmpDir = NSTemporaryDirectory();
就像我们实验室做的企业级通信APP,像用户的账号密码,登陆时需要的IP号和端口号,用户头像等这些信息都需要存放到Library/Cache下。而偏好设置像响铃,震动这些存放到Library/preference下。
而我们的源代码打包后放在Document路径下。我们下载的文件,下载过程中会放在tmp下,下载完成再放到Library。
数据缓存方法
1)数据模型缓存
我们可以用NSKeyedArchiver来实现。首先需要把模型类进行编解码重写。也就是要遵循NSCoding协议。
@interface Student : NSObject <NSCoding>
@property (nonatomic, assign) int idNum;
@property (nonatomic, copy) NSString *name;
@end
@implementation Student
#pragma mark 编码 对对象属性进行编码的处理
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeInt:_idNum forKey:IDNUM];
[aCoder encodeObject:_name forKey:NAME];
}
#pragma mark 解码 解码归档数据来初始化对象
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
_idNum = [aDecoder decodeIntForKey:IDNUM];
_name = [aDecoder decodeObjectForKey:NAME];
}
return self;
}
@end
然后对模型对象进行序列化
//获得文件路径
NSString *documentPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSString *filePath = [documentPath stringByAppendingPathComponent:@"file.archiver"];
//归档(序列化)
NSArray *archiveAry = @[@"jereh",@"ios"];
if ([NSKeyedArchiver archiveRootObject: archiveAry toFile:filePath]) {
}
//解归档(反序列化)
NSArray *unArchiveAry = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
2)直接对文件进行操作我们常用到的plist
创建文件->添加数据
读取:
//path 读取当前程序定义好的provinces.plist省份城市文件
NSString *path = [[NSBundle mainBundle] pathForResource:@"student" ofType:@"plist"];
NSDictionary *data = [NSDictionary dictionaryWithContentsOfFile:path];
self.studentContent = [NSArray arrayWithArray:[data objectForKey:@"student"]];//array数组的名称就叫student
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];//获取标准函数对象
NSMutableDictionary *default = [defaults objectForKey:@"xxx"];//通过对象获取名称下NSMutableDictionary数据
NSString *studentname= [default objectForKey:@"name"];
增删改查
[defaults setObject:@"kaka" forKey:@"newdata"];//添加id对象类型数据
[defaults objectForKey:@"newdata"];//获取对象类型数据
[defaults setDouble:2013 forKey:@"double"];//添加double类型数据
[defaults doubleForKey:@"double"];//获取double类型数据
[defaults setBool:NO forKey:@"BOOL"];//添加BOOL类型数据
[defaults boolForKey:@"BOOL"];//获取BOOL类型数据
[defaults setInteger:12 forKey:@"int"];//添加int类型数据
[defaults integerForKey:@"int"];//获取int类型数据
[defaults setFloat:0.23 forKey:@"float"];//添加Float类型数据
[defaults floatForKey:@"float"];//获取float类型数据
[defaults removeObjectForKey:@"newdata"];//删除对象数据
3)SQLite
项目中的sqlit3,简单说一下使用过程吧
使用的过程根据使用的函数大致分为如下几个过程:
sqlite3_open()
sqlite3_prepare()
sqlite3_step()
sqlite3_column()
sqlite3_finalize()
sqlite3_close()
具体的讲解,可以参考这微博主:http://blog.csdn.net/zhuzhihai1988/article/details/7878093
4) CoreData
CoreData更像是一个对象序列化框架,而不仅仅是一个数据库的API。我理解的是非关系型数据库的一种,采用key-value的形式存储。