iOS-底层(7):isa相关问题答疑

2020-09-15  本文已影响0人  恍然如梦_b700

今天朋友问了我两道面试题,我们来看第一道

第一道

假设我们有一个FYPerson类,并且- (void)sayHello+ (void)sayHappy 都有实现

#import <Foundation/Foundation.h>
#import "FYPerson.h"
#import <objc/runtime.h>

#ifdef DEBUG
#define FYLog(format, ...) printf("%s\n", [[NSString stringWithFormat:format, ## __VA_ARGS__] UTF8String]);
#else
#define FYLog(format, ...);
#endif

void fyObjc_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));
        
        FYLog(@"Method, name: %@", key);
    }
    free(methods);
}

void fyInstanceMethod_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));//有
    
    FYLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void fyClassMethod_classToMetaclass(Class pClass){
    
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));//类的元类中不包含sayHello实例方法
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));//元类中不包含sayHello实例方法

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));//类的元类中包含sayHappy类方法
    // 元类 为什么有 sayHappy 类方法 0 1
    //
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));///元类中包含sayHappy实例方法
    
    FYLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}

void fyIMP_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__);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        FYPerson *person = [FYPerson alloc];
        Class pClass     = object_getClass(person);
        fyObjc_copyMethodList(pClass);

        fyInstanceMethod_classToMetaclass(pClass);
        fyClassMethod_classToMetaclass(pClass);
        fyIMP_classToMetaclass(pClass);
    }
    return 0;
}

打印结果

image.png
/***********************************************************************
* 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.  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_getInstanceMethod(cls->getMeta(), sel);也就是返回类的元类的实例方法,这也说明类方法就是元类的实例方法
我们看看cls->getMeta()做了什么

// NOT identical to this->ISA when this is a metaclass
    Class getMeta() {
        if (isMetaClass()) return (Class)this;
        else return this->ISA();
    }

如果是元类直接返回了自己,因为我们类方法最终存在于元类这一层,就不用继续往下找了,防止了无限递归

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

打印结果为0x100001d00-0x7fff6cac3580-0x7fff6cac3580-0x100001d30,imp1和imp4都找到了方法实现,类里面包含实例方法,元类里面包含类方法,那么为什么imp2和imp3确是一样的呢?

image.png
通过打印我们看到,其实这两个为_objc_msgForward方法的地址,因为元类里面不包含sayHello的对象方法,类里面同样找不到sayHappy类方法,所以就会来到消息转发流程,来到_objc_msgForward,至于_objc_msgForward到底是什么,我会在后面的对象的消息机制中讲到

第二道

        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //1
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //0
        BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];       //0
        BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]];     //0
        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]];       //1
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //1
        BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]];       //1
        BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]];     //1
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);
image.png

和你想的结果一样吗?

我们来分析一下这道题

首先我们了解一下isa走位和类的继承关系,看这张图,这张图不理解的可以看我的这篇文章,iOS-底层(6):类的结构分析

image.png

要知道打印结果,我们要看一下这四个方法的底层实现:

+ (BOOL)isMemberOfClass:(Class)cls {
//传入的类和类对象的元类比较
    return self->ISA() == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
//传入的类依次和对象所属的元类比较
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
//传入的类依次和类对象的元类 - >元类的父类->NSobject根源类->NSobject 比较
    for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
  //传入的类依次和对象的所属的类 - >类的父类->NSobject 比较
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

其实上面的方法并不会直接进入,打开汇编我们发现实际调用的是objc_opt_isKindOfClass
这个方法更明显,也就是说 isKindOfClass 是拿传入了的类和对象isa的继承链来比较

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

拓展

  1. 由于类的信息在内存中永远只存在一份,所以 类对象只有一份

  2. 所有的对象 都是以 objc_object为模板继承过来的,所有的对象 是 来自 NSObject(OC) ,但是真正到底层的 是一个objc_object(C/C++)的结构体类型

属性 & 成员变量 & 实例变量

NSString 是常量类型, 因为不能添加属性,如果定义在类中的{}中,是成员变量

成员变量中 除去基本数据类型、NSString,其他都是 实例变量(即可以添加属性的成员变量),实例变量主要是判断是不是对象

上一篇下一篇

猜你喜欢

热点阅读