OC经典面试题class_getInstanceMethod、class_getClassMethod、isKindOfClass

oc中方法存在于类中,类方法存在于元类中

class_getInstanceMethod

//cls 类
Method class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)

04F436F8-EB5C-4797-934C-31589535E469.png

意思是这个class_getInstanceMethod会返回类的实例方法

问题1

LGPerson类

@interface LGPerson : NSObject
- (void)sayHello;
+ (void)sayHappy;
@end

@implementation LGPerson
- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}
+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end

main.m

void lgInstanceMethod_classToMetaclass(Class pClass){
    
    //获取类名
    const char *className = class_getName(pClass);
    //元类
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        lgInstanceMethod_classToMetaclass(pClass);
        NSLog(@"Hello, World!");
    }
    return 0;
}

问:输出情况?

  1. sayHello是实例方法,sayHappy是类方法。
  2. pClass是类,metaClass是元类
    根据原则oc中实例方法存在与类中,类方法存在与元类中
    大致可以推断出:
    sayHello是实例方法在LGPerson类中,所以
  • method1:打印出sayHello的内存地址,
  • method2:NULL
    sayHappy是累方法,在LGPerson的元类中
  • method3:输出是0x00即NULL
  • method4:输出是sayHappy的内存地址

class_getClassMethod

image.png

问题2

void lgClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    //元类
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));

    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    
    LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

问:输出情况?

  1. sayHello是实例方法,sayHappy是类方法。
  2. pClass是类,metaClass是元类
    根据原则oc中实例方法存在与类中,类方法存在与元类中
    输出情况
  • method1:0x00 即nil
  • method2: 0x00 即nil
  • method3: 0x100008148
  • method4: 0x100008148
    sayHello是实例方法,所以class_getClassMethod不论怎么查询都是nil
    sayHappy是类方法,类方法存在于元类中,但是method3和method4都打印了地址,, 这是为什么呢?
    可以查看class_getClassMethod源码
//获取类方法
Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}
//获取元类
Class getMeta() {
     //首先判断本身是否为元类,是就返回本身
     if (isMetaClass()) return (Class)this;
     else return this->ISA();
}
//获取isa
inline Class 
objc_object::ISA() 
{
    ASSERT(!isTaggedPointer()); 
#if SUPPORT_INDEXED_ISA
    if (isa.nonpointer) {
        uintptr_t slot = isa.indexcls;
        return classForIndex((unsigned)slot);
    }
    return (Class)isa.bits;
#else
    return (Class)(isa.bits & ISA_MASK);
#endif
}

通过查看源码发现
class_getClassMethod本质还是调用class_getInstanceMethod,在传入的第一个参数的时候传入的是Class的元类cls->getMeta()
所以method3自然就能打印出来
在getMeta()会判断当前的class是否为元类,如果是就返回本身。
所以method4自身也能打印出来。

isKindOfClass和isMemberOfClass

isKindOfClass分别有类方法和实例方法

  • isKindOfClass:是给定类的实例还是从该类继承的任何类的实例
  • isMemberOfClass:是否给定类的实例
    问:

BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);


BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

+isKindOfClass 和 - isKindOfClass的区别,源码

//类方法
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

通过源码结构isa的走位图


image.png

类方法是获取当前类的元类,不断获取元类父类,与传入的类型进行比较
实例方法是获取当前的类,不断获取父类,与传入的类型进行比较。

实际代码运行并没有调用上述代码,而是通过llvm优化后调用如下方法:

BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    //obj如果是实例对象, obj->getIsa()获取的是类信息
    //obj如果是类对象, obj->getIsa()获取的是元类信息
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }
#endif
    return ((BOOL(*)(id, SEL, Class))objc_msgSend)(obj, @selector(isKindOfClass:), otherClass);
}

id:typedef struct objc_object *id; id是任意类型的objc_object
Class:class是objc_class类型,而objc_class是继承objc_object的。

struct objc_class : objc_object {}
typedef struct objc_class *Class;

所以objc_opt_isKindOfClass的第一个参数obj

  • obj如果是实例对象, obj->getIsa()获取的是类class
  • obj如果是类对象, obj->getIsa()获取的是元类MetaClass

解析1:

 BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]]; 

objc_opt_isKindOfClass的源码分析
传入的参数是[NSObject class],通过obj->getIsa()获取到是NSObject的根元类,所以在第一次for循环的时候,cls是根元类与otherClass根类想比较,则第一次比较不相等。
第二次for循环cls获取其父类,根据isa的走位,根元类的父类是NSObject类,所以第二次循环相等。

   Class cls = obj->getIsa();//根元类,根元类的父类是NSObject
    if (fastpath(!cls->hasCustomCore())) {
        for (Class tcls = cls; tcls; tcls = tcls->superclass) {
            if (tcls == otherClass) return YES;
        }
        return NO;
    }

所以输出YES

解析2

BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];    

isMemberOfClass是判断一个对象是否给定类的实例
所以返回NO

解析3

BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];     

objc_opt_isKindOfClass在根据其源码和isa走位图。
传入的obj是LGPerson的类class,经过obj->getIsa()获取LGPerson元类metaclass,而otherclass是LGPerson的类class,metaclass与class比较所以第一次循环不相等。metaclass获取其父类,因此与LGPerson的类class相比不相等。所以不断循环后不相等
所以返回NO

解析4

BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];    

返回NO

解析5-7

objc_opt_isKindOfClass传入的第一个参数是个对象,经过objc->getIsa()获取的是类class,class与otherclass相比较是相等。
返回yes

解析6-8

返回yes

2020-09-30 17:50:06.470167+0800 KCObjc[80323:9507443]  re1 :1
 re2 :0
 re3 :0
 re4 :0
2020-09-30 17:50:06.471930+0800 KCObjc[80323:9507443]  re5 :1
 re6 :1
 re7 :1
 re8 :1
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271