缓存的四种方式?各自应用的场景?
缓存本质将请求到的数据存储到本地,将数据显示到UI界面前先询问本地数据库是否已经存储了访问过的数据,如果没有再去网络请求数据。
只要是存储数据就必须先转化成二进制来进行存储。无论是什么都必须转化成NSData类型的数据来进行存储。
当然普通的对象直接可以通过语句将对象转化成Data数据,可是如何将数据模型的对象转化成NSData呢,自然使用到了归档里返回NSData的方法。
归档——数据模型的缓存
* 归档缓存数据模型的特点?
使用NSKeyedArchiver类来实现数据模型缓存,为了把模型对象用NSKeyedArchiver归档,模型类需要遵循NSCoding协议,非常麻烦。
GitHub上也有相关的封装来逐个便利对象的属性进行所有属性归档协议和反归档协议的操作。
以归档形式来保存数据模型,只能一次性归档保存和一次性反归档解压,只能针对小量的数据模型。
数据操作比较笨拙,就是说如果想改动数据的某一小部分,都需要解压整个数据进行操作然后再归档整个数据进行保存。
* 归档和苹果官方的Core Data的对比?
Core Data的优势是不用反归档完整的数据模型就可以独立访问模型的属性。
然而,在应用中实现Core Data带来的复杂度完全抵消了优势。此外,我们通常情况下都是按照需求进行缓存,可能并不需要独立访问模型的属性。
plist——得自己显示创建文件,读取文件,很麻烦。
* Plist与NSUserDefaules的区别?
自己建立的plist文件什么的,还得自己显示创建文件,读取文件,很麻烦,而用NSUserDefaults则不用管这些东西,就像读字符串一样,直接读取就可以了。
存入NSUserDefaults的值可以是多样的,可以是基础数据类型,也可以是OC对象,而且读取数据十分方便,就跟读取字典的值一样。
NSUserDefaults是一个单例类,并不需要用户在程序中设置NSUserDefaults的全局变量,哪里用到NSUserDefaults的数据,就在哪里创建一个NSUserDefaults对象,然后进行读写操作。
NSUserDefaults——App的设置中需要存储的信息
* UserDefaults的缺点?
官方文档上明确表明UserDefaults不是立即写入磁盘,而是根据时间戳定时的把缓存中的数据写入本地磁盘。为了避免,调用了set方法之后调用synchornize方法强制更新磁盘内容。
* UserDefaults应用场景?
NSUserDefaults保存应用程序的设置和属性。无需指定存储路径,适合持久化存储轻量级数据,比如要保存一个登陆界面的用户名、密码,再次登陆的时候可以直接读取NSUserDefaults的信息。
NSUserDefaults实际上是以“键值对”形式保存的(类似于NSDictionary),因此我们需要通过key来读取或者保存数据(value)。
* NSUserDefautls将数据存储在什么地方?
NSUserDefaults的文件就在Library(存档文件夹,保存用户的配置)下的Preferences偏好设置文件中。
Documents放不可再生的配置文件,Library放可再生的数据文件,temp文件夹在系统内存不够或者关机开机的时候会自动清除。
Library下面还有一个Caches?就是硬盘缓存的位置,而且Library上一层的文件夹就是沙盒。
理解为什么说删除沙盒也可以删除NSUserDefaults的文件了吧!
Caches与Prefereces同级别,NSUserDefault存在Prefereces的Plist配置文件的子文件中。
存储在应用程序内置的一个plist文件里,首先要找到应用程序在MAC上的位置,找到后在添加路径/Library/Prefereces/plist文件/userDefaults就是userDefaults的位置。
本质上说,userDefaults算是plist的一个分支子文件夹罢了。如果想要删掉的话,用removeObjectForKey或者删掉沙盒,也就是你的应用程序然后重新安装就可以清空userDefaults里的键值对。
SQLite(libsqlite3)
——可以自己封装,也可使用FMDataBase等第三方封装库,还可以使用系统的CoreData
* FMDatabase
一个FMDatabase对象代表一个单独的SQLite数据库,全局变量FMDatabase* fm用来执行SQL语句。
FMDatabaseQueue主要用于在多线程中执行多个查询或更新,它是线程安全的,线程安全就是保证同一时间只有一个人操作数据库的原始数据。
最典型的例子就是银行的个人账户绝不能允许两个人同时访问一个原始数据,否则假设两个人都基于最初的数据一个人存钱一个人取钱,那最后就会因为无法及时刷新数据的原因造成数据错误,因此必须通过队列来限制先后顺序,从而保证线程安全。
* 沙盒的文件路径?
每一个程序应用都有一个沙盒,就是这个应用程序对应在MAC或iphone上的位置,沙盒的下面就是Library这个文件夹。文件下面就有用户偏好设置的文件夹和Caches硬盘缓存文件夹!
* 使用数据库对网络请求到的内容进行缓存?
缓存帮助类里的方法以FMDB为基础,将网络请求得到的JSON数据转化成一个字典数组传递到缓存类,然后方法遍历字典数组,将每一个字典整体当成数据库的一行数据进行存储。
很多时候数据库仅仅存储服务器返回回来的字典远远不够,尤其是需要登录认证才能从网络请求到的数据还必须一同传入Access_Taken来满足不同账号使用同一个应用。
网络请求数据是底层,解析数据是业务层,没有缓存机制时,数据来源就是网络请求回来的数据,因为有了缓存机制,数据增添一个来源,首先判断数据库沙盒本地缓存是否为空,然后直接解析从缓存里的数据。
数据库存字典也可以存模型。数据缓存起来的时候,不传字典数组,而是传模型数组。无论是模型数组还是字典数组都需要将数据转化成二进制。使用归档返回NSData的方法把数据转变成二进制数据存储到文件里面去必须让对象遵守NSCoding协议。存储模型数组自然要比存储字典数组更好。
把对象转化成数据后,要想把数据存入文件,必须遵守NSCoding协议,因为数据库也是一个文件呀!但是让每一个成员变量遵守NSCoding协议实在是一个很痛苦的事情。所以封装方法遍历所有的成员变量,然后遍历里对每一个成员变量封装起来了。
* 缓存的应用场景?
GET请求一般用来查询数据,POST请求一般发大量数据给服务器处理(变动性比较大),因此一般只对GET请求进行缓存,而不对POST请求进行缓存。
URL不变又需要多次请求,且每次请求到的数据可能都是一样的,比如服务器上的某张图片,无论下载多少次,返回的数据都是一样的。这种情况就使用缓存处理。好处除了节省用户流量,还可以提升程序的响应速度。
使用NSURLCache类进行URL缓存?
* 使用方法?
每个网络请求都有一个对应的网络缓存回应类,要想对GET请求进行数据缓存,只需设置网络请求对象的缓存策略属性(判断有无缓存执行不同的操作,包括使用缓存、重新请求、不再请求等),系统就会自动利用NSURLCache将GET请求到的数据缓存到内存缓存里。记住哟!一定是存储到了内存缓存之中!
* 缓存策略?
不同的数据类型使用不同的缓存策略,需要及时更新的数据不能用缓存!比如股票、彩票数据。固定频率更新的数据可以定期更改缓存策略或者清除缓存来提高程序的响应速度。
忽略缓存的存在每次都重新请求
忽略缓存中不存在Data数据时才重新请求
要么直接返回失败要么只使用缓存的Data数据,离线状态
缓存策略都是先验证本地数据和远程数据是否相同,同则缓存不同则重新请求。
* 清除缓存?
NSURLCache类创建一个单例类对象,调用相应的Remove方法来消除缓存。
* iOS 5之前只支持内存缓存,同时支持内存缓存和硬盘缓存是什么概念?也就是缓存原理?
应用第一次从网络请求数据,手机的硬盘缓存没有数据,内存缓存中也没有数据。
进行数据请求,首先会将请求的数据进行解析和显示,只要解析显示到了屏幕上,内存缓存便有了数据,然后将服务器的数据缓存到手机的硬盘内存也就是沙盒中,只要程序继续运行,内存缓存便一直会有数据,除非程序终止关闭。
内存缓存通过代码创建的数据库进行数据存储,只要运行在内存中的程序读取了硬盘缓存里的数据,那么内存缓存就开始有数据,当然内存缓存会随着程序的关闭而消失。
Library里的Cache文件夹吧,那里面就是硬盘缓存的位置,跟NSUserDefault存放在同一个文件夹。
XML解析和Json解析?
XML解析的两种不同方式,一是DOM树节点解析,二是SAX解析。一个是树结构,树节点解析的本质就是根据XML的数据结构来逐层剖析。SAX解析的本质就是遍历字符串的每一个字符遇到特定的字符回调不同的方法来对XML的数据进行加工!SAX是按照顺序往下一一遍历,而DOM解析是将元素抽象成数据模型,不一定按照顺序执行,解析XML通常不使用官方的XML解析器类NSXMLParser。多使用第三方库GData来以DOM的方式解析XML。GData解析XML的关键就在于找到根节点元素,根据抽象出来的数据模型一层一层地查找内容。当然除了层层查找,还有一种XPath语法直接数组接收某一个具体路径关键词下的内容。
iOS5以前是没有Json的原生解析框架的,iOS5通常使用苹果的官方JSON序列化类NSJSONSerialization,使用原生Json解析的最大好处就在于不用担心第三方Json解析库的冲突问题,过去使用第三方库解析Json的时候,很容易冲突。
什么时候使用枚举又什么时候使用结构体?
1、比如获取数据模型里的性别,使用枚举的本质相当于创建了一个新的基础数据类型。只不过我们定义的性别这个类似整型数据只有男或女这两个值。
2、当我们想要获取到生日的时候,就必须包含年、月、日这三个基础数据类型。
数据模型——首先分清楚结构,然后一层结构一个对象。一个对象就是一个类。
其实我发现最难的还是建立数据模型,如何根据View视图的需要进行分类,然后提取相应地数据模型。问题的关键可能不是对象的创建,而是类的创建,首先需要创建几个类这就让人很迷茫。我该根据什么东西来创建类呢?首先,分析的是要提取的数据文件还是分析View视图的数据需要,当然,首先考虑视图的数据 需要,根据视图来对数据的需要进行分类。那么首先面临的问题就是如何看懂效果图,分析它的数据模块,并对相同类型的数据模块进行分类。通过这个累的对象,进而设置相同的属性来接受数据。传递存储数据。
问题来了?当我遍历数组时遇到了字典,我们肯定会对字典进行进一步处理,但我不会直接在本类里进行,而是通过下一个类的方法来对字典进一步分析。当然需要把分析完的结果返回到第一个类的对象的属性里存储起来。当然索然返回的只是一个对象,可是这个对象是一个层次的数据的集合,也就是所,第一个类的对象里只有两个属性,可是第二个对象里有下一个结构层次的所有数据。通过第一个对象可以找到第一层的数据,然后通过一个数组属性又可以找到多个第二层次的数据。接着再通过第二层次的数组属性可以找到第三层次结构的数据。那么使用数据的时候,难道说必须通过第一个类创建的对象,点语法数组属性,在进行遍历这个属性又找到那个对象然后又找数组属性么?当然不用!我们想要第三层次的数据,直接使用第三层次的属性所属的对象的类不就可以,这样就直接进入第三层次结构的数据耶!也就是说,首先要做的时分析数据结构,知道有几个层次,一个层次使用一个对象去接收,使用数组去保存。当然通常的下一层数据都是以数组的形式出现,而对象存储的东西则通常是一个字典的内容!也就是说,每当考虑用全新的对象去接收下层数据的值时,首先要做的都是将下层数据全用数组接收起来。
那么问题又来了?为什么将下层数据用对象接收后必须通过第一个类的一个全局数组去接收。按理说,不用全局数组呀!问题在于,这个数组里面包含的不是字典,而是对象。我拿到这个已经包含下层次数据对象的数组后可以用来干些什么呢?首先,我肯定需要对这个数组进一步分析处理,寻找下一个层次结构的数据,问题就在于,我该如何通过这个装满对象的数组去找到下一个层次的值呢?
通过上面的分析,现在已经清楚的知道,那个数据模型的本质 其实即使Json文件的结构。究竟如何去看待这个结构又如何设置属性呢?很简单,就从数据结构的第一层开始,首先少不了一个字典属性去接收,那么现在一个最重要的问题出现了,为什么每次创建数据模型的对象都少不了在属性里创建一个字典属性,说法是为了将传入的字典值存储到属性本地上,方便刷新,可是问题来了,其实根本不需要通过字典这个属性将字典存储到本地呀!唯一行的通的解释就是在非初始化的函数里依然能使用到传入过来的字典参数值。只能这么理解!否则你说刷新这也根本无影响呀!现在我已弄懂数据模型的层次结构了,有什么类型就创建什么属性。假设传入的参数是一个字典,如果字典里有字符串,就直接使用字符串属性去接收,如果字典里有数组,就直接使用数组的去接收已经遍历数组中的元素一个元素存入一个对象中。本质上此时的数组接收的是一个个存入一个个数组元素的对象。毕竟数组中只有一种数据类型。因此将数组中的元素存储到一个个对象中,在使用数组去接收这些对象那个,就会发现,其实遍历的那个数组和接收所有对象的这个属性数组,在内容存储上并无太大的差别!而且,我发现在遍历一个字典是,我们不仅要看到那些字符串或数组这样的显性属性,更应该看到本身数组元素的个数就是一个属性值。而且大有用处!
现在已经创建好了数据模型,马上进入UI的创建,我觉得有必要将数据模型的思路捋一捋,首先。建立数据模型的本质就是将文件中的数据用对象的属性来实现。那么问题来了,一个文件的数据是又结构的,如何根据结构来处理数据是我们建立数据属性的重要指标。比如说:到底应该如何处理不同层次的数据,应该如何接收呢?肯定是通过属性,那么能够一个对象的属性接收所有的数据么?答案是否定的。但是可以通过属于不同类的对象的不同属性来存储不同结构层次的值。简单的说,就是数据是有结构层次的,存储在对象的属性里的值也是有层次的,只不过这个层次联系在于,首先需要在存储下层数据的类里建立一个属性,这个属性其实就是存储上层数据的属性的所属类。通过这个属性就可以等价于上一个对象,接收到上一个对象里所有的值。那么问题来了。根据解析JSon数据的方法,遇到字典就接收,遇到数组就遍历。遍历数组的过程中对数组中的字典进行进一步的分析。也就是说,肯定下一个对象里肯定有一个是用来存储传过来的字典的属性。字典包含数组,数组遍历字典。这就是本质。问题还在于?如何知道这些值究竟用在何处呢?难道是在用的时候在去构造需要的属性,还是将文件里所有的数据都解析出来存入属性里。当然最好的方法还是通过提前分析的方式先弄清楚可能需要用到的数据类型,然后在对象的属性里提前构造。也就是说,最好提前知道需要提取哪些数据。方便构建属性值!
用来存储数据的类叫做模型 ,有一个问题就是当建立的数据模型里面存在性别的属性时,如何才能表示性别呢?你说用整型吧!然后0或1,鬼知道你0或1表示的是男还是女。同样如果你使用字符串,问题依然存在!所以最好的方式还是使用枚举。那么问题又来了,如何使用枚举呢?基础数据类型懂么?就是像int这样的。那么当你觉得使用int无法用int里面提供的数字完美地表达性别时,这时候可以通过枚举创建一个类似于基础数据类型。换句话说吧!OC类型的对象,可以通过创建自定义类来实现,对于基础类型数据,则可以通过枚举来创建。创建的方式就是
typedef enum{
SexMan;
SexWoman;
}Sex;
这样用可以象int那样去接收数据模型里的0或1了。只不过当初是使用int这个基本数据类型去接收数据模型里的1或1,而现在则可以通过Sex这个基本数据类型创建的一个变量去接收数据模型里的SexMan或SexWoman。注意基本数据类型是非OA对象类型只是写assign就行,不写也行。
现在问题又来了,就是如果我希望创建一个属性来接收数据模型里的生日,生日,有年月日,肯定首先想到的是字符串,但是为什么要使用结构体?使用结构体的意义在哪儿呢?而且结构体的使用就是
typedef struct{
int year;
int mouth;
int day;
}Data;
注意这个结构体也是一个基础类型,使用的时候也把它当成一个int使用就可以了!虽然我不懂使用结构体的意义在哪儿?