美文网首页
iOS-OC-分类 -objc_getClass 和object

iOS-OC-分类 -objc_getClass 和object

作者: 洧中苇_4187 | 来源:发表于2020-04-29 18:05 被阅读0次

一.假设有如下继承关系

1. @interface MJStudent : MJPerson    //MJStudent.m
2. @interface MJStudent (Test) //MJStudent+Test.m

首先要知道的事,当类和分类中有同样方法的时候,也就是MJStudent 和 MJStudent+Test,优先调用分类的方法.
下面通过源码进行验证:
runtime源码 找到objc4对应的目录下载,你值得拥有

二. 接下来根据源码一步步往里面探究(说明:我下载的runtime版本是objc4-779.1,不同版本行号可能有出入)
1>首先进入objc-os.mm这个文件,别问我怎么知道的,看到MJ老是视频,找到void _objc_init(void)这个方法,根据这个方法上面的介绍可以看到,这个模块是由dyld引导初始化的方法

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time

根据map_images这个引用点击去(函数前面提供了行号,便于后来的小伙伴自己检验)

928: _dyld_objc_notify_register(&map_images, load_images, unmap_image);

2>跟着下面的代码继续,这个方法点进去只有声明,在Xcode左上角搜索,发现实现在objc-os.mm这个文件

2942: return map_images_nolock(count, paths, mhdrs);

3>继续跟进

592: _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
此时我要截个图 image.png

跟着这个方法往下滑,直到你看到下面这个:

3519: objc::unattachedCategories.addForClass(lc, cls);

这个方法翻译过来就是,没有附加分类的方法.添加到某个类,不用多说,添加的这个类在本文就是就是MJStudent,也就是分类继承的那个类

3>addForClass(lc, cls),继续点下去,

1127: attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
话不多少继续跟进,mlists是需要添加的方法数组,mcount是需要添加的方法个数 image.png 4>核心代码 将原来类中的方法数组往后挪,然后把分类的插在前面,对应到代码就是 把array 这个数组扩大到 count+addedCount那么大,然后将array原来的数组内容总数放到最后,也就是count个,最后把addedLists里面的放到array最前面. image.png

附送一份分类的结构体

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);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};

三.通过代码验证,在viewController写如下方法

//打印类的所有方法
- (void)printAllMethodWithCategory:(Class )cls{
    unsigned int count;
    NSMutableString *methodStr = [[NSMutableString alloc]init];
    Method *methodList = class_copyMethodList(cls, &count);
    
    for (int i = 0; i<count; i++) {
         Method method = methodList[i];
        SEL sel = method_getName(method);
        NSString *methodTempStr = NSStringFromSelector(sel);
        [methodStr appendString:methodTempStr];
        [methodStr appendString:@", "];
    }
    NSLog(@"%@",methodStr);
    free(methodList);    
}

四.有没有人和我有同样的疑问:
runtime里面挖一挖 根据这个结构体我看不出它竟然可以当做数组用??????
解答:Method *methodList = class_copyMethodList(cls, &count);这个返回值是一个指针,我们知道数据是可以根据下标来取值的,指针跟数组一样,可以像数组一样访问.

typedef struct method_t *Method;  Method的定义

struct method_t { 结构体Method的定义
    SEL name;
    const char *types;
    MethodListIMP imp;
...
}

打印MJStudent这个类

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    Class metaCls = object_getClass([MJStudent class]);
    
    [self printAllMethodWithCategory:metaCls];
}

结果如下

2020-04-29 16:36:42.378382+0800 categoryTest[64560:2772078] initialize, load, initialize, load,

有两个load方法 和 initialize,也就是说前面是分类的,后面是元类的,

+load方法在代码初始化的时候由操作系统主动调用,只调用一次;
+initialize方法在当前类给它第一次发送消息的时候调用,如果子类父类同样实现,则会调用多次;

