iOS:Category

作者: 码小菜 | 来源:发表于2019-12-11 10:27 被阅读0次

    目录
    一,作用
    二,本质
    三,同名方法调用优先级

    一,作用

    1,给系统类或第三方类添加属性

    • 因为无法修改系统类或第三方类的代码,所以只能利用分类进行添加
    • 因为在分类中不能添加成员变量,所以系统不会自动生成带下划线的成员变量和get/set方法
    • 如果想正常使用分类中的属性,那必须利用runtime的关联函数来手动实现get/set方法
    @interface UIView (Add)
    @property (nonatomic, copy) NSString *name;
    @end
    
    @implementation UIView (Add)
    - (NSString *)name {
        return objc_getAssociatedObject(self, @"name");
    }
    - (void)setName:(NSString *)name {
        objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    @end
    

    2,给系统类或第三方类添加方法

    • 因为无法修改系统类或第三方类的代码,所以只能利用分类进行添加
    @interface UIView (Add)
    - (void)removeAllSubviews;
    @end
    
    @implementation UIView (Add)
    - (void)removeAllSubviews {
        [self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
            [obj removeFromSuperview];
        }];
    }
    @end
    

    3,拆分自定义类的代码

    • 如果某类的代码量比较大,可以将代码按照功能拆分到各个分类中
    @interface ApiRequest : NSObject
    @end
    
    @interface ApiRequest (Login)
    @end
    
    @interface ApiRequest (Home)
    @end
    
    @interface ApiRequest (My)
    @end
    
    二,本质
    @interface Person (Add) <NSCopying>
    @property (nonatomic, assign) NSInteger age;
    - (void)eat;
    + (void)run;
    @end
    
    @implementation Person (Add)
    - (void)eat {
        NSLog(@"eat");
    }
    + (void)run {
        NSLog(@"run");
    }
    @end
    

    将上述代码用clang转为C++代码,Person (Add)的底层代码如下,可以看到分类的本质是结构体

    // 底层结构
    struct _category_t {
        const char *name;                              // 类名
        struct _class_t *cls;                          // 指向类的指针
        const struct _method_list_t *instance_methods; // 实例方法列表
        const struct _method_list_t *class_methods;    // 类方法列表
        const struct _protocol_list_t *protocols;      // 协议列表
        const struct _prop_list_t *properties;         // 属性列表
    };
    
    // 用_category_t实例化Person (Add)
    static struct _category_t _OBJC_$_CATEGORY_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "Person",
        0, // &OBJC_CLASS_$_Person,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Add,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Add,
        (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Add,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Add, 
    };
    
    // Person (Add)的实例方法列表
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[1];
    } _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"eat", "v16@0:8", (void *)_I_Person_Add_eat}}
    };
    
    // Person (Add)的类方法列表
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[1];
    } _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        1,
        {{(struct objc_selector *)"run", "v16@0:8", (void *)_C_Person_Add_run}}
    };
    
    // Person (Add)的协议列表
    static struct /*_protocol_list_t*/ {
        long protocol_count;  // Note, this is 32/64 bit
        struct _protocol_t *super_protocols[1];
    } _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        1,
        &_OBJC_PROTOCOL_NSCopying
    };
    
    // Person (Add)的属性列表
    static struct /*_prop_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count_of_properties;
        struct _prop_t prop_list[1];
    } _OBJC_$_PROP_LIST_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_prop_t),
        1,
        {{"age","Tq,N"}}
    };
    

    注意:因为_category_t中没有成员变量列表,所以在分类中不能添加成员变量

    三,同名方法调用优先级
    • 原类与分类:优先调用分类的
    • 多个分类:优先调用后编译的(编译顺序从上往下)

    1, 代码验证

    编译顺序
    // Person
    @interface Person : NSObject
    - (void)eat;
    @end
    
    @implementation Person
    - (void)eat {
        NSLog(@"Person eat");
    }
    @end
    
    // Person (Add1)
    @interface Person (Add1)
    - (void)eat;
    @end
    
    @implementation Person (Add1)
    - (void)eat {
        NSLog(@"Person (Add1) eat");
    }
    @end
    
    // Person (Add2)
    @interface Person (Add2)
    - (void)eat;
    @end
    
    @implementation Person (Add2)
    - (void)eat {
        NSLog(@"Person (Add2) eat");
    }
    @end
    
    // 使用
    Person *person = [Person new];
    [person eat];
    
    // 打印
    Person (Add1) eat
    

    2,源码分析(源码下载地址

    • 方法一:重新组织类中的方法
    static void remethodizeClass(Class cls)
    {
        category_list *cats;
        bool isMeta;
    
        runtimeLock.assertLocked();
    
        isMeta = cls->isMetaClass();
    
        // Re-methodizing: check for more categories
    
        // 获取所有的分类
        if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
            if (PrintConnecting) {
                _objc_inform("CLASS: attaching categories to class '%s' %s", 
                             cls->nameForLogging(), isMeta ? "(meta)" : "");
            }
            // 方法二
            attachCategories(cls, cats, true /*flush caches*/);        
            free(cats);
        }
    }
    
    • 方法二:将分类中的方法附加到原类中
    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);
    }
    
    • 方法三:将分类的方法列表数组附加到原类的方法列表数组中
    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]));
        }
    }
    
    • 上述三个方法的逻辑图
    步骤一 步骤二 步骤三

    3,注意点

    • 分类是在运行期合并到原类中的

    • 分类方法并没有覆盖原类方法,只是放在原类方法的前面,而方法调用是顺序查找,所以优先调用分类方法

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        [self logMethodNamesWithClass:[Person class]];
    }
    
    // 打印类对象或元类对象中的方法名
    - (void)logMethodNamesWithClass:(Class)cls {
        unsigned int count;
        Method *methodList = class_copyMethodList(cls, &count);
        NSMutableString *methodNames = [NSMutableString string];
        for (int i = 0; i < count; i++) {
            Method method = methodList[i];
            SEL selector = method_getName(method);
            NSString *methodName = NSStringFromSelector(selector);
            [methodNames appendString:methodName];
            [methodNames appendString:@", "];
        }
        free(methodList);
        NSLog(@"%@---%@", cls, methodNames);
    }
    
    // 打印
    Person---eat, eat, eat,
    

    相关文章

      网友评论

        本文标题:iOS:Category

        本文链接:https://www.haomeiwen.com/subject/fwqbgctx.html