MD5及本地缓存

MD5:

MD5的全称是Message-Digest Algorithm 5(信息-摘要算法),在90年代初由 MIT Laboratory for Computer Science和RSA Data Security Inc的 Ronald L. Rivest开发出来,经MD2、MD3和MD4发展而来。它的作用是让大容量信息在用数字签名软件签署私人密匙前被"压缩"成一种 保密的格式(就是把一个任意长度的字节串变换成一定长的大整数)。不管是MD2、MD4还是MD5,它们都需要获得一个随机长度的信息并产生一个128位 的信息摘要。虽然这些算法的结构或多或少有些相似,但MD2的设计与MD4和MD5完全不同,那是因为MD2是为8位机器做过设计优化的,而MD4和 MD5却是面向32位的电脑。这三个算法的描述和C语言源代码在Internet RFCs 1321中有详细的描述(http://www.ietf.org/rfc/rfc1321.txt),这是一份最权威的文档,由Ronald L. Rivest在1992年8月向IEFT提交。. .

   Van Oorschot和Wiener曾经考虑过一个在散列中暴力搜寻冲突的函数(Brute-Force Hash Function),而且他们 猜测一个被设计专门用来搜索MD5冲突的机器(这台机器在1994年的制造成本大约是一百万美元)可以平均每24天就找到一个冲突。但单从1991年到 2001年这10年间,竟没有出现替代MD5算法的MD6或被叫做其他什么名字的新算法这一点,我们就可以看出这个瑕疵并没有太多的影响MD5的安全性。 上面所有这些都不足以成为MD5的在实际应用中的问题。并且,由于MD5算法的使用不需要支付任何版权费用的,所以在一般的情况下(非绝密应用领域。但即 便是应用在绝密领域内,MD5也不失为一种非常优秀的中间技术),MD5怎么都应该算得上是非常安全的了。

  算法的应用

  MD5的典型应用是对一段信息(Message)产生信息摘要(Message-Digest),以防止被篡改。比如,在UNIX下有很多软件在下载的时候都有一个文件名相同,文件扩展名为.md5的文件,在这个文件中通常只有一行文本,大致结构如:

   MD5 (tanajiya.tar.gz) = 0ca175b9c0f726a831d895e269332461

   这就是tanajiya.tar.gz文件的数字签名。MD5将整个文件当作一个大文本信息,通过其不可逆的字符串变换算法,产生了这个唯一的MD5信 息摘要。如果在以后传播这个文件的过程中,无论文件的内容发生了任何形式的改变(包括人为修改或者下载过程中线路不稳定引起的传输错误等),只要你对这个 文件重新计算MD5时就会发现信息摘要不相同,由此可以确定你得到的只是一个不正确的文件。如果再有一个第三方的认证机构,用MD5还可以防止文件作者 的"抵赖",这就是所谓的数字签名应用。

  MD5还广泛用于加密和解密技术上。比如在UNIX系统中用户的密码就是以MD5(或其它类 似的算法)经加密后存储在文件系统中。当用户登录的时候,系统把用户输入的密码计算成MD5值,然后再去和保存在文件系统中的MD5值进行比较,进而确定 输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这不但可以避免用户的密码被具有系统管理员 权限的用户知道,而且还在一定程度上增加了密码被破解的难度。

  正是因为这个原因,现在被黑客使用最多的一种破译密码的方法就是一种被 称为"跑字典"的方法。有两种方法得到字典,一种是日常搜集的用做密码的字符串表,另一种是用排列组合方法生成的,先用MD5程序计算出这些字典项的 MD5值,然后再用目标的MD5值在这个字典中检索。我们假设密码的最大长度为8位字节(8 Bytes),同时密码只能是字母和数字,共 26+26+10=62个字符,排列组合出的字典的项数则是P(62,1)+P(62,2)….+P(62,8),那也已经是一个很天文的数字了,存储这 个字典就需要TB级的磁盘阵列,而且这种方法还有一个前提,就是能获得目标账户的密码MD5值的情况下才可以。这种加密技术被广泛的应用于UNIX系统 中,这也是为什么UNIX系统比一般操作系统更为坚固一个重要原因。

  算法描述

  对MD5算法简要的叙述可以为:MD5以512位分组来处理输入的信息,且每一分组又被划分为16个32位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。

   在MD5算法中,首先需要对信息进行填充,使其字节长度对512求余的结果等于448。因此,信息的字节长度(Bits Length)将被扩展至 N*512+448,即N*64+56个字节(Bytes),N为一个正整数。填充的方法如下,在信息的后面填充一个1和无数个0,直到满足上面的条件时 才停止用0对信息的填充。然后,在在这个结果后面附加一个以64位二进制表示的填充前信息长度。经过这两步的处理,现在的信息字节长 度=N*512+448+64=(N+1)*512,即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。

  MD5中有四个32位被称作链接变量(Chaining Variable)的整数参数,他们分别为:A=0x01234567,B=0x89abcdef,C=0xfedcba98,D=0x76543210。

  当设置好这四个链接变量后,就开始进入算法的四轮循环运算。循环的次数是信息中512位信息分组的数目。

  将上面四个链接变量复制到另外四个变量中:A到a,B到b,C到c,D到d。

   主循环有四轮(MD4只有三轮),每轮循环都很相似。第一轮进行16次操作。每次操作对a、b、c和d中的其中三个作一次非线性函数运算,然后将所得结 果加上第四个变量,文本的一个子分组和一个常数。再将所得结果向右环移一个不定的数,并加上a、b、c或d中之一。最后用该结果取代a、b、c或d中之 一。
以一下是每次操作中用到的四个非线性函数(每轮一个)。

   F(X,Y,Z) =(X&Y)|((~X)&Z)
   G(X,Y,Z) =(X&Z)|(Y&(~Z))
   H(X,Y,Z) =X^Y^Z
   I(X,Y,Z)=Y^(X|(~Z))
   (&是与,|是或,~是非,^是异或)

  这四个函数的说明:如果X、Y和Z的对应位是独立和均匀的,那么结果的每一位也应是独立和均匀的。F是一个逐位运算的函数。即,如果X,那么Y,否则Z。函数H是逐位奇偶操作符。

  假设Mj表示消息的第j个子分组(从0到15),<< 
    FF(a,b,c,d,Mj,s,ti)表示a=b+((a+(F(b,c,d)+Mj+ti)<<     GG(a,b,c,d,Mj,s,ti)表示a=b+((a+(G(b,c,d)+Mj+ti)<<     HH(a,b,c,d,Mj,s,ti)表示a=b+((a+(H(b,c,d)+Mj+ti)<<     II(a,b,c,d,Mj,s,ti)表示a=b+((a+(I(b,c,d)+Mj+ti)<< 
  这四轮(64步)是:

  第一轮

   FF(a,b,c,d,M0,7,0xd76aa478)
   FF(d,a,b,c,M1,12,0xe8c7b756)
   FF(c,d,a,b,M2,17,0x242070db) 
          FF(b,c,d,a,M3,22,0xc1bdceee)
   FF(a,b,c,d,M4,7,0xf57c0faf)
   FF(d,a,b,c,M5,12,0x4787c62a)
   FF(c,d,a,b,M6,17,0xa8304613)
   FF(b,c,d,a,M7,22,0xfd469501)
   FF(a,b,c,d,M8,7,0x698098d8)
   FF(d,a,b,c,M9,12,0x8b44f7af)
   FF(c,d,a,b,M10,17,0xffff5bb1)
   FF(b,c,d,a,M11,22,0x895cd7be)
   FF(a,b,c,d,M12,7,0x6b901122)
   FF(d,a,b,c,M13,12,0xfd987193)
   FF(c,d,a,b,M14,17,0xa679438e)
   FF(b,c,d,a,M15,22,0x49b40821) 
  第二轮

   GG(a,b,c,d,M1,5,0xf61e2562)
   GG(d,a,b,c,M6,9,0xc040b340)
   GG(c,d,a,b,M11,14,0x265e5a51)
   GG(b,c,d,a,M0,20,0xe9b6c7aa)
   GG(a,b,c,d,M5,5,0xd62f105d)
   GG(d,a,b,c,M10,9,0x02441453)
   GG(c,d,a,b,M15,14,0xd8a1e681)
   GG(b,c,d,a,M4,20,0xe7d3fbc8)
   GG(a,b,c,d,M9,5,0x21e1cde6)
   GG(d,a,b,c,M14,9,0xc33707d6)
   GG(c,d,a,b,M3,14,0xf4d50d87)
   GG(b,c,d,a,M8,20,0x455a14ed)
   GG(a,b,c,d,M13,5,0xa9e3e905)
   GG(d,a,b,c,M2,9,0xfcefa3f8)
   GG(c,d,a,b,M7,14,0x676f02d9)
   GG(b,c,d,a,M12,20,0x8d2a4c8a)

  第三轮

   HH(a,b,c,d,M5,4,0xfffa3942)
   HH(d,a,b,c,M8,11,0x8771f681)
   HH(c,d,a,b,M11,16,0x6d9d6122)
   HH(b,c,d,a,M14,23,0xfde5380c)
   HH(a,b,c,d,M1,4,0xa4beea44)
   HH(d,a,b,c,M4,11,0x4bdecfa9)
   HH(c,d,a,b,M7,16,0xf6bb4b60)
   HH(b,c,d,a,M10,23,0xbebfbc70)
   HH(a,b,c,d,M13,4,0x289b7ec6)
   HH(d,a,b,c,M0,11,0xeaa127fa)
   HH(c,d,a,b,M3,16,0xd4ef3085)
   HH(b,c,d,a,M6,23,0x04881d05)
   HH(a,b,c,d,M9,4,0xd9d4d039)
   HH(d,a,b,c,M12,11,0xe6db99e5)
   HH(c,d,a,b,M15,16,0x1fa27cf8)
   HH(b,c,d,a,M2,23,0xc4ac5665)

  第四轮

   II(a,b,c,d,M0,6,0xf4292244)
   II(d,a,b,c,M7,10,0x432aff97)
   II(c,d,a,b,M14,15,0xab9423a7)
   II(b,c,d,a,M5,21,0xfc93a039)
   II(a,b,c,d,M12,6,0x655b59c3)
   II(d,a,b,c,M3,10,0x8f0ccc92)
   II(c,d,a,b,M10,15,0xffeff47d)
   II(b,c,d,a,M1,21,0x85845dd1)
   II(a,b,c,d,M8,6,0x6fa87e4f)
   II(d,a,b,c,M15,10,0xfe2ce6e0)
   II(c,d,a,b,M6,15,0xa3014314)
   II(b,c,d,a,M13,21,0x4e0811a1)
   II(a,b,c,d,M4,6,0xf7537e82)
   II(d,a,b,c,M11,10,0xbd3af235)
   II(c,d,a,b,M2,15,0x2ad7d2bb)
   II(b,c,d,a,M9,21,0xeb86d391)

  常数ti可以如下选择:

  在第i步中,ti是4294967296*abs(sin(i))的整数部分,i的单位是弧度。(4294967296等于2的32次方)
所有这些完成之后,将A、B、C、D分别加上a、b、c、d。然后用下一分组数据继续运行算法,最后的输出是A、B、C和D的级联。

  当你按照我上面所说的方法实现MD5算法以后,你可以用以下几个信息对你做出来的程序作一个简单的测试,看看程序有没有错误。

   MD5 ("") = d41d8cd98f00b204e9800998ecf8427e
   MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661
   MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72
   MD5 ("message digest") = f96b697d7cb7938d525a2f31aaf161d0
   MD5 ("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b
   MD5 ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") =
d174ab98d277d9f5a5611c2c9f419d9f
   MD5 ("123456789012345678901234567890123456789012345678901234567890123456789
01234567890") = 57edf4a22be3c955ac49da2e2107b67a

  如果你用上面的信息分别对你做的MD5算法实例做测试,最后得出的结论和标准答案完全一样,那我就要在这里象你道一声祝贺了。要知道,我的程序在第一次编译成功的时候是没有得出和上面相同的结果的。


  MD5的安全性

  MD5相对MD4所作的改进:

   1. 增加了第四轮;

   2. 每一步均有唯一的加法常数;

   3. 为减弱第二轮中函数G的对称性从(X&Y)|(X&Z)|(Y&Z)变为(X&Z)|(Y&(~Z));

   4. 第一步加上了上一步的结果,这将引起更快的雪崩效应;

   5. 改变了第二轮和第三轮中访问消息子分组的次序,使其更不相似;

   6. 近似优化了每一轮中的循环左移位移量以实现更快

、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、

 

对文件使用MD5 加密,首先,需要获取到当前文件的路径

 

 

+ (NSString *)md5ForFileWithPath:(NSString *)path

 

{

 

    NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:path]; 

 

    if( handle== nil ) { 

 

        return nil; 

 

    } 

 

    CC_MD5_CTX md5; 

 

    CC_MD5_Init(&md5); 

 

    BOOL done = NO; 

 

    while(!done) 

 

    { 

 

        NSData* fileData = [handle readDataOfLength: 256 ]; 

 

        CC_MD5_Update(&md5, [fileData bytes], [fileData length]); 

 

        if( [fileData length] == 0 ) done = YES; 

 

    } 

 

    unsigned char digest[CC_MD5_DIGEST_LENGTH]; 

 

    CC_MD5_Final(digest, &md5); 

 

    NSString* s = [NSString stringWithFormat: @"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 

 

                   digest[0], digest[1],  

 

                   digest[2], digest[3], 

 

                   digest[4], digest[5], 

 

                   digest[6], digest[7], 

 

                   digest[8], digest[9], 

 

                   digest[10], digest[11], 

 

                   digest[12], digest[13], 

 

                   digest[14], digest[15]]; 

 

   

 

    return s; 

 

}

 

iOS应用的本地缓存机制设计

功能需求

这个缓存机制满足下面这些功能。

1、可以将数据缓存到本地磁盘。

2、可以判断一个资源是否已经被缓存。如果已经被缓存,在请求相同的资源,先到本地磁盘搜索。

3、可以判断文件缓存什么时候过期。这里为了简单起见这里,我们在请求url资源的时候,给每次请求的文件设定一个过期的时间。

4、可以实现:如果文件已经被缓存,而且没有过期,这将本地的数据返回,否则重新请求url。

5、可以实现:如果文件下载不成功或者下载没有完成,下次打开程序的时候,移除这些没有成功或者没有下载完成的文件。

6、可以实现:同时请求或者下载多个资源。

设计实现

1、设计一个CacheItem类,用来请求一个web连接,它的一个实例表示一个缓存项。这个CacheItem类,需要一个url创建一个NSURLConnection,去请求web资源。使用CacheItem类主要用来请求web资源。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
/* ---------缓存项-------------- */  
  
@interface CacheItem : NSObject {  
@public  
  id<CacheItemDelegate> delegate;  
    //web地址  
  NSString              *remoteURL;  
@private  
    //是否正在下载  
  BOOL                  isDownloading;  
       //NSMutableData对象  
  NSMutableData         *connectionData;  
   //NSURLConnection对象  
  NSURLConnection       *connection;  
}  
  
/* -------------------------- */  
  
@property (nonatomic, retain) id<CacheItemDelegate> delegate;  
@property (nonatomic, retain) NSString  *remoteURL;  
@property (nonatomic, assign) BOOL      isDownloading;  
@property (nonatomic, retain) NSMutableData *connectionData;  
@property (nonatomic, retain) NSURLConnection *connection;  
  
/* ----------开始下载方法----------- */  
  
- (BOOL) startDownloadingURL:(NSString *)paramRemoteURL;  
  
@end  
2、在NSURLConnection开始请求之前,调用CachedDownloadManager类,来搜索和管理本地的缓存文件。将缓存文件的情况保存到一个字典类中。这个字典设计如下:
 
{  
  
  "http://www.cnn.com" =     {  
  
    DownloadEndDate = "2011-08-02 07:51:57 +0100";  
  
    DownloadStartDate = "2011-08-02 07:51:55 +0100";  
  
    ExpiresInSeconds = 20;  
  
    ExpiryDate = "2011-08-02 07:52:17 +0100";  
  
    LocalURL = "/var/mobile/Applications/ApplicationID/Documents/  
  
                httpwww.cnn.com.cache";  
  
  };  
  
  "http://www.baidu.com" =     {  
  
    DownloadEndDate = "2011-08-02 07:51:49 +0100";  
  
    DownloadStartDate = "2011-08-02 07:51:44 +0100";  
  
    ExpiresInSeconds = 20;  
  
    ExpiryDate = "2011-08-02 07:52:09 +0100";  
  
    LocalURL = "/var/mobile/Applications/ApplicationID/Documents/  
  
                httpwww.oreilly.com.cache";  
  
  };  
  
}

2、在NSURLConnection开始请求之前,调用CachedDownloadManager类,来搜索和管理本地的缓存文件。将缓存文件的情况保存到一个字典类中。这个字典设计如下:

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{  
  
  "http://www.cnn.com" =     {  
  
    DownloadEndDate = "2011-08-02 07:51:57 +0100";  
  
    DownloadStartDate = "2011-08-02 07:51:55 +0100";  
  
    ExpiresInSeconds = 20;  
  
    ExpiryDate = "2011-08-02 07:52:17 +0100";  
  
    LocalURL = "/var/mobile/Applications/ApplicationID/Documents/  
  
                httpwww.cnn.com.cache";  
  
  };  
  
  "http://www.baidu.com" =     {  
  
    DownloadEndDate = "2011-08-02 07:51:49 +0100";  
  
    DownloadStartDate = "2011-08-02 07:51:44 +0100";  
  
    ExpiresInSeconds = 20;  
  
    ExpiryDate = "2011-08-02 07:52:09 +0100";  
  
    LocalURL = "/var/mobile/Applications/ApplicationID/Documents/  
  
                httpwww.oreilly.com.cache";  
  
  };  
  
}

 

上面这个字典里面嵌套了字典。里面那层字典表示一个缓存项的缓存信息:下载结束时间、下载开始时间、缓存有效时间、缓存过期时间、缓存到本地的路径。

下面看下CachedDownloadManager类。用它来实现和封装我们的缓存策略。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/* -----------CachedDownloadManager-------------- */
  
@interface CachedDownloadManager : NSObject
  
{
  
@public
  
id delegate;
  
@private
  
//记录缓存数据的字典
  
NSMutableDictionary *cacheDictionary;
  
//缓存的路径
  
NSString *cacheDictionaryPath;
  
}
  
@property (nonatomic, assign)
  
id delegate;
  
@property (nonatomic, copy)
  
NSMutableDictionary *cacheDictionary;
  
@property (nonatomic, retain)
  
NSString *cacheDictionaryPath;
  
/* 保持缓存字典 */
  
- (BOOL) saveCacheDictionary;
  
/* 公有方法:下载 */
  
- (BOOL) download:(NSString *)paramURLAsString
  
urlMustExpireInSeconds:(NSTimeInterval)paramURLMustExpireInSeconds
  
updateExpiryDateIfInCache:(BOOL)paramUpdateExpiryDateIfInCache;
  
/* -------------------------- */
  
@end

 

从上面代码可以看出,这个管理缓存的类中,有一个缓存字典:cacheDictionary,用来表示所有资源的缓存情 况;cacheDictionaryPath用来表示缓存的路径;saveCacheDictionary用来将缓存字典归档到本地文件中。 download:urlMustExpireInSeconds:updateExpiryDateIfInCache是一个公共接口,通过传递 url、缓存过期时间、是否更新缓存过期时间三个参数来方便的使用,实现我们的缓存策略。

3、如果这个文件已经被下载,而且没有过期,则从本地获取文件的数据。如果文件已经过期,则重新下载。我们通过download:urlMustExpireInSeconds:updateExpiryDateIfInCache方法来实现,主要看这个方法的代码:

 

复制代码
  1 /* ---------下载-------------- */
2
3 - (BOOL) download:(NSString *)paramURLAsString
4
5 urlMustExpireInSeconds:(NSTimeInterval)paramURLMustExpireInSeconds
6
7 updateExpiryDateIfInCache:(BOOL)paramUpdateExpiryDateIfInCache{
8
9 BOOL result = NO;
10
11 if (self.cacheDictionary == nil ||[paramURLAsString length] == 0)
       {
14
15 return(NO);
16
17 }
18
19 paramURLAsString = [paramURLAsString lowercaseString];
20
21 //根据url,从字典中获取缓存项的相关数据
22
23 NSMutableDictionary *itemDictionary =
24
25 [self.cacheDictionary objectForKey:paramURLAsString];
26
27 /* 使用下面这些变量帮助我们理解缓存逻辑 */
28
29 //文件是否已经被缓存
30
31 BOOL fileHasBeenCached = NO;
32
33 //缓存是否过期
34
35 BOOL cachedFileHasExpired = NO;
36
37 //缓存文件是否存在
38
39 BOOL cachedFileExists = NO;
40
41 //缓存文件能否被加载
42
43 BOOL cachedFileDataCanBeLoaded = NO;
44
45 //缓存文件数据
46
47 NSData *cachedFileData = nil;
48
49 //缓存文件是否完全下载
50
51 BOOL cachedFileIsFullyDownloaded = NO;
52
53 //缓存文件是否已经下载
54
55 BOOL cachedFileIsBeingDownloaded = NO;
56
57 //过期时间
58
59 NSDate *expiryDate = nil;
60
61 //下载结束时间
62
63 NSDate *downloadEndDate = nil;
64
65 //下载开始时间
66
67 NSDate *downloadStartDate = nil;
68
69 //本地缓存路径
70
71 NSString *localURL = nil;
72
73 //有效时间
74
75 NSNumber *expiresInSeconds = nil;
76
77 NSDate *now = [NSDate date];
78
79 if (itemDictionary != nil){
80
81 fileHasBeenCached = YES;
82
83 }
84
85 //如果文件已经被缓存,则从缓存项相关数据中获取相关的值
86
87 if (fileHasBeenCached == YES)
        {
88
89 expiryDate = [itemDictionary objectForKey:CachedKeyExpiryDate];
92
93 downloadEndDate = [itemDictionary objectForKey:CachedKeyDownloadEndDate];
96
97 downloadStartDate = [itemDictionary objectForKey:CachedKeyDownloadStartDate];
100
101 localURL = [itemDictionary objectForKey:CachedKeyLocalURL];
104
105 expiresInSeconds = [itemDictionary objectForKey:CachedKeyExpiresInSeconds];
108
109 //如果下载开始和结束时间不为空,表示文件全部被下载
110
111 if (downloadEndDate != nil && downloadStartDate != nil)
    {
114
115 cachedFileIsFullyDownloaded = YES;
116
117 }
118
119 /* 如果expiresInSeconds不为空,downloadEndDate为空,表示文件已经正在下载 */
120
121 if (expiresInSeconds != nil && downloadEndDate == nil)
    {
124
125 cachedFileIsBeingDownloaded = YES;
126
127 }
128
129 /* 判断缓存是否过期 */
130
131 if (expiryDate != nil && [now timeIntervalSinceDate:expiryDate] > 0.0)
    {
134
135 cachedFileHasExpired = YES;
136
137 }
139 if (cachedFileHasExpired == NO)
    {   
141 /* 如果缓存文件没有过期,加载缓存文件,并且更新过期时间 */
142
143 NSFileManager *fileManager = [[NSFileManager alloc] init];
144
145 if ([fileManager fileExistsAtPath:localURL] == YES)
       {
146 cachedFileExists = YES;
148
149 cachedFileData = [NSData dataWithContentsOfFile:localURL];
150
151 if (cachedFileData != nil)
           {
153 cachedFileDataCanBeLoaded = YES;
154
155 } /* if (cachedFileData != nil){ */
156
157 } /* if ([fileManager fileExistsAtPath:localURL] == YES){ */
158
159 [fileManager release];
160
161 /* 更新缓存时间 */
162
163 if (paramUpdateExpiryDateIfInCache == YES)
    {
164
165 NSDate *newExpiryDate =
166
167 [NSDate dateWithTimeIntervalSinceNow:
168
169 paramURLMustExpireInSeconds];
170
171 NSLog(@"Updating the expiry date from %@ to %@.",
172
173 expiryDate, newExpiryDate);
176
177 [itemDictionary setObject:newExpiryDate forKey:CachedKeyExpiryDate];
180
181 NSNumber *expires =[NSNumber numberWithFloat:paramURLMustExpireInSeconds];
184
185 [itemDictionary setObject:expires forKey:CachedKeyExpiresInSeconds];
188
189 }
190
191 } /* if (cachedFileHasExpired == NO){ */
192
193 }
194
195 if (cachedFileIsBeingDownloaded == YES)
    {
196
197 NSLog(@"这个文件已经正在下载...");
198
199 return(YES);
200
201 }
202
203 if (fileHasBeenCached == YES){
204
205 if (cachedFileHasExpired == NO && cachedFileExists == YES &&
208
209 cachedFileDataCanBeLoaded == YES && [cachedFileData length] > 0 &&
212
213 cachedFileIsFullyDownloaded == YES)
        {
214
215 /* 如果文件有缓存而且没有过期 */
216
217 NSLog(@"文件有缓存而且没有过期.");
218
219 [self.delegate cachedDownloadManagerSucceeded:self
222
223 remoteURL:[NSURL URLWithString:paramURLAsString]
224
225 localURL:[NSURL URLWithString:localURL]
226
227 aboutToBeReleasedData:cachedFileData isCachedData:YES];
230
231 return(YES);
232
233 } else {
234
235 /* 如果文件没有被缓存,获取缓存失败 */
236
237 NSLog(@"文件没有缓存.");
238
239 [self.cacheDictionary removeObjectForKey:paramURLAsString];
240
241 [self saveCacheDictionary];
242
243 } /* if (cachedFileHasExpired == NO && */
244
245 } /* if (fileHasBeenCached == YES){ */
246
247 /* 去下载文件 */
248
249 NSNumber *expires = [NSNumber numberWithFloat:paramURLMustExpireInSeconds];
252
253 NSMutableDictionary *newDictionary = [[[NSMutableDictionary alloc] init] autorelease];
256
257 [newDictionary setObject:expires forKey:CachedKeyExpiresInSeconds];
260
261 localURL = [paramURLAsString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
266
267 localURL = [localURL stringByReplacingOccurrencesOfString:@"://" withString:@""];
270
271 localURL = [localURL stringByReplacingOccurrencesOfString:@"/" withString:@"{1}quot;];
274
275 localURL = [localURL stringByAppendingPathExtension:@"cache"];
276
277 NSString *documentsDirectory =
278
279 [self documentsDirectoryWithTrailingSlash:NO];
280
281 localURL = [documentsDirectory stringByAppendingPathComponent:localURL];
284
285 [newDictionary setObject:localURL forKey:CachedKeyLocalURL];
288
289 [newDictionary setObject:now forKey:CachedKeyDownloadStartDate];
292
293 [self.cacheDictionary setObject:newDictionary forKey:paramURLAsString];
296
297 [self saveCacheDictionary];
298
299 CacheItem *item = [[[CacheItem alloc] init] autorelease];
300
301 [item setDelegate:self];
302
303 [item startDownloadingURL:paramURLAsString];
304
305 return(result);
306
307 }
复制代码


4、下面我们设计缓存项下载成功和失败的两个委托方法:

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@protocol CacheItemDelegate <NSObject>  
  
//下载成功执行该方法  
  
- (void) cacheItemDelegateSucceeded  
  
  :(CacheItem *)paramSender  
  
  withRemoteURL:(NSURL *)paramRemoteURL  
  
  withAboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData;  
  
//下载失败执行该方法  
  
- (void) cacheItemDelegateFailed  
  
  :(CacheItem *)paramSender  
  
  remoteURL:(NSURL *)paramRemoteURL  
  
  withError:(NSError *)paramError; 
  
@end

当我们下载成功的时候,修改缓存字典中的下载时间,表示已经下载完成,而且需要将请求的资源数据缓存到本地:

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
//缓存项的委托方法
  
- (void) cacheItemDelegateSucceeded:(CacheItem *)paramSender
  
withRemoteURL:(NSURL *)paramRemoteURL
  
withAboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData{
  
//从缓存字典中获取该缓存项的相关数据
  
NSMutableDictionary *dictionary =
  
[self.cacheDictionary objectForKey:[paramRemoteURL absoluteString]];
  
//取当前时间
  
NSDate *now = [NSDate date];
  
//获取有效时间
  
NSNumber *expiresInSeconds = [dictionary
  
objectForKey:CachedKeyExpiresInSeconds];
  
//转换成NSTimeInterval
  
NSTimeInterval expirySeconds = [expiresInSeconds floatValue];
  
//修改字典中缓存项的下载结束时间
  
[dictionary setObject:[NSDate date]
  
forKey:CachedKeyDownloadEndDate];
  
//修改字典中缓存项的缓存过期时间
  
[dictionary setObject:[now dateByAddingTimeInterval:expirySeconds]
  
forKey:CachedKeyExpiryDate];
  
//保存缓存字典
  
[self saveCacheDictionary];
  
NSString *localURL = [dictionary objectForKey:CachedKeyLocalURL];
  
/* 将下载的数据保持到磁盘 */
  
if ([paramAboutToBeReleasedData writeToFile:localURL
  
atomically:YES] == YES){
  
NSLog(@"缓存文件到磁盘成功.");
  
} else{
  
NSLog(@"缓存文件到磁盘失败.");
  
}
  
//执行缓存管理的委托方法
  
[self.delegate
  
cachedDownloadManagerSucceeded:self
  
remoteURL:paramRemoteURL
  
localURL:[NSURL URLWithString:localURL]
  
aboutToBeReleasedData:paramAboutToBeReleasedData
  
isCachedData:NO];
  
}

如果下载失败我们需要从缓存字典中移除改缓存项:

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
//缓存项失败失败的委托方法
  
- (void) cacheItemDelegateFailed:(CacheItem *)paramSender
  
remoteURL:(NSURL *)paramRemoteURL
  
withError:(NSError *)paramError{
  
/* 从缓存字典中移除缓存项,并发送一个委托 */
  
if (self.delegate != nil){
  
NSMutableDictionary *dictionary =
  
[self.cacheDictionary
  
objectForKey:[paramRemoteURL absoluteString]];
  
NSString *localURL = [dictionary
  
objectForKey:CachedKeyLocalURL];
  
[self.delegate
  
cachedDownloadManagerFailed:self
  
remoteURL:paramRemoteURL
  
localURL:[NSURL URLWithString:localURL]
  
withError:paramError];
  
}
  
[self.cacheDictionary
  
removeObjectForKey:[paramRemoteURL absoluteString]];
  
}

5、加载缓存字典的时候,我们可以将没有下载完成的文件移除:

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//初始化缓存字典
  
NSString *documentsDirectory =
  
[self documentsDirectoryWithTrailingSlash:YES];
  
//生产缓存字典的路径
  
cacheDictionaryPath =
  
[[documentsDirectory
  
stringByAppendingString:@"CachedDownloads.dic"] retain];
  
//创建一个NSFileManager实例
  
NSFileManager *fileManager = [[NSFileManager alloc] init];
  
//判断是否存在缓存字典的数据
  
if ([fileManager
  
fileExistsAtPath:self.cacheDictionaryPath] == YES){
  
NSLog(self.cacheDictionaryPath);
  
//加载缓存字典中的数据
  
NSMutableDictionary *dictionary =
  
[[NSMutableDictionary alloc]
  
initWithContentsOfFile:self.cacheDictionaryPath];
  
cacheDictionary = [dictionary mutableCopy];
  
[dictionary release];
  
//移除没有下载完成的缓存数据
  
[self removeCorruptedCachedItems];
  
} else {
  
//创建一个新的缓存字典
  
NSMutableDictionary *dictionary =
  
[[NSMutableDictionary alloc] init];
  
cacheDictionary = [dictionary mutableCopy];
  
[dictionary release];
  
}

 

这样就基本上完成了我们需要的功能,下面看看我们如何使用我们设计的缓存功能。

例子场景

我们用一个UIWebView来显示stackoverflow这个网站,我们在这个网站的内容缓存到本地20秒,如果在20秒内用户去请求该网站,则从本地文件中获取内容,否则过了20秒,则重新获取数据,并缓存到本地。

在界面上拖放一个button和一个webview控件,如下图。

1
2
3
4
5
- (void)viewDidLoad { [super viewDidLoad]; [self setTitle:@"本地缓存测试"];
 
CachedDownloadManager *newManager =[[CachedDownloadManager alloc] init];
 
self.downloadManager = newManager; [newManager release]; [self.downloadManager setDelegate:self]; }

在button的点击事件中加入下面代码,请求stackoverflow :

 

 

1
2
3
static NSString *url = @http://stackoverflow.com;
 
[self.downloadManager download:url urlMustExpireInSeconds:20.0fupdateExpiryDateIfInCache:YES];

 

上面的代码表示将这个stackoverflow的缓存事件设置为20s,并且如果在20s内有相同的请求,则从本地获取 stackoverflow的内容数据。updateExpiryDateIfInCache设置为yes表示:在此请求的时候,缓存时间又更新为 20s,类似我们的session。如果设置成no,则第一次请求20s之后,该缓存就过期。

请求完成之后会执行CachedDownloadManager的委托方法。我们将数据展示在uiwebview中,代码如下:

 

 

1
2
3
- (void) cachedDownloadManagerSucceeded:(CachedDownloadManager *)paramSender remoteURL:(NSURL *)paramRemoteURL localURL:(NSURL*)paramLocalURL aboutToBeReleasedData:(NSData *)paramAboutToBeReleasedData isCachedData:(BOOL)paramIsCachedData
 
{ [webview loadData:paramAboutToBeReleasedData MIMEType:@"text/html" textEncodingName:@"UTF-8" baseURL:[NSURL URLWithString:@"http://stackoverflow.com"]]; }

posted @ 2015-04-21 10:43  飞天至虹  阅读(925)  评论(0编辑  收藏  举报