• extension(类扩展)和 category(类别)


    extension(类扩展)
    
    
    
    
    简单来说,extension在.m文件中添加,所以其权限为private,所以只能拿到源码的类添加extension。另外extension是编译时决议,和interface和implement里的代码融合在一块了一般。
    category(类别)
    
    category能在不继承类的情况下给类动态添加方法。
    1、创建category
    
    
    
    
    关于@dynamic的特性及用法可参考:
    https://blog.csdn.net/qq_28446287/article/details/79094491
    2、category的优缺点
    
        可以将类的实现代码分散到多个不同的文件或框架中
        
    
    
        创建对私有方法的前向引用
        OC语法中,你不能对一个类的方法定义为private,只有+、-,对实例变量可以private、public。
        具体可参考此文档http://www.cnblogs.com/stevenwuzheng/p/5457487.html
    
        向对象添加非正式协议
        对NSObject进行一个类别叫做非正式协议,可以只实现想要的方法
    
    3、category在runtime中的源码
    
    typedef struct objc_category *Category;
    
        1
    
    struct objc_category 
    {
        //类别的名字
        char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
        //该类的名字
        char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
        //实例方法列表
        struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
        //类方法列表
        struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
        //协议列表
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    }                                                            OBJC2_UNAVAILABLE;
    
    
    明显看出,Category没有容纳变量的地方。
    4、category的原理
    
    objective-c的运行依赖runtime,runtime依赖于dyld动态加载,查看objc-os.mm文件发现其调用栈如下:
    
    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
    
        // fixme defer initialization until an objc-using image is found?
        environ_init();
        tls_init();
        static_init();
        lock_init();
        exception_init();
    
        // Register for unmap first, in case some +load unmaps something
        _dyld_register_func_for_remove_image(&unmap_image);
        dyld_register_image_state_change_handler(dyld_image_state_bound,
                                                 1/*batch*/, &map_2_images);
        dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
    }
    
    
    category被附加到类上是在map_images的时候发生的。在new-ABI的标准下,_objc_init函数里调用的map_iamges最终会调用objc-runtime-new.mm中的_read_images函数。_read_images中部分代码:
    
     // Discover categories. 
        for (EACH_HEADER) {
            category_t **catlist = 
                _getObjc2CategoryList(hi, &count);
            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  
                    /* ||  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和它的主类(或元类)注册到哈希表中.
    如果主类(或元类)已经实现,那么重建它的方列表。
    
    category中的实例方法和属性被整合到主类中,而类方法被整合到元类中。而协议被同时整合到了主类和元类中。
    
    /***********************************************************************
    * remethodizeClass
    * Attach outstanding categories to an existing class.
    * Fixes up cls's method list, protocol list, and property list.
    * Updates method caches for cls and its subclasses.
    * Locking: runtimeLock must be held by the caller
    **********************************************************************/
    static void remethodizeClass(Class cls)
    {
        category_list *cats;
        bool isMeta;
    
        runtimeLock.assertWriting();
    
        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);
        }
    }
    
    
    
    如注释所说,此函数将未处理的category整合到主类(或元类),整合cls的方法、协议、属性列表,更新cls及其子类的方法缓存。
    
    查看其中的attachCategories函数:
    
    // Attach method lists and properties and protocols from categories to a class.
    // Assumes the categories in cats are all loaded and sorted by load order, 
    // oldest categories first.
    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);
            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);
    }
    
    
    通过while循环,遍历所有的category,得每个category的方法列表mlist、proplist和protolist并存入主类(或元类)的mlists、proplists和protolists中。从而更新类的数据字段data()中mlist、proplist和protolist的值。
    
    category没有替换掉原来类的方法,也就是说如果category和原来类有method1,那么在将category整合到原来类之后,类的method_list会有两个method1
    
    category中的method1会被放在类的method_list前面,而原来类的method1被放 到了method_list后面,在调用时候会先调用method_list前面的,所以看起来是将原来类的method1覆盖了,实际上并不是那么回事。
    5、category的两个面试题
    
    3.1 一个类和它的category同时拥有一个方法,在调用时候调用哪一个?答案参考“2、category的原理”
    
    3.2 一个类有多个category并都拥有同一个方法,在调用时候调用哪一个?答案参考“2、category的原理”
    
    举个例子:
    
    //#import "type.m"
    - (void)test
    {
        NSLog(@"type class!!");
    }
    
    //#import "type+xxxx.m"
    - (void)test
    {
        NSLog(@"category xxxx");
    }
    
    //#import "type+xxxx1.m"
    - (void)test
    {
        NSLog(@"category xxxx1");
    }
    
    //#import "type+xxxx2.m"
    - (void)test
    {
        NSLog(@"category xxxx2");
    }
    
    
    
    
    
    
    
    
    
    可以知道,输出的结果跟compile source文件中的category的.m文件放置顺序有关。且放最底部的时候输出(主类.m文件的放置不影响,理由参考”2、category的原理”)
    6、category动态添加变量
    
    @interface type (xxxx)
    
    @property (nonatomic, assign)  NSInteger number;
    
    @end
    
    
    static void *numberKey = &numberKey;
    
    @implementation type (xxxx)
    
    - (void)setNumber:(NSInteger)number
    {
        objc_setAssociatedObject(self, &numberKey, [NSString stringWithFormat:@"%ld",number], OBJC_ASSOCIATION_ASSIGN);
    
    }
    
    - (NSInteger)number
    {
        return [objc_getAssociatedObject(self, &numberKey) integerValue];
    }
    
    @end
    
    objc_setAssociatedObject和objc_getAssociatedObject的描述可参考:https://www.cnblogs.com/liuting-1204/p/6526342.html
  • 相关阅读:
    Java8学习笔记(五)--Stream API详解[转]
    Java8学习笔记(四)--接口增强
    Java8学习笔记(三)--方法引入
    JAVA8学习笔记(二)----三个预定义接口
    JAVA8学习笔记(一)----Lambda表达式
    Java基础加强总结(三)——代理(Proxy)
    Java基础加强总结(二)——泛型
    mysql统计表的大小
    jquery异步上传图片
    瀑布流
  • 原文地址:https://www.cnblogs.com/Free-Thinker/p/10797048.html
Copyright © 2020-2023  润新知