Category同名函数如何调用原来实现

Category中是原生开发中经常用到的一个技术实现,利用这一技术可以在不知道原始类实现的情况的情况下未类添加属性和方法实现,可以很好对类功能进行扩展.本文主要讨论两个问题:

  • Category中的实现如何附加到对应类中;
  • Category中被"覆盖的方法",如何能调用到原始实现.

Category加载

OC最重要的特性就是运行时,而Category之所以与众不同就在于其加载也是在运行时.在程序加载时内核会通过dyld将machO文件加载到内存进行解析,然后通过_objc_init来进行应用的初始化.

_objc_init

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();
    //调用C++静态构造函数
    static_init();
    //这个函数的实现是空,但是根据名称可以判断应该是初始化一些锁操作
    lock_init();
    //初始化异常捕获:当执行出现异常时调用对应的异常函数
    exception_init();
    
    //注册函数通知(镜像的映射,加载,解除映射),在这里主要看map_images的实现
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

_dyld_objc_notify_register

map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{

    ...
    ...
//在进行了一些列的解析操作开始读取镜像
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;

}

_read_images

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{

    ...
    ...

    // Discover categories. 
    for (EACH_HEADER) {
        //获取到当前(image)hi中的所有category列表
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            //遍历category列表中的所有category
            category_t *cat = catlist[i];
            //返回cls的实时类指针,如果由于弱链接而忽略cls,则返回nil。
            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. 
            /*
            处理当前分类:
            1. 将当前分类与其对应的类进行注册;
            2. 重建类中的方法列表(rw->methodList)
*/
            bool classExists = NO;

            //处理当前分类中的对象实现(实例方法,实例属性以及协议)
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    printf("%s已经初始化\n", class_getName(cls));
                    remethodizeClass(cls);
                    classExists = YES;
                } else {
                    printf("%s尚未初始化\n", class_getName(cls));
                }
                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");
...
...

}

addUnattachedCategoryForClass

static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
                                          header_info *catHeader)
{
    runtimeLock.assertLocked();

    //获取全局静态变量cats:该散列表主要维护class与categoty的对应关系,每个class对应了一个list
    NXMapTable *cats = unattachedCategories();
    category_list *list;
  
    //获取当前类对应的category列表
    list = (category_list *)NXMapGet(cats, cls);
    
    if (!list) {
    //如果当前类对应的category列表不存在就初始化一个
        list = (category_list *)
            calloc(sizeof(*list) + sizeof(list->list[0]), 1);
    } else {
    //如果当前类对应的category列表存在就重新扩容
        list = (category_list *)
            realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
    }
    //将新的category添加在列表的后端
    list->list[list->count++] = (locstamped_category_t){cat, catHeader};
    //插入当前新的list
    NXMapInsert(cats, cls, list);
}

remethodizeClass

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();
    //判断当前类是不是元类
    isMeta = cls->isMetaClass();

    // 获取需要附加到对应类中的Category列表
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        //开始附加Category列表到当前类中
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}

//从全局散列表中获取到当前类对应的Category列表,并将该Category列表从全局散列表中删除
static category_list *
unattachedCategoriesForClass(Class cls, bool realizing)
{
    runtimeLock.assertLocked();
    return (category_list *)NXMapRemove(unattachedCategories(), cls);
}

 

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

   //重新排列Categoty列表中的Category顺序
    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));

    // 通过逆向遍历可以获取到最新的类别:所以最后编译的类别是在类别方法列表的前端的
    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

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            
            // 当前类的列表中的数量已经大于1: 多个列表->多个列表
            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: 0个列表 -> 一个列表
            list = addedLists[0];
        } 
        else {
            // 当前类的列表中的数量等于1: 1个列表 -> 多个列表
            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]));
        }
    }

根据分析,可以看出:

  • category中的方法会出现在原始类方法列表的前边;
  • 多个category中方法,最后编译的方法会出现在在它之前编译的方法之前;
  • category中的同名方法并没有覆盖原来的方法实现,而是出现在方法数组的前边.换句话来说,category中的同名方法只是优先级别原来的方法高,在方法调用时优先被调用.

Category中出现同名方法时,如何调用原来的方法实现?

由于Category中的同名方法并没有覆盖原来的方法,所以是依然可以查找到原始方法的实现的;由于Category中的同名方法比原来的实现优先级高,所以在正常情况下原始实现是不会被调用到的.如果在Category中重写本类方法,那么应该如何调用到原来的方法实现呢?这里只对仅有一个分类的情况做讨论:

@interface Man : NSObject
- (void)hello;
@end
@implementation Man
- (void)hello {
    NSLog(@"Man[%s]", __FUNCTION__);
}
@end


@interface Student : Man
- (void)sayHello;
@end
@implementation Student
- (void)sayHello {
    NSLog(@"Student[%s]", __FUNCTION__);
}
@end
  • Category和其对应的类本类同时实现了同名方法:
  • @interface Student (Category)
    
    @end
    
    @implementation Student (Category)
    
    -(void)sayHello {
        NSLog(@"Student (Category)(%s)", __FUNCTION__);
    }
    
    @end
    

    那么这时可以遍历类的方法的列表,查找到位于最后一个位置的方法就是原始的方法实现:

  •     Student *stu = [[Student alloc] init];
        //[stu sayHello]; 由于Category同名方法会在原始方法之前,所以直接调用就会调用到Category中的实现
        unsigned int outCount = 0;
        Method *methods = class_copyMethodList([Student class], &outCount);
        for(unsigned int index = outCount; index ; index--) {
            Method method = methods[index];
            if (strcmp(sel_getName(method_getName(method)), "sayHello") == 0 ) {
                void(*imp)(id, SEL) = (void(*)(id, SEL))method_getImplementation(method);
                imp(stu, @selector(sayHello));
                break;
            }
        }

     

  • Category和其对应的类本类中的某个父类同时实现了同名方法:
@interface Student (Category)

@end

@implementation Student (Category)
- (void)hello {
    NSLog(@"Student (Category)(%s)", __FUNCTION__);
}

@end

这时在Category对应的本类中并没有实现同名方法,所以在本类的方法列表中查找不到对应的方法,于是可以直接调用父类的实现.

    Student *stu = [[Student alloc] init];
    [stu hello]; //直接进行方法调用会调用到Category中的方法实现
    //由于Category对应的本类(Student)中并没有实现hello方法,所以可以使用父类中的方法实现
    struct objc_super superObj = {
        .receiver = stu,
        .super_class = class_getSuperclass([Student class]),
    };
    ((void(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superObj, @selector(hello));

综合两种情况,可以将代码进行合并:

    Student *stu = [[Student alloc] init];
    unsigned int outCount = 0;
    SEL sel = sel_registerName("sayHello");
    typedef void(*MethodImplementation)(id, SEL);
    MethodImplementation imp = NULL;
    Method *methods = class_copyMethodList([Student class], &outCount);
    
    unsigned int methodCount = 0;
    for(unsigned int index = outCount; index ; index--) {
        Method method = methods[index];
        SEL methodSEL = method_getName(method);
        if (methodSEL && methodSEL == sel) {
            methodCount++;
            if (!imp) {
                imp = (MethodImplementation)method_getImplementation(method);
            }
        }
    }
    if (methodCount != 2) {
    //这个判断只适用于当前类(Student)仅有一个Category,且Category中包含同名方法(与本类或者本类父类存在同名方法)的判断
        imp = NULL;
    }
    if (imp) {
        imp(stu, sel);
    } else {
        struct objc_super superObj = {
            .receiver = stu,
            .super_class = class_getSuperclass([Student class]),
        };
        ((void(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superObj, sel);
    }

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值