美文网首页
iOS-底层(7):isa相关问题答疑

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

作者: 恍然如梦_b700 | 来源:发表于2020-09-15 20:37 被阅读0次

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

    第一道

    假设我们有一个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(Class _Nullable cls, SEL _Nonnull name) 返回类的对象方法
    /***********************************************************************
    * 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(Class _Nullable cls, SEL _Nonnull name) 返回类的类方法
    /***********************************************************************
    * 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();
        }
    

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

    • class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name) 返回方法的实现
    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;
    }
    
    • BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];

      • 调用+(BOOL)isKindOfClass:(Class)cls类方法,首先tcls 被赋值为 self(NSObject)->ISA() 和 传入的NSObject类比较,根据isa走位图可知 tclsNSObject根元类tcls != cls, tcls = tcls->superclass, NSObject根元类的父类是NSObject,进入下层循环, tcls == cls 成立 returen YES
    • BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];

      • 这个比较简单就是判断 self->ISA() == cls?很明显 根元类 不等于 NSObject
    • BOOL re3 = [(id)[Person class] isKindOfClass:[Person class]];

      • Person的isa为Person元类, tcls依次等于 Person元类 -> NSObject根元类-> NSObjec,都不等于Person类。
    • BOOL re4 = [(id)[Person class] isMemberOfClass:[Person class]];

      • 判断条件依然是self->ISA() == cls Person元类 != Person
    • BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]]; //1

      • 这里调用的是- (BOOL)isKindOfClass:(Class)cls对象方法,[self class](NSObject) == cls(NSObject)
    • BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]]; //1

      • 这里调用的是- (BOOL)isMemberOfClass:(Class)cls对象方法,判断条件:[self class] == cls [self class](Person) == cls(Person)成立
    • BOOL re7 = [(id)[Person alloc] isKindOfClass:[Person class]]; //1

      • 这里调用的是- (BOOL)isKindOfClass:(Class)cls对象方法,[self class](Person) == cls(Person)
    • BOOL re8 = [(id)[Person alloc] isMemberOfClass:[Person class]]; //1

      • 这里调用的是- (BOOL)isMemberOfClass:(Class)cls对象方法,判断条件:[self class] == cls [self class](Person) == cls(Person)成立

    其实上面的方法并不会直接进入,打开汇编我们发现实际调用的是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++)的结构体类型

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

    • 属性(property):在OC中是通过@property开头定义,且是带下划线成员变量 + setter + getter方法的变量

    • 成员变量(ivar):在OC的类中{}中定义的,且没有下划线的变量

    • 实例变量:通过当前对象类型,具备实例化的变量,是一种特殊的成员变量,例如 NSObject、UILabel、UIButton等

    • 成员变量 和 实例变量什么区别?

      • 实例变量(即成员变量中的对象变量 就是 实例变量):以实例对象实例化来的,是一种特殊的成员变量

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

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

    相关文章

      网友评论

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

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