2020-04-29 15:07:08.739100+0800 categoryTest[64560:2772078] --+[MJPerson load]--
2020-04-29 15:07:08.739958+0800 categoryTest[64560:2772078] --+[MJStudent load]--
2020-04-29 15:07:08.740078+0800 categoryTest[64560:2772078] --+[MJStudent(Test) load]--
2020-04-29 15:07:12.276934+0800 categoryTest[64560:2772078] --+[MJPerson initialize]--
2020-04-29 15:07:12.277105+0800 categoryTest[64560:2772078] --+[MJStudent(Test) initialize]--

通过源码也可以验证,以下我直接贴出位置,方便读者验证,在objc-loadmethod.mm文件,可以发现是直接调用方法,

+load.

204: (*load_method)(cls, @selector(load));

load方法最初由call_load_methods调起,这个方法的注释比较重要,贴出来

* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first. 
* Category +load methods are not called until after the parent class's 
  • initialize(在objc-initialize.mm这个文件,可以看到是通过objc_msgSend,缓存查找->子类查找-> 父类查找->元类查找->...)
382:
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

五. 在打印方法的时候把objc_getClassobject_getClass傻傻分不清楚,查看网上注释如下,

1:Class objc_getClass(const chat *aClassName)
1> 传入字符串类名
2> 返回对应的类对象

2. Class object_getClass(id obj)
1> 传入的obj可能是instance对象,class对象、meta-class对象
2> 返回值
a:如果是instance对象,返回class对象
b:如果是class对象,返回meta-class对象
c:如果是meta-class对象,返回NSObject(基类)的meta-class对象

3:- (class)class、+(class)class
1>返回的就是类对象

不如自己主动验证一波,反正有源码--------->下面直接贴结果
objc-runtime.mm objc_getClass,下面这个就是返回的Class,继续跟进,

6843 : result = getClassExceptSomeSwift(name);
1607:  Class result = getClass_impl(name);
1590: // Try runtime-allocated table
    Class result = (Class)NXMapGet(gdb_objc_realized_classes, name);

跟进到最后: 通过一个table表,去查,
没有找到特别有说服力的证据,但是这个方法设定是,传一个字符串,返回对应的类,如果传入一个不存在的类,返回null,各位看官可以自行验证.

if (isEqual(table, pair->key, key)) {
    *value = (void *)pair->value;
    return (void *)pair->key;

objc-class.mm文件
逻辑: 传入类对象->返回类,传入类->返回元类,传入元类->返回NSObject,通过isa指针往上层找

179:
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

这个Method 接收一个数组methodList,怎么methodList[I]取出来,还是一个Method( 禁止套娃 )

Method *methodList = class_copyMethodList(cls, &count);
    for (int i = 0; i<count; i++) {
         Method method = methodList[I];

而class_copyMethodList()这个方法列表的实现:-->把重要代码贴出来,依然困惑,

Method *class_copyMethodList(Class cls, unsigned int *outCount){
old_method_list *mlist;
Method *result = nil;
...
if (count > 0) {
        result = (Method *)malloc((count+1) * sizeof(Method));
        iterator = nil;
        while ((mlist = nextMethodList(cls, &iterator))) {
            int i;
            for (i = 0; i < mlist->method_count; i++) {
                result[m++] = (Method)&mlist->method_list[I];  这似乎是个数组
            }
        }
    }
...
    return result;
}

总结

----最近脑子里时长想到一部电视剧的画面--<<宝莲灯>>
三圣母的儿子沉香 和 他的舅舅 二郎神 打架时候的场景,沉香一心救母,苦练武技,觉得自己已经很刻苦,但是在二郎神面前不堪一击,边打二郎神边对沉香说,你老是觉得自己厉害,每次练功不尽全力,觉得差一点点没大碍,跟我过招每次都差一点点,所以你永远都救不了你妈.
做事儿,搞技术也是一样,不管是多小的点,动脑子搞清楚,别人家说什么是什么,最好自己验证,拓展,这样才能成为你的东西,总而言之一句话,事无巨细,认真对待,在你的能力范围内做到最好.
[找到了视频](https://b23.tv/Zfgj5G)

相关文章

网友评论

      本文标题:iOS-OC-分类 -objc_getClass 和object

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