EGOCache 소스 분석

6942 단어
1. 소개
EGOCache는 간단하고 스레드가 안전한 건-값(key-value) 기반 캐시 프레임워크로 NSString, UI/NSImage와 NSData를 지원하며 프로토콜을 실현하는 모든 종류를 저장하여 캐시 만료 시간(기본값은 1일)을 설정할 수 있다.디스크 캐시만 제공되고 메모리 캐시는 제공되지 않습니다.
두 가지 질문 코드를 가지고 읽을 수 있습니다: EGOCache는 어떻게 캐시를 진행합니까?캐시 만료는 어떻게 감지합니까?
2. 코드 분석
  • EGOCache는 단일 클래스로 전체 프로그램의 응용 주기는 한 번만 초기화된다.init 방법에서 캐시 디렉터리를 초기화합니다:
  • - (instancetype)init {
     NSString* cachesDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];
     NSString* oldCachesDirectory = [[[cachesDirectory stringByAppendingPathComponent:[[NSProcessInfo processInfo] processName]] stringByAppendingPathComponent:@"EGOCache"] copy];
    
     if([[NSFileManager defaultManager] fileExistsAtPath:oldCachesDirectory]) {
      [[NSFileManager defaultManager] removeItemAtPath:oldCachesDirectory error:NULL];
     }
     
     cachesDirectory = [[[cachesDirectory stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] stringByAppendingPathComponent:@"EGOCache"] copy];
     return [self initWithCacheDirectory:cachesDirectory];
    }
    
  • 방법에서 EGOCache 실례 대상을 초기화할 때마다plist 파일에 존재하는 모든 캐시 항목을 훑어보고 캐시 항목의 시간과 현재 시간을 비교한다. 캐시 항목의 시간이 현재 시간보다 이르면 캐시 파일을 삭제하고plist 파일에 있는 키의 기록을 삭제한다.

  • 구분 방법 중의 세 개의 대기열을 주의하십시오:캐시 항목에 대한 작업을 위한 cacheInfoQueue 동기화 대기열frozenCacheInfoQueue 동기화 대기열, frozenCacheInfo에 대한 작업, frozenCacheInfo와cacheInfo의 차이점은 전자는 변할 수 없다는 것이다. 매번cacheInfo 내용이 업데이트된 후frozenCacheInfo에 동기화되어 사용자 캐시 항목에서 읽은 데이터가 작업 중인 것이 없고 데이터의 안전과 일치성을 확보합니다.diskQueue 병렬 대기열은 파일을 복사하고 파일 데이터를 쓰며 키에 따라 파일을 제거합니다.
    전역 동기화 대기열이 새 라인을 열지 않았습니다. 직렬 실행입니다.전역 병발 비동기 대기열에 새로운 라인이 열려 있어 병발 실행이 가능합니다.수동으로 만든 직렬 동기화 대기열이 새 라인을 열지 않았습니다. 직렬 실행입니다.수동으로 만든 직렬 비동기 대기열에 새 스레드 1개가 열려 있습니다. 직렬 실행입니다.
    - (instancetype)initWithCacheDirectory:(NSString*)cacheDirectory {
     if((self = [super init])) {
      _cacheInfoQueue = dispatch_queue_create("com.enormego.egocache.info", DISPATCH_QUEUE_SERIAL);
      dispatch_queue_t priority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
      dispatch_set_target_queue(priority, _cacheInfoQueue);
      
      _frozenCacheInfoQueue = dispatch_queue_create("com.enormego.egocache.info.frozen", DISPATCH_QUEUE_SERIAL);
      priority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
      dispatch_set_target_queue(priority, _frozenCacheInfoQueue);
      
      _diskQueue = dispatch_queue_create("com.enormego.egocache.disk", DISPATCH_QUEUE_CONCURRENT);
      priority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
      dispatch_set_target_queue(priority, _diskQueue);
      
      //      
      _directory = cacheDirectory;
    
      //        
      _cacheInfo = [[NSDictionary dictionaryWithContentsOfFile:cachePathForKey(_directory, @"EGOCache.plist")] mutableCopy];
      if(!_cacheInfo) {
       _cacheInfo = [[NSMutableDictionary alloc] init];
      }
      
      //     
      [[NSFileManager defaultManager] createDirectoryAtPath:_directory withIntermediateDirectories:YES attributes:nil error:NULL];
      
      //        NSTimeInterval
      NSTimeInterval now = [[NSDate date] timeIntervalSinceReferenceDate];
      NSMutableArray* removedKeys = [[NSMutableArray alloc] init];
      
      //   plist      ,                 :            ,         ,    plist       key    
      for(NSString* key in _cacheInfo) {
       if([_cacheInfo[key] timeIntervalSinceReferenceDate] <= now) {
        [[NSFileManager defaultManager] removeItemAtPath:cachePathForKey(_directory, key) error:NULL];
        [removedKeys addObject:key];
       }
      }
      [_cacheInfo removeObjectsForKeys:removedKeys];
      //   plist      
      self.frozenCacheInfo = _cacheInfo;
            
      //        :1 
      [self setDefaultTimeoutInterval:86400];
     }
     
     return self;
    }
    
  • 캐시 데이터 읽기: 캐시 항목을 읽을 때 캐시 항목이 존재하는지 확인합니다(hasCacheForKey:).만약 캐시 항목이 존재한다면, 이어서 읽은 캐시 항목의 저장 시간이 현재 시간에 비해 만료되었는지 판단합니다. (Why? 일부 캐시 항목은 EGOCache가 초기화된 후에 만료되었지만, 이 캐시 항목을 읽을 수 있습니다. 이것은 옳지 않습니다.)캐시 항목이 만료되지 않으면 읽은 캐시 항목의 데이터를 되돌려줍니다.
  • - (NSString*)stringForKey:(NSString*)key {
     return [[NSString alloc] initWithData:[self dataForKey:key] encoding:NSUTF8StringEncoding];
    }
    
    - (NSData*)dataForKey:(NSString*)key {
     //        
     if([self hasCacheForKey:key]) {
      return [NSData dataWithContentsOfFile:cachePathForKey(_directory, key) options:0 error:NULL];
     } else {
      return nil;
     }
    }
    
    - (BOOL)hasCacheForKey:(NSString*)key {
     NSDate* date = [self dateForKey:key];
     if(date == nil) return NO;
     //        
     if([date timeIntervalSinceReferenceDate] < CFAbsoluteTimeGetCurrent()) return NO;
     
     return [[NSFileManager defaultManager] fileExistsAtPath:cachePathForKey(_directory, key)];
    }
    
  • 캐시를 지우고 키 키에 따라 파일을 삭제(removeCacheForKey:)할 때 삭제할 파일과 로컬 파일의 이름을 저장하는 것을 피한다.그리고 비동기 대기열에서 파일 삭제하기;캐시 항목 시간 (setCacheTimeoutInterval: forKey:) 을 설정합니다. 캐시 시간이 존재하면 캐시 항목 정보를 삭제하고 키에 따라 캐시 시간을 업데이트합니다.캐시 (clearCache) 를 제거할 때 직렬 동기화 대기열에서 파일을 삭제한 다음 캐시 항목 정보를 삭제합니다.
  • - (void)removeCacheForKey:(NSString*)key {
      //                       
     CHECK_FOR_EGOCACHE_PLIST();
    
     dispatch_async(_diskQueue, ^{
      [[NSFileManager defaultManager] removeItemAtPath:cachePathForKey(_directory, key) error:NULL];
     });
    
     [self setCacheTimeoutInterval:0 forKey:key];
    }
    
    - (void)clearCache {
     dispatch_sync(_cacheInfoQueue, ^{
      for(NSString* key in _cacheInfo) {
       [[NSFileManager defaultManager] removeItemAtPath:cachePathForKey(_directory, key) error:NULL];
      }
      
      [_cacheInfo removeAllObjects];
      
      dispatch_sync(_frozenCacheInfoQueue, ^{
       self.frozenCacheInfo = [_cacheInfo copy];
      });
    
      [self setNeedsSave];
     });
    }
    
    - (void)setCacheTimeoutInterval:(NSTimeInterval)timeoutInterval forKey:(NSString*)key {
     NSDate* date = timeoutInterval > 0 ? [NSDate dateWithTimeIntervalSinceNow:timeoutInterval] : nil;
     
     // Temporarily store in the frozen state for quick reads
     // frozenCacheInfo        ,      
     dispatch_sync(_frozenCacheInfoQueue, ^{
      NSMutableDictionary* info = [self.frozenCacheInfo mutableCopy];
      
      if(date) {
      //       ,         
       info[key] = date;
      } else {
      //        ,          
       [info removeObjectForKey:key];
      }
      
      self.frozenCacheInfo = info;
     });
     
     // Save the final copy (this may be blocked by other operations)
     dispatch_async(_cacheInfoQueue, ^{
      if(date) {
       _cacheInfo[key] = date;
      } else {
       [_cacheInfo removeObjectForKey:key];
      }
      
      dispatch_sync(_frozenCacheInfoQueue, ^{
       self.frozenCacheInfo = [_cacheInfo copy];
      });
    
      //              EGOCache.plist
      [self setNeedsSave];
     });
    }
    
    - (void)setNeedsSave {
      //          
     dispatch_async(_cacheInfoQueue, ^{
      if(_needsSave) return;
      _needsSave = YES;
      
      double delayInSeconds = 0.5;
      dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
      dispatch_after(popTime, _cacheInfoQueue, ^(void){
       if(!_needsSave) return;
       [_cacheInfo writeToFile:cachePathForKey(_directory, @"EGOCache.plist") atomically:YES];
       _needsSave = NO;
      });
     });
    }
    

    좋은 웹페이지 즐겨찾기