美文网首页iOS底层原理
类 & isa 经典面试题分析

类 & isa 经典面试题分析

作者: 何必太轻浮 | 来源:发表于2020-09-18 01:58 被阅读0次

    在iOS面试中关于类&isa的面试题有很多,其中有两道题是比较经典的,这里我来解读一下。
    1、class_getInstanceMethod,class_getClassMethod
    2、isKindOfClass 和 isMemberOfClass的区别

    第一题的题目

    @interface MCPerson : NSObject
    {
        NSString *nickName;
    }
    @property (nonatomic,copy) NSString *name;
    -(void)sayHello;
    +(void)sayHappy;
    @end
    
    @implementation MCPerson
    -(void)sayHello{
        NSLog(@"%s",__func__);
    }
    +(void)sayHappy{
        NSLog(@"%s",__func__);
    }
    @end
    
    void mcInstanceMethod_classToMetaclass(Class pClass){
        
        const char *className = class_getName(pClass);
        Class metaClass = objc_getMetaClass(className);
        [[NSObject class] isKindOfClass:[NSObject class]];
        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));
        
        NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
    }
    
    void mcClassMethod_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));    
        
        NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
    }
    
    
    • objc_getMetaClass:获取元类
    • class_getInstanceMethod:获取实例方法
    • class_getClassMethod:获取类方法
      要求给出上述哪些方法存在,哪些方法不存在
    首先看一下打印结果
    pic0

    首先我们必须要了解,实例方法、类方法分别存在哪里

    • 实例方法以实例方法存在类中
    • 类方法以实例方法存在元类中
    • class_getInstanceMethod 苹果官方文档的定义是:如果在传入的类或者类的父类中没有找到指定的实例方法,则返回NULL
    • class_getClassMethod 苹果官方文档的定义是:如果在传入的类或者类的父类中没有找到指定的类方法,则返回NULL
    • 元类也是有父类的哦

    知道这些,我们很清楚的就可以得到第一个函数的答案

    //    类中有该实例方法 1
        Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    //    元类中没有该实例方法  0
        Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
    //    类中没有该实例方法,有这个类方法  0
        Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    //    类方法以实例方法的形式存在元类中  1
        Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    

    第二个函数就有一些疑问了

      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));
    
        NSLog(@"%s-%p-%p-%p-%p-%p",__func__,method1,method2,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();
        }
    

    通过代码我们可以看到,class_getClassMethod在底层也是调用的class_getInstanceMethod方法,由于是元类,在中metaClass是元类,所以直接返回了自己,就相当于是在当前元类中找实例方法,所以找到了方法。

    在元类中

     Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
    

    等价于

     Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    

    总结:

    • class_getInstanceMethod:获取实例方法,如果指定的类或其父类不包含带有指定选择器的实例方法,则为NULL

    • class_getClassMethod:获取类方法,如果指定的类或其父类不包含具有指定选择器的类方法,则为NULL。如果传的类是元类,等价于调用class_getInstanceMethod方法

    • class_getMethodImplementation:获取方法的具体实现,如果未查找到,则进行消息转发,之后再详细解析

    第二题:isKindOfClass 和 isMemberOfClass的区别

    • 1、isKindOfClass可用于判断对象是否是一个类的成员,或者是该派生类的成员
    • 2、isMemberOfClass可用于判断对象是否是当前类的成员

    如下题,推测输出结果

    void isClassEqual(){
        //-----使用 iskindOfClass & isMemberOfClass 类方法
        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
        BOOL re3 = [(id)[MCPerson class] isKindOfClass:[MCPerson class]];       //
        BOOL re4 = [(id)[MCPerson class] isMemberOfClass:[MCPerson class]];     //
        NSLog(@"\n re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
    }
    void isInstanceEqual (){
        //-----使用 iskindOfClass & isMemberOfClass 类方法
        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
        BOOL re7 = [(id)[MCPerson alloc] isKindOfClass:[MCPerson class]];       //
        BOOL re8 = [(id)[MCPerson alloc] isMemberOfClass:[MCPerson class]];     //
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
    }
    

    先看下输出结果


    pic1

    分析源码

    //第一次比较是 获取类的元类 与 传入类对比,再次之后的对比是获取上次结果的父类 与 传入 类进行对比
    + (BOOL)isKindOfClass:(Class)cls {
    //    NSOBject-> isa 根元类
    //    NSObject元类是根元类 根元类的父类是NSObject
    //    NSObject VS NSObject -> NSObject元类 VS NSObject -> NSObject VS NSObject return YES;
    //    MCPerson元类 的父类是NSObject元类
    //    MCPerson VS MCPerson -> MCPerson元类 VS MCPerson -> NSObject元类 VS MCPerson ---- return NO
        for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    //第一次是获取对象的类 与 传入类对比,如果不相等,后续对比是继续获取上次 类的父类 与传入类进行对比
    - (BOOL)isKindOfClass:(Class)cls {
    //    NSObject 对象 VS NSObject  直接  NSObject类 VS NSObject类 return YES;
    //    MCPerson 对象 VS MCPerson  直接 MCPerson类 VS MCPerson类 return YES;
        for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    
    // 获取元类进行比较
    + (BOOL)isMemberOfClass:(Class)cls {
        return self->ISA() == cls;
    }
    // 获取类进行比较
    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    

    然而理想很丰满,现实很骨感啊,isMemberOfClass是可以进入源码断点的,但是isKindOfClass不进断点,原因是llvm对isKindOfClass进行了优化。

    断点进行调试,Debug-> Debug Workflow -> Always show Disassembly 显示汇编,找到了objc_opt_isKindOfClass方法,断点打进去试试


    pic2

    果然断住了


    pic3 pic4

    看到这里,基本就了解了,答案如下


    pic1

    相关文章

      网友评论

        本文标题:类 & isa 经典面试题分析

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