美文网首页
Objective-C底层面试题总结

Objective-C底层面试题总结

作者: 丸疯 | 来源:发表于2020-12-24 16:46 被阅读0次

方法的归属问题探索

  1. 定一个Person类,定义一个实例方法,一个类方法,并完成实现
@interface Person : NSObject
- (void)sayHello;
+ (void)sayHappy;

@end

@implementation Person

- (void)sayHello{
    NSLog(@"Person say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"Person say : Happy!!!");
}

@end
  1. 获取类的方法并打印出来
void objc_copyMethodList(Class pClass){
    unsigned int count = 0;
    Method *methods = class_copyMethodList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Method const method = methods[i];
        //获取方法名
        NSString *key = NSStringFromSelector(method_getName(method));
        
        NSLog(@"Method, name: %@", key);
    }
    free(methods);
}
  1. 获取实例方法并打印其地址
void instanceMethod_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));
    
    NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
  1. 获取类方法并打印其地址
void classMethod_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));
    // 元类 为什么有 sayHappy 类方法 0 1
    //
    Method method4 = class_getClassMethod(metaClass, @selector(esd));
    
    NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
  1. 获取方法实现
void IMP_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    // - (void)sayHello;
    // + (void)sayHappy;
    IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
    IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));

    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}
  1. 方法调用
        Person *person = [Person alloc];
        Class pClass     = object_getClass(person);
        objc_copyMethodList(pClass);

        instanceMethod_classToMetaclass(pClass);
        classMethod_classToMetaclass(pClass);
        IMP_classToMetaclass(pClass);
  1. 执行结果
Method, name: sayHello
instanceMethod_classToMetaclass - 0x1000081b0-0x0-0x0-0x100008148
classMethod_classToMetaclass-0x0-0x0-0x100008148-0x0
IMP_classToMetaclass-0x100003d10-0x7fff67dc7bc0-0x7fff67dc7bc0-0x100003d40

接下来我们逐个函数进行分析,然后论证结果

objc_copyMethodList

  • class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount)
    runtime提供的函数,用来获取类的方法列表,并返回方法个数。

从这个方法里面我们知道,Person中只存储了实例方法

class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name)

  • command + shift + 0先查看官方文档


    class_getInstanceMethod文档.png

从文档中我们可以看出,当查找实例方法的时候,会沿着当前类的继承链找下去。

  • method1
    • Person类中找sayHello实例方法,能找到,所以地址有值
  • method2
    • MetaClass(Person)的中找sayHello实例方法,找不到
    • 然后沿着MetaClass→SuperMetaClass→RootMetaClass→NSObject→nil继承链找,直到nil都没找到sayHello实例方法,所以打印method2的地址为0x0
  • method3
    • Person类中找sayHappy类方法,没有找到
    • 然后沿着Person→NSObject→nil继承链找,直到nil都没找到sayHappy类方法,所以答应method2的地址为0x0
  • method4
    • MetaClass(Person)的中找sayHappy类方法,能找到,所以打印的地址有值

class_getClassMethod(Class _Nullable cls, SEL _Nonnull name)

  • 先看看文档怎么说


    class_getClassMethod文档.png

从文档上我们可以看出,也是通过父类是否有class method来判断

  • 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_getInstanceMethod,这里不继续通过→ISA()来继续往下摸,是因为会产生无限循环。我们知道class_getInstanceMethod会根据继承链一直去寻找实例方法。

  • method1:
    • 首先获取Person的元类MetaClass(Person),然后寻找沿着MetaClass→SuperMetaClass→RootMetaClass→NSObject→nil去寻找sayhello类的实例方法,最终都找不到,所以地址为0x0
  • method2:
    • 首先获取MetaClass(Person)的元类,因为MetaClass(Person)已经是元类了,所以直接返回MetaClass(Person),然后寻找沿着MetaClass→SuperMetaClass→RootMetaClass→NSObject→nil去寻找sayhello类的实例方法,最终都找不到,所以地址为0x0
  • method3:
    • 首先获取Person的元类MetaClass(Person),然后寻找沿着MetaClass→SuperMetaClass→RootMetaClass→NSObject→nil去寻找Person的元类继承链的sayHappy实例方法,通过前面的分析我们知道Person的类方法sayHappy就存在MetaClass(Person)中,所以返回打印的地址有值。
  • method4:
    • 首先获取MetaClass(Person)的元类,因为MetaClass(Person)已经是元类了,所以直接返回MetaClass(Person),然后寻找沿着MetaClass→SuperMetaClass→RootMetaClass→NSObject→nil去寻方法名为sayHappy的方法,通过前面的分析我们知道sayHappy就存在MetaClass(Person)中,所以返回打印的地址有值。

class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name)

  • class_getMethodImplementation文档


    class_getMethodImplementation文档.png
    • 该方法会返回一个指向方法实现的函数指针
    • 速度比method_getImplementation(class_getInstanceMethod(cls, name))
    • 返回的函数指针不一定是该方法的实现,也可能是runtime的一个内部函数
    • 如果类实例无法响应selector,则返回的函数指针将是runtime消息转发机制的一部分
  • 再来看看源码
IMP class_getMethodImplementation(Class cls, SEL sel)
{
    IMP imp;

    if (!cls  ||  !sel) return nil;

    imp = lookUpImpOrNil(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);

    // Translate forwarding function to C-callable external version
    if (!imp) {
        return _objc_msgForward;
    }

    return imp;
}

通过源码,首先去查找方法实现,如果没有找到,则进行消息转发。

  • imp1:
    • 通过前面我们知道,sayhello实例方法,存在Person类中,能找到,所以返回函数指针,能打印出其地址
  • imp2:
    • sayhello不存在元类中,所以进行了消息转发
  • imp3:
    • sayHappy是类方法,不存在类中,所以进行了消息转发
  • imp4:
    • sayhappy是类方法,存在元类中,能在元类中找到,所以返回函数指针,能打印出其地址

iskindOfClass & isMemberOfClass

  • 类方法isKindOfClassisMemberOfClass
    BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
    BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];
    BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]];
    NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

输出如下

 re1 :1
 re2 :0
 re3 :0
 re4 :0
  • 实例方法isKindOfClassisMemberOfClass
    BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
    BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
    BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]];
    BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];

输出:

 re5 :1
 re6 :1
 re7 :1
 re8 :1

为什么会是这个结果,我们来分析一下isKindOfClassisMemberOfClass的源码

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

通过源码我们可以看到,当isKindOfClass作为类方法的时候,是先通过ISA获取当前类的元类,然后递归当前的元类继承链和当前类进行对比。

  • re1:
    • NSObject→ISA():RootMetaClass VS NSObject,不满足,循环继续
    • 根元类superclassNSObject VS NSObject。返回YES
  • re3
    • Peson→ISA():MetaClass(Person) VS Peson,不满足,循环继续
    • Meta(Person)→superclass:SuperMetaClass(Peson) VS Person, 不满足,循环继续
    • SuperMeta(Peson)→superclass:RootMetaClass VS Person, 不满足,循环继续
    • RootMetaClass→superclass:NSObject VS Person, 不满足,循环继续
    • NSObject→superclass:nil,跳出循环,返回NO

  1. isKindOfClass实例方法源码
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
// class的实例方法
- (Class)class {
    return object_getClass(self);
}

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

objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA();

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}

通过源码,当isKindOfClass作为实例方法的时候,先获取当前实例对象的isa当前实例的类,与传入类进行对比,然后再把传入类与当前实例对象的类继承链来进行对比

  • re5:
    • [NSObject alloc]→getIsa() : NSObject VS NSObject,返回YES
  • re7:
    • [Person alloc]→getIsa() : Person VS VS Person,返回YES。

!!!注意这里有坑点
看上去分析的很对,实际上是这样的么?我们进行断点调试的时候发现isKindOfClass不走我们上面分析的两个方法,而是统一走objc_opt_isKindOfClass

isKindOfClass的调用.png
😢接着我们来分析objc_opt_isKindOfClass的源码
BOOL
objc_opt_isKindOfClass(id obj, Class otherClass)
{
#if __OBJC2__
    if (slowpath(!obj)) return NO;
    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);
}

看到源码,是不是和上面分析的是基本一致的,首先通过需要判断的对象或者类的isa来获取当对象的类或者类的元类,然后通过他们的继承链来与需要对比的类进行对比
Tips: 类通过isa可以获取元类,对象通过isa获取实例当前对象的类

  • isMemberOfClass
    为了避免刚刚的问题,我们首先来看下isMemberOfClass方法的调用走哪个方法
    isMemberOfClass方法流程.png
    确实会走isMemberOfClass的底层方法,可以放心分析了
  • isMemberOfClass类方法的源码
+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

通过源码,拿元类和传入类来进行对比

  • r2:
    • NSObject→ISA(): RootMetaClass VS NSObject,返回NO
  • r4:
    • Person→ISA(): MetaClass(Person) VS NSObject,返回NO

  • isMemberOfClass实例方法的源码
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

// class的实例方法
- (Class)class {
    return object_getClass(self);
}

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

objc_object::getIsa() 
{
    if (fastpath(!isTaggedPointer())) return ISA();

    extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
    uintptr_t slot, ptr = (uintptr_t)this;
    Class cls;

    slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
    cls = objc_tag_classes[slot];
    if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
        slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
        cls = objc_tag_ext_classes[slot];
    }
    return cls;
}

分析源码,[self class]其实就是通过isa去获取实例化当前对象的类,然后与传入类来进行对比。

  • r6:
    • [NSObject alloc]→getIsa(): NSObject VS NSObject,返回YES
  • r4:
    • [Person alloc]→getIsa(): Person VS Person,返回YES

相关文章

网友评论

      本文标题:Objective-C底层面试题总结

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