[Edward 연금방] MJextension 읽기 노트.

7939 단어
앞에 쓴다: 이 시리즈는 단지 개인의 독서 노트일 뿐 너무 많은 설명과 분석을 하지 않는다.

지식 포인트:


설계에서 전환 효율을 높이기 위해 캐시를 대량으로 사용했는데 프로젝트에서 캐시를 사용하는 방안은 두 가지가 있다.

  • 관련 대상의 방법을 이용하여 클래스 대상에 직접 관련 대상을 추가한다.예를 들어 저자는 MJProperty류에서 이 기교를 사용했다.같은 속성objc_property_t에서 생성된 MJProperty 대상의 내용이 모두 같기 때문에 저자는 이를 캐시하여 중복된 실행 생성 논리를 피했다.저자는 속성property을 교묘하게 이용key했다.속성은 구조체 포인터입니다.클래스의 속성 주소는 컴파일러에 의해 결정되고 유일합니다.그것은 key의 역할을 잘 충당할 수 있다.
  • + (instancetype)cachedPropertyWithProperty:(objc_property_t)property
    {
        MJProperty *propertyObj = objc_getAssociatedObject(self, property);
        if (propertyObj == nil) {
            propertyObj = [[self alloc] init];
            propertyObj.property = property;
            objc_setAssociatedObject(self, property, propertyObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }
        return propertyObj;
    }
    
  • 정적 사전 대상을 사용하여 +initialize 또는 +load에서 정적 사전 대상을 초기화합니다.저자는 이 방안으로 서로 다른 종류의 정보를 캐시하는데, 이 사전의 key 은 클래스 이름 문자열이고, value 는 그 내용이다.마찬가지로 NSObject+MJClass.h에 속성이 필요한 흑백 명단을 저장하면 백명단의 속성은 사전과 모델의 전환을 하고 흑명단의 속성은 무시된다.화이트리스트는 모두 전환에 참여한다는 뜻이다.
  • // 
    static NSMutableDictionary *allowedPropertyNamesDict_;
    static NSMutableDictionary *ignoredPropertyNamesDict_;
    
    @implementation NSObject (MJClass)
    
    + (void)load
    {
        allowedPropertyNamesDict_ = [NSMutableDictionary dictionary];
        ignoredPropertyNamesDict_ = [NSMutableDictionary dictionary];
    }
    
    #pragma mark -  
    + (void)mj_setupAllowedPropertyNames:(MJAllowedPropertyNames)allowedPropertyNames;
    {
        NSMutableArray *array = allowedPropertyNamesDict_[NSStringFromClass(self)];
        if (array) return array;
        
        //  、 
        allowedPropertyNamesDict_[NSStringFromClass(self)] = array = [NSMutableArray array];
        
        ....
    
        return array;
        
    }
    
    + (NSMutableArray *)mj_totalAllowedPropertyNames
    {
        NSMutableArray *array = ignoredPropertyNamesDict_[NSStringFromClass(self)];
        if (array) return array;
        
        //  、 
        ignoredPropertyNamesDict_[NSStringFromClass(self)] = array = [NSMutableArray array];
        
        ....
    
        return array;
    }
    
    @end
    

    매크로 정의를 사용하여 클래스를 압축하는 역할을 합니다


    저자는 매크로 MJExtensionCodingImplementation 를 썼습니다. 압축 파일을 지원하려면 실현 함수에 이 매크로를 추가하면 됩니다.
    @implementation MJBag
    // NSCoding 
    MJExtensionCodingImplementation
    
    @end
    

    작가님은 어떻게 하셨을까요?우리는 이 거대한 것을 펼치기만 하면 알 수 있다.이 매크로는 우리- (id)initWithCoder:(NSCoder *)decoder- (void)encodeWithCoder:(NSCoder *)encoder 방법을 실현시켰다.이 두 가지 방법 중, 각각 저자가 쓴 인코딩과 디코딩 방법을 호출하였으며, 방법에서 모든 속성을 두루 훑어보며 조작하였다.
    #define MJCodingImplementation \
    - (id)initWithCoder:(NSCoder *)decoder \
    { \
    if (self = [super init]) { \
    [self mj_decode:decoder]; \
    } \
    return self; \
    } \
    \
    - (void)encodeWithCoder:(NSCoder *)encoder \
    { \
    [self mj_encode:encoder]; \
    }
    
    #define MJExtensionCodingImplementation MJCodingImplementation
    

    속성에 대한 문자열 설명(the attribute string of a property)


    우리는 runtime 라이브러리const char *property_getAttributes(objc_property_t property) 방법을 통해 속성의 유형 설명과 속성 이름을 얻을 수 있다.우리는 문자열의 글자 크기 방법 @() 을 사용하여 c 문자열을 NSString 형식으로 바꿀 수 있다.
     :T +   +   + V +      
    

    공식 연결: Property Type String, Type Encodings

    부족한 점:


    존재하는 다중 루틴 문제

    MJExtension는 다중 루틴의 전환을 지원하지 않는 것 같아서 원본 코드를 읽는 과정에서 두 개의 다중 루틴 문제를 발견했다.
  • 다중 스레드 문제1 나는 이 장면을 구성하여 두 스레드가 같은 모델을 동시에 전환하지만 사용하는 사전은 같지 않다.코드는 아래와 같습니다. 보기 편리하도록 // 후에 인쇄 결과를 제시했습니다.
  • - (void)testMutilThread1
    {
        dispatch_async(dispatch_queue_create(NULL, NULL), ^{
            NSDictionary *dict = @{
                                   @"name":@"name",
                                   @"price":@(2)
                                   };
            [MJBag mj_setupReplacedKeyFromPropertyName:^NSDictionary *{
                return @{
                         @"price":@"price"
                         };
            }];
            MJBag *bag =  [MJBag mj_objectWithKeyValues:dict];
            NSLog(@"%@",bag);        // name:name,price:0.000000
        });
        
        dispatch_async(dispatch_queue_create(NULL, NULL), ^{
            NSDictionary *dict = @{
                                   @"name":@"name",
                                   @"price2":@(3)
                                   };
            [MJBag mj_setupReplacedKeyFromPropertyName:^NSDictionary *{
                return @{
                            @"price":@"price2"
                         };
            }];
            MJBag *bag =  [MJBag mj_objectWithKeyValues:dict];
            NSLog(@"%@",bag);      //name:name,price:3.000000
        });
    }
    

    우리는 첫 번째 대상의 결과가 틀렸다는 것을 볼 수 있다.그 이유는 같은 종류의 속성과 사전의 映射 관계표가 프로그램에 한 부만 존재하기 때문이다.우리는 첫 번째 라인에서 映射 관계를 설정하고 실행 변환을 실행하는 사이에 두 번째 라인에서 映射 관계를 다시 설정하였는데, 이로 인해 첫 번째 사전이 두 번째 사전의 映射 관계를 사용하여 변환이 잘못되었다.
  • 다중 스레드 문제2 우리는 여러 스레드가 같은 사전 대상을 동시에 수정할 때 자물쇠를 채워야 한다는 것을 안다.그러나 나는 코드 여러 곳에서 문제를 발견했는데, 특히 캐시 사전의 조작에 있어서.나는 작가의 자물쇠가 위치를 잘못 추가했는지 의심스럽다. 작가는 사전의 획득에만 자물쇠를 추가했다. 사전이 만들어지기 전에 이미 완성되었기 때문에 다중 루틴 접근은 문제가 없는 것 같다.이 문제에 대해 나는 실험을 하지 않았고 현재 코드에 대한 분석 차원에 있다.

  • 하나의 모델은 여러 사전의 효율이 떨어지는 문제에 대응한다


    앞서 말했듯이 저자는 사전을 캐시로 사용하여 모델 사전의 전환 효율을 가속화시킨다.그 중 중요한 캐시 중 하나는 클래스의 속성 목록과 사전과의 映射 관계이다.저자는 사전을 캐시 용기로 사용하는데, 종류마다 캐시 데이터가 하나밖에 없다.이로 인해 하나의 모델이 서로 다른 위치에 사용되어야 하고, 서로 다른 사전을 사용하여 변환해야 한다. 그러면 매번 변환할 때마다 맵 관계를 다시 설정해야 하기 때문에 캐시가 효력을 잃게 된다.더 심각한 것은 작가가 캐시를 일률적으로 정리하는 방식을 채택했다는 것이다.맵 관계를 설정하는 코드를 살펴보겠습니다.하나의 클래스가 맵 관계를 다시 설정하면 모든 캐시가 정리됩니다.이것은 결코 총명한 방법이 아니다.
    + (void)mj_setupReplacedKeyFromPropertyName:(MJReplacedKeyFromPropertyName)replacedKeyFromPropertyName
    {
        // 
        [self mj_setupBlockReturnValue:replacedKeyFromPropertyName key:&MJReplacedKeyFromPropertyNameKey];
        // 
        [[self dictForKey:&MJCachedPropertiesKey] removeAllObjects];
    }
    

    의심스럽다


    서로 다른 분류에는 같은 유방명이 있는데, 왜 충돌하는 상황이 나타나지 않습니까?


    뒤에 runtime의 원본을 찾아서 원인을 찾아라.
    @implementation NSObject (MJClass)
    
    + (NSMutableDictionary *)dictForKey:(const void *)key
    {
        @synchronized (self) {
            if (key == &MJAllowedPropertyNamesKey) return allowedPropertyNamesDict_;
            if (key == &MJIgnoredPropertyNamesKey) return ignoredPropertyNamesDict_;
            if (key == &MJAllowedCodingPropertyNamesKey) return allowedCodingPropertyNamesDict_;
            if (key == &MJIgnoredCodingPropertyNamesKey) return ignoredCodingPropertyNamesDict_;
            return nil;
        }
    }
    
    ......
    
    @end
    
    @implementation NSObject (Property)
    
    + (NSMutableDictionary *)dictForKey:(const void *)key
    {
        @synchronized (self) {
            if (key == &MJReplacedKeyFromPropertyNameKey) return replacedKeyFromPropertyNameDict_;
            if (key == &MJReplacedKeyFromPropertyName121Key) return replacedKeyFromPropertyName121Dict_;
            if (key == &MJNewValueFromOldValueKey) return newValueFromOldValueDict_;
            if (key == &MJObjectClassInArrayKey) return objectClassInArrayDict_;
            if (key == &MJCachedPropertiesKey) return cachedPropertiesDict_;
            return nil;
        }
    }
    
    ......
    
    @end
    

    총결산

  • MJExtension는 캐시를 사용하여 전환 효율을 가속화시켰다. 두 가지 캐시 방법을 사용했다. 첫 번째는 관련 대상을 사용하는 방법이고 두 번째는 정적 사전을 사용하는 방법이다.
  • MJExtension宏 정의를 사용하여 클래스를 압축하는 역할을 한다.
  • MJExtension 속성의 문자열 설명(the attribute string of a property)을 사용하여 속성의 유형을 얻기;
  • 다중 노드의 사용MJExtension을 피하고 문제가 발생하지 않도록 권장한다.
  • 캐시를 정리하는 방법+mj_setupReplacedKeyFromPropertyName:, +mj_setupReplacedKeyFromPropertyName121: 등을 최대한 많이 사용하지 마세요.이 방법을 사용하면 모든 종류의 캐시를 정리해서 전환 효율을 떨어뜨릴 수 있습니다.
  • 좋은 웹페이지 즐겨찾기