类的底层实现-若干小问题

888 阅读4分钟

问题一

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        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 - re2 :%hhd -  re3 :%hhd -  re4 :%hhd - ",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 -  re6 :%hhd -  re7 :%hhd -  re8 :%hhd - ",re5,re6,re7,re8);
    }
    return 0;
}

打印结果如下:

2020-09-17 13:20:35.074630+0800 KCObjc[50956:958090]  re1 :1 - re2 :0 -  re3 :0 -  re4 :0 -
2020-09-17 13:20:35.075304+0800 KCObjc[50956:958090]  re5 :1 -  re6 :1 -  re7 :1 -  re8 :1 -

这道题目唯一的疑问就在于re1,答案就在源码之中:

// Calls [obj 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);
}

+ (BOOL)isMemberOfClass:(Class)cls {
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

可能会有疑问,为什么isKindOfClass不是调用+ (BOOL)isKindOfClass:(Class)cls或者-(BOOL)isKindOfClass:(Class)cls方法呢?在llvm源码中有相关注释:

// This is the table of ObjC "accelerated dispatch" functions.  They are a set
// of objc methods that are "seldom overridden" and so the compiler replaces the
// objc_msgSend with a call to one of the dispatch functions.  That will check
// whether the method has been overridden, and directly call the Foundation 
// implementation if not.  
// This table is supposed to be complete.  If ones get added in the future, we
// will have to add them to the table.
const char *AppleObjCTrampolineHandler::g_opt_dispatch_names[] = {
    "objc_alloc",
    "objc_autorelease",
    "objc_release",
    "objc_retain",
    "objc_alloc_init",
    "objc_allocWithZone",
    "objc_opt_class",
    "objc_opt_isKindOfClass",
    "objc_opt_new",
    "objc_opt_respondsToSelector",
    "objc_opt_self",
};

大体的意思是说上面的几个方法几乎不会被重写,编译器会先检查上述表中对应的objc方法是否有被重写,如果没有,编译器会将objc_msgSend来调用objc方法替换成了直接调用上述函数,可能这样子在效率上会有些提升。 问题回到re1,在上图中,根类NSObject的元类即根元类superClass指向了根类NSObject,因此在objc_opt_isKindOfClass返回了ture

问题二

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);
        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", "InstanceMethod",method1,method2,method3,method4);
        
        Method method5 = class_getClassMethod(pClass, @selector(sayHello));
        Method method6 = class_getClassMethod(metaClass, @selector(sayHello));
        Method method7 = class_getClassMethod(pClass, @selector(sayHappy));
        Method method8 = class_getClassMethod(metaClass, @selector(sayHappy));
        LGLog(@"%s-%p-%p-%p-%p","ClassMethod",method5,method6,method7,method8);
        
        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));
        LGLog(@"%s-%p-%p-%p-%p","MethodImplementation", imp1,imp2,imp3,imp4);
    }
    return 0;
}

输出如下:

InstanceMethod - 0x1000031b0-0x0-0x0-0x100003148
ClassMethod-0x0-0x0-0x100003148-0x100003148
MethodImplementation-0x100001ce0-0x7fff71842580-0x7fff71842580-0x100001d10

解析:

class_getInstanceMethod

  1. sayHelloLGPerson的实例方法,因此method1有值,method2为空
  2. sayHappyLGPerson的类方法,因此method3为空,method4有值 class_getInstanceMethod实现如下:
/***********************************************************************
* class_getInstanceMethod.  Return the instance method for the
* specified class and selector.
**********************************************************************/
Method class_getInstanceMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;
    // This deliberately avoids +initialize because it historically did so.
    // This implementation is a bit weird because it's the only place that 
    // wants a Method instead of an IMP.
#warning fixme build and search caches
    // Search method lists, try method resolver, etc.
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
#warning fixme build and search caches
    return _class_getMethod(cls, sel);
}

class_getClassMethod

  1. sayHelloLGPerson的实例方法,因此method5method6为空
  2. sayHappyLGPerson的类方法,因此method7有值,method8为空,但是打印的输出里面method8是有值的,看一下源码:
/***********************************************************************
* class_getClassMethod.  Return the class method for the specified
* class and selector.
**********************************************************************/
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方法,只不过class_getInstanceMethod方法参数由类对象变成了元类对象,也说明了类方法其实就是元类的实例方法这一观点。

同时还可以发现如果class_getClassMethod参数是元类的话会直接变成调用class_getInstanceMethod方法,而参数还是元类。因此method8其实返回的是LGPerson的类方法sayHappy

class_getMethodImplementation

  1. sayHelloLGPerson的实例方法,因此imp1有值,imp2为空
  2. sayHappyLGPerson的类方法,因此imp3为空,imp4有值

但是实际的输出中imp2和imp3都是有值的。 再来看一下源码:

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;
}

原来是方法实现没有找到的时候会返回_objc_msgForward,也就是消息转发。