분류(Category)

12507 단어

역할:


클래스에 새 메서드를 추가할 때 OC는 모두 4가지 메서드를 제공합니다.
  • 클래스에 직접 다시 쓰는 방법, 단점: 클래스의 봉인성을 파괴했다.
  • 계승 재자류를 통해 확장하는 방법, 단점: 비용이 많고 코드의 복잡도 증가
  • 프로토콜을 통해 하나의 유형을 확장하는 방법은 장점이 있다. 이 방식은 코드의 결합도를 크게 낮출 수 있지만 실현에 있어 더욱 복잡할 수 있다.
  • 마지막으로 Category를 사용합니다.

  • 장점과 단점:


    Category의 이점:

  • 하나의 클래스를 바꾸지 않고 존재하는 클래스에 새로운 방법을 추가
  • 소스 코드 없이 프레임 내의 클래스를 확장할 수 있습니다(예: NSString).
  • 개의 파일 볼륨 감소
  • 필요에 따라 다른 Category
  • 를 로드할 수 있음

    Category 단점:

  • 방법의 확장은 하드 인코딩으로 동적 추가할 수 없음
  • Category에서 방법의 우선순위가 원래 클래스의 방법보다 높기 때문에 Category에서 방법은 원래 클래스에서 같은 이름을 가진 방법을 덮어써서 알 수 없는 문제를 일으킬 수 있습니다.
  • 구성원 변수를 추가할 수 없음(안 되는 것이 아니라 복잡할 뿐)
  • 속성을 추가할 수 있지만 Getter와setter 방법을 자동으로 생성하지 않기 때문에 관련 대상을 통해 실현해야 합니다.
  • 같은 종류의 Category에는 이름을 중복할 수 없는 방법이 있다.

  • Category 구현 원리:


    Category 소스: objc-runtime-new.h와objc-runtime-new.mm 두 파일 중

    Category runtime의 정의:

    struct category_t {
        const char *name;
        classref_t cls;
        struct method_list_t *instanceMethods;
        struct method_list_t *classMethods;
        struct protocol_list_t *protocols;
        struct property_list_t *instanceProperties;
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    

    통과:
     struct method_list_t *instanceMethods;
     struct method_list_t *classMethods;
     struct protocol_list_t *protocols;
     struct property_list_t *instanceProperties;
    

    구조체에서 이 네 가지 변수를 보면 분류는 실례 방법, 클래스 방법, 프로토콜과 속성 목록을 추가할 수 있지만 실례 변수를 추가할 수 없다. 왜냐하면 실례 변수에 대응하는 바늘 변수가 저장되어 있지 않기 때문이다.

    Category 로드의 세부 단계 및 호출된 함수:

    _objc_init(void)->map_images(...)->map_images_nolock(...)->_read_images(...)->remethodizeClass(Class cls)->attachCategories(Class cls, category_list *cats, bool flush_caches)->void attachLists(List* const * addedLists, uint32_t addedCount
    

    함수 설명:
  • _objc_init:runtime의 입구 함수로 초기화 작업
  • map_images: 잠금
  • map_images_lock: 모든 클래스의 등록과fixup 작업을 완료하고 초기화 작업과load 클래스를 호출하는 방법도 포함합니다.
  • _read_images: 클래스의 불러오기, 프로토콜의 불러오기, 클래스의 불러오기 등 작업 완료
  • remethodizeClass: 범주를 대상 클래스에 바인딩
  • attachCategories: 클래스의 방법과 속성을 목표 클래스에 귀속시킵니다.
  • attachLists: 목표 클래스의 방법과 분류의 방법을 목록에 넣습니다.

  • 주로 Category 원리에 관련된 세 가지 방법:read_images,attachCategories,attachLists가 보자read_images 함수 원본 코드의 중요한 부분:
    // Discover categories. 
        for (EACH_HEADER) {
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count);
            bool hasClassProperties = hi->info()->hasCategoryClassProperties();
    
            for (i = 0; i < count; i++) {
                category_t *cat = catlist[i];
                Class cls = remapClass(cat->cls);
    
                if (!cls) {
                    // Category's target class is missing (probably weak-linked).
                    // Disavow any knowledge of this category.
                    catlist[i] = nil;
                    if (PrintConnecting) {
                        _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                     "missing weak-linked target class", 
                                     cat->name, cat);
                    }
                    continue;
                }
    
                // Process this category. 
                // First, register the category with its target class. 
                // Then, rebuild the class's method lists (etc) if 
                // the class is realized. 
                bool classExists = NO;
                if (cat->instanceMethods ||  cat->protocols  
                    ||  cat->instanceProperties) 
                {
                    addUnattachedCategoryForClass(cat, cls, hi);
                    if (cls->isRealized()) {
                        remethodizeClass(cls);
                        classExists = YES;
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category -%s(%s) %s", 
                                     cls->nameForLogging(), cat->name, 
                                     classExists ? "on existing class" : "");
                    }
                }
    
                if (cat->classMethods  ||  cat->protocols  
                    ||  (hasClassProperties && cat->_classProperties)) 
                {
                    addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                    if (cls->ISA()->isRealized()) {
                        remethodizeClass(cls->ISA());
                    }
                    if (PrintConnecting) {
                        _objc_inform("CLASS: found category +%s(%s)", 
                                     cls->nameForLogging(), cat->name);
                    }
                }
            }
        }
    
        ts.log("IMAGE TIMES: discover categories");
    
        // Category discovery MUST BE LAST to avoid potential races 
        // when other threads call the new category code before 
        // this thread finishes its fixups.
    
        // +load handled by prepare_load_methods()
    
        if (DebugNonFragileIvars) {
            realizeAllClasses();
        }
    
    
        // Print preoptimization statistics
        if (PrintPreopt) {
            static unsigned int PreoptTotalMethodLists;
            static unsigned int PreoptOptimizedMethodLists;
            static unsigned int PreoptTotalClasses;
            static unsigned int PreoptOptimizedClasses;
    
            for (EACH_HEADER) {
                if (hi->isPreoptimized()) {
                    _objc_inform("PREOPTIMIZATION: honoring preoptimized selectors "
                                 "in %s", hi->fname());
                }
                else if (hi->info()->optimizedByDyld()) {
                    _objc_inform("PREOPTIMIZATION: IGNORING preoptimized selectors "
                                 "in %s", hi->fname());
                }
    
                classref_t *classlist = _getObjc2ClassList(hi, &count);
                for (i = 0; i < count; i++) {
                    Class cls = remapClass(classlist[i]);
                    if (!cls) continue;
    
                    PreoptTotalClasses++;
                    if (hi->isPreoptimized()) {
                        PreoptOptimizedClasses++;
                    }
                    
                    const method_list_t *mlist;
                    if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) {
                        PreoptTotalMethodLists++;
                        if (mlist->isFixedUp()) {
                            PreoptOptimizedMethodLists++;
                        }
                    }
                    if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) {
                        PreoptTotalMethodLists++;
                        if (mlist->isFixedUp()) {
                            PreoptOptimizedMethodLists++;
                        }
                    }
                }
            }
    
            _objc_inform("PREOPTIMIZATION: %zu selector references not "
                         "pre-optimized", UnfixedSelectors);
            _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) method lists pre-sorted",
                         PreoptOptimizedMethodLists, PreoptTotalMethodLists, 
                         PreoptTotalMethodLists
                         ? 100.0*PreoptOptimizedMethodLists/PreoptTotalMethodLists 
                         : 0.0);
            _objc_inform("PREOPTIMIZATION: %u/%u (%.3g%%) classes pre-registered",
                         PreoptOptimizedClasses, PreoptTotalClasses, 
                         PreoptTotalClasses 
                         ? 100.0*PreoptOptimizedClasses/PreoptTotalClasses
                         : 0.0);
            _objc_inform("PREOPTIMIZATION: %zu protocol references not "
                         "pre-optimized", UnfixedProtocolReferences);
        }
    

    여기서 코드는 다음과 같습니다.
     bool classExists = NO;
     if (cat->instanceMethods ||  cat->protocols  
        ||  cat->instanceProperties) 
     {
         //          
         addUnattachedCategoryForClass(cat, cls, hi);
         if (cls->isRealized()) {
             //         
             remethodizeClass(cls);
             classExists = YES;
         }
         if (PrintConnecting) {
             _objc_inform("CLASS: found category -%s(%s) %s", 
                         cls->nameForLogging(), cat->name, 
                         classExists ? "on existing class" : "");
         }
     }
    

    주석이 있는 부분은 중요한 관건이다. 1. 귀속 분류와 목표류를 제거한다.2. 재구성 방법 목록.다음으로는 attachCategories 함수를 살펴보겠습니다.
    static void 
    attachCategories(Class cls, category_list *cats, bool flush_caches)
    {
        if (!cats) return;
        if (PrintReplacedMethods) printReplacements(cls, cats);
    
        bool isMeta = cls->isMetaClass();
    
        // fixme rearrange to remove these intermediate allocations
        method_list_t **mlists = (method_list_t **)
            malloc(cats->count * sizeof(*mlists));
        property_list_t **proplists = (property_list_t **)
            malloc(cats->count * sizeof(*proplists));
        protocol_list_t **protolists = (protocol_list_t **)
            malloc(cats->count * sizeof(*protolists));
    
        // Count backwards through cats to get newest categories first
        int mcount = 0;
        int propcount = 0;
        int protocount = 0;
        int i = cats->count;
        bool fromBundle = NO;
        while (i--) {
            auto& entry = cats->list[i];
    
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            if (mlist) {
                mlists[mcount++] = mlist;
                fromBundle |= entry.hi->isBundle();
            }
    
            property_list_t *proplist = 
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            if (proplist) {
                proplists[propcount++] = proplist;
            }
    
            protocol_list_t *protolist = entry.cat->protocols;
            if (protolist) {
                protolists[protocount++] = protolist;
            }
        }
    
        auto rw = cls->data();
    
        prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
        rw->methods.attachLists(mlists, mcount);
        free(mlists);
        if (flush_caches  &&  mcount > 0) flushCaches(cls);
    
        rw->properties.attachLists(proplists, propcount);
        free(proplists);
    
        rw->protocols.attachLists(protolists, protocount);
        free(protolists);
    }
    

    원본 코드를 보면 목표 클래스에서 확장된 모든 클래스의 속성, 방법, 프로토콜 목록 헤더를 하나의 목록에 꺼내서 attachLists라는 함수에 통일적으로 전달하는 것을 알 수 있다.다음은 attachLists 소스를 살펴보겠습니다.
    void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;
    
        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
    }
    

    여기서 강조하자면oldCount와 newCount는 각각 목표 클래스의 방법, 속성, 프로토콜 목록의 길이와 클래스의 방법, 속성, 프로토콜 목록의 길이를 대표한다.realloc 함수를 통해 메모리를 신청합니다. 여기서 realloc 신청 메모리를 사용하는 몇 가지 상황은 여러분이 직접 찾아보실 수 있습니다. 여기서 설명하지 않겠습니다. Realloc가 메모리를 다시 신청한 후에memmove와memcpy 두 함수를 사용하여 목록 내용을 우리가 다시 신청한 메모리에 넣을 수 있습니다. 여기서 알 수 있듯이 Category의 방법은 목표 클래스를 덮어쓰는 방법이 없습니다.단지 Category의 방법을 목표 클래스 방법의 앞에 두었을 뿐이다. 방법을 호출할 때 만약에 Category와 목표 클래스에 같은 이름이 있다면 시스템은 먼저 앞의 클래스에 넣는 방법을 찾을 것이다. 이것이 바로 Category에서 방법의 우선순위가 목표 클래스보다 높은 방법이다.

    Category 추가 속성:


    원본 코드에서 속성 목록을 가리키는 바늘 변수를 보았지만 시스템은 이 속성에 Getter와setter 함수를 생성하지 않았습니다. 실례 변수를 생성하고 Getter와setter 방법을 생성하는 수단이 필요합니다.

    좋은 웹페이지 즐겨찾기