美文网首页iOS沉淀IOS开发知识点
iOS面试题-OC对象的isa指针

iOS面试题-OC对象的isa指针

作者: 小荣袁 | 来源:发表于2021-03-21 20:06 被阅读0次

    Objective-C中的对象,简称OC对象,主要可以分为3种

    1. instance对象(实例对象)

    • instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象
    • 代码表现
    NSObject *object1 = [[NSObject alloc] init];
    NSObject *object2 = [[NSObject alloc] init];
    
    • object1, object2都是NSObject的instance对象(实例对象)
    • 它们是不同的两个对象,分别占据着两块不同的内存
    • instace对象在内存中存储的信息包括
      • isa指针
      • 其它成员变量

    2. class对象(类对象)

    • 代码表现
    NSObject *object1 = [[NSObject alloc] init];
    NSObject *object2 = [[NSObject alloc] init];
    Class objectClass1 = [object1 class];
    Class objectClass2 = [object2 class];
    Class objectClass3 = [NSObject class];
    Class objectClass4 = object_getClass(object1); // Runtime
    Class objectClass5 = object_getClass(object2); // Runtime
    
    • objectClass1 ~ objectClass5都是NSObject的class对象(类对象),class方法返回的一直是class对象
    • 它们都是同一个对象.每个类在内存中有且只有一个class对象
    • class对象在内存中存储的信息主要包括
      • isa指针
      • superclass指针
      • 类的属性信息(@property),类的对象方法信息(instance method)
      • 类的协议信息(protocol),类的成员变量信息(ivar)

    3. meta-class对象(元类对象)

    • 代码表现
    Class objectMetaClass = object_getClass([NSObject class]); // Runtime
    
    • objectMetaClass是NSObjectmeta-class对象
    • 每个类在内存中有且只有一个meta-class对象
    • meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括
      • isa指针
      • superclass指针
      • 类的类方法信息(class method)
    • 查看Class是否为meta-class
    BOOL result = class_isMetaClass([NSObject class]); // Runtime
    

    object_getClass的内部实现

    image.png
    • 因为Objective-C和swift的混编问题,一些底层实现有所变化
    Class objc_getClass(const char *aClassName)
    {
        if (!aClassName) return Nil;
    
        // NO unconnected, YES class handler
        return look_up_class(aClassName, NO, YES);
    }
    
    Class look_up_class(const char *name, 
                  bool includeUnconnected __attribute__((unused)), 
                  bool includeClassHandler __attribute__((unused)))
    {
        // 传过来的类名为空的话,直接返回nil
        if (!name) return nil;
    
        // 定义Class结果result
        Class result;
        // 定义未实现过变量unrealized
        bool unrealized;
        {
            // 加锁
            runtimeLock.lock();
            // 获取非swift的Class
            result = getClassExceptSomeSwift(name);
            // 如果result有值,且result类的方法实现过了
            unrealized = result  &&  !result->isRealized();
            if (unrealized) {
                // 未实现的话,调用realizeClassMaybeSwiftAndUnlock实现一下,同时解锁
                result = realizeClassMaybeSwiftAndUnlock(result, runtimeLock);
                // runtimeLock is now unlocked
            } else {
                // 解锁
                runtimeLock.unlock();
            }
        }
    
        if (!result) {
            // Ask Swift about its un-instantiated classes.
    
            // We use thread-local storage to prevent infinite recursion
            // if the hook function provokes another lookup of the same name
            // (for example, if the hook calls objc_allocateClassPair)
    
            auto *tls = _objc_fetch_pthread_data(true);
    
            // Stop if this thread is already looking up this name.
            for (unsigned i = 0; i < tls->classNameLookupsUsed; i++) {
                if (0 == strcmp(name, tls->classNameLookups[i])) {
                    return nil;
                }
            }
    
            // Save this lookup in tls.
            if (tls->classNameLookupsUsed == tls->classNameLookupsAllocated) {
                tls->classNameLookupsAllocated =
                    (tls->classNameLookupsAllocated * 2 ?: 1);
                size_t size = tls->classNameLookupsAllocated *
                    sizeof(tls->classNameLookups[0]);
                tls->classNameLookups = (const char **)
                    realloc(tls->classNameLookups, size);
            }
            tls->classNameLookups[tls->classNameLookupsUsed++] = name;
    
            // Call the hook.
            Class swiftcls = nil;
            if (GetClassHook.get()(name, &swiftcls)) {
                ASSERT(swiftcls->isRealized());
                result = swiftcls;
            }
    
            // Erase the name from tls.
            unsigned slot = --tls->classNameLookupsUsed;
            ASSERT(slot >= 0  &&  slot < tls->classNameLookupsAllocated);
            ASSERT(name == tls->classNameLookups[slot]);
            tls->classNameLookups[slot] = nil;
        }
    
        return result;
    }
    

    说说isa

    • instance对象的isa指向class
      • 当调用对象方法时,通过instanceisa找到class,最后找到对象方法的实现进行调用
    • classisa指向meta-class
      • 当调用类方法时,通过classisa找到meta-class,最后找到类方法的实现进行调用
    • meta-classisa指向基类meta-class

    说说superclass

    • classsuperclass指向meta-class

      • 如果没有父类,superclass指针为nil
    • meta-classsuperclass指向基类meta-class

      • 基类的meta-classsuperclass指向基类的class
    • 例子1(实例对象调用父类的对象方法),Student的instance实例调用Person的对象方法流程


      image.png
    • 例子2(类对象调用父类的类方法),Student的class对象调用Person类的类方法流程


      image.png

    经典的isa和superclass图解

    image.png
    • intance调用对象方法的轨迹

      • isa找到class,方法不存在,就通过superclass找父类,一直找,直到所有的父类找完都没有这个对象方法的实现时,再经过runtime的动态方法解析和消息转发,如果都没有,就会报unrecognized selector sent to instance 0xxxxxxxxx
    • class调用类方法的轨迹

      • isa找meta-class,方法不存在,就通过superclass找父类
      • 这种有一种特殊情况🙊,找到meta-class的都没有找到时,因为meta-classsuperclass指向基类meta-class的,所以会调用基类meta-class的类方法.这里不用奇怪明明调用的是类方法,最后却调用了基类的对象方法.
        • 示例代码如下
        #import <Foundation/Foundation.h>
        #import <objc/objc.h>
        
        @interface XYPerson : NSObject
        
        + (void)test;
        
        @end
        
        @implementation XYPerson
        
        @end
        
        @interface NSObject (Test)
        
        + (void)test;
        
        @end
        
        @implementation NSObject (Test)
        
        - (void)test
        {
            NSLog(@"-[NSObject test] - %p", self);
        }
        
        @end
        
        int main(int argc, const char * argv[]) {
            @autoreleasepool {
                // [XYPerson class] - 0x1000041e0
                NSLog(@"[XYPerson class] - %p", [XYPerson class]);
                // [NSObject class] - 0x7fff90dd4118
                NSLog(@"[NSObject class] - %p", [NSObject class]);
        
                // -[NSObject test] - 0x1000041e0
                [XYPerson test];
        //        objc_msgSend([XYPerson class], @selector(test));
        
                // -[NSObject test] - 0x7fff90dd4118
                [NSObject test];
        //        objc_msgSend([NSObject class], @selector(test));
            }
            return 0;
        }
        

    说说isa和superclass细节

    • 代码示意
    // MJPerson类对象的地址:0x00000001000014c8
    // MJPerson实例对象的isa:0x001d8001000014c9(为什么地址有差别,需要要与上一个 ISA_MASK 才是真实的值)
    // isa & ISA_MASK:0x00000001000014c8
    
    MJPerson *person = [[MJPerson alloc] init];
    
    Class personClass = [MJPerson class];
    
    Class personMetaClass = object_getClass(personClass);
    
    • 从64bit开始,isa需要进行一次位运算,才能计算出真实的isa地址值

      • isa & ISA_MASK
      • ISA_MASK的值


        image.png
    • superclass不需要做此操作,直接就是对应的地址值

    isa和superclass-class和meta-class的结构

    • 代码结构
    /// Represents an instance of a class.
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    struct objc_class : objc_object {
        //...
        // Class ISA;
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
        class_rw_t *data() const {
            return bits.data();
        }
        ...
    }
    
    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint16_t witness;
    #if SUPPORT_INDEXED_ISA
        uint16_t index;
    #endif
    
        explicit_atomic<uintptr_t> ro_or_rw_ext;
    
        Class firstSubclass;
        Class nextSiblingClass;
    };
    
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        union {
            const uint8_t * ivarLayout;
            Class nonMetaclass;
        };
    
        explicit_atomic<const char *> name;
        // With ptrauth, this is signed if it points to a small list, but
        // may be unsigned if it points to a big list.
        void *baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    };
    
    • 图片结构


      image.png

    模拟class内部的结构

    #import <Foundation/Foundation.h>
    
    #ifndef MJClassInfo_h
    #define MJClassInfo_h
    
    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    # endif
    
    #if __LP64__
    typedef uint32_t mask_t;
    #else
    typedef uint16_t mask_t;
    #endif
    typedef uintptr_t cache_key_t;
    
    struct bucket_t {
        cache_key_t _key;
        IMP _imp;
    };
    
    struct cache_t {
        bucket_t *_buckets;
        mask_t _mask;
        mask_t _occupied;
    };
    
    struct entsize_list_tt {
        uint32_t entsizeAndFlags;
        uint32_t count;
    };
    
    struct method_t {
        SEL name;
        const char *types;
        IMP imp;
    };
    
    struct method_list_t : entsize_list_tt {
        method_t first;
    };
    
    struct ivar_t {
        int32_t *offset;
        const char *name;
        const char *type;
        uint32_t alignment_raw;
        uint32_t size;
    };
    
    struct ivar_list_t : entsize_list_tt {
        ivar_t first;
    };
    
    struct property_t {
        const char *name;
        const char *attributes;
    };
    
    struct property_list_t : entsize_list_tt {
        property_t first;
    };
    
    struct chained_property_list {
        chained_property_list *next;
        uint32_t count;
        property_t list[0];
    };
    
    typedef uintptr_t protocol_ref_t;
    struct protocol_list_t {
        uintptr_t count;
        protocol_ref_t list[0];
    };
    
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;  // instance对象占用的内存空间
    #ifdef __LP64__
        uint32_t reserved;
    #endif
        const uint8_t * ivarLayout;
        const char * name;  // 类名
        method_list_t * baseMethodList;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;  // 成员变量列表
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    };
    
    struct class_rw_t {
        uint32_t flags;
        uint32_t version;
        const class_ro_t *ro;
        method_list_t * methods;    // 方法列表
        property_list_t *properties;    // 属性列表
        const protocol_list_t * protocols;  // 协议列表
        Class firstSubclass;
        Class nextSiblingClass;
        char *demangledName;
    };
    
    #define FAST_DATA_MASK          0x00007ffffffffff8UL
    struct class_data_bits_t {
        uintptr_t bits;
    public:
        class_rw_t* data() {
            return (class_rw_t *)(bits & FAST_DATA_MASK);
        }
    };
    
    /* OC对象 */
    struct mj_objc_object {
        void *isa;
    };
    
    /* 类对象 */
    struct mj_objc_class : mj_objc_object {
        Class superclass;
        cache_t cache;
        class_data_bits_t bits;
    public:
        class_rw_t* data() {
            return bits.data();
        }
        
        mj_objc_class* metaClass() {
            return (mj_objc_class *)((long long)isa & ISA_MASK);
        }
    };
    
    #endif /* MJClassInfo_h */
    
    // objective-c++
    #import <Foundation/Foundation.h>
    #import <objc/runtime.h>
    #import "MJClassInfo.h"
    
    // MJPerson
    @interface MJPerson : NSObject <NSCopying>
    {
    @public
        int _age;
    }
    @property (nonatomic, assign) int no;
    - (void)personInstanceMethod;
    + (void)personClassMethod;
    @end
    
    @implementation MJPerson
    
    - (void)test
    {
        
    }
    
    - (void)personInstanceMethod
    {
        
    }
    + (void)personClassMethod
    {
        
    }
    - (id)copyWithZone:(NSZone *)zone
    {
        return nil;
    }
    @end
    
    // MJStudent
    @interface MJStudent : MJPerson <NSCoding>
    {
    @public
        int _weight;
    }
    @property (nonatomic, assign) int height;
    - (void)studentInstanceMethod;
    + (void)studentClassMethod;
    @end
    
    @implementation MJStudent
    - (void)test
    {
        
    }
    - (void)studentInstanceMethod
    {
        
    }
    + (void)studentClassMethod
    {
        
    }
    - (id)initWithCoder:(NSCoder *)aDecoder
    {
        return nil;
    }
    
    - (void)encodeWithCoder:(NSCoder *)aCoder
    {
        
    }
    @end
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            MJStudent *stu = [[MJStudent alloc] init];
            stu->_weight = 10;
            
            mj_objc_class *studentClass = (__bridge mj_objc_class *)([MJStudent class]);
            mj_objc_class *personClass = (__bridge mj_objc_class *)([MJPerson class]);
            
            class_rw_t *studentClassData = studentClass->data();
            class_rw_t *personClassData = personClass->data();
            
            class_rw_t *studentMetaClassData = studentClass->metaClass()->data();
            class_rw_t *personMetaClassData = personClass->metaClass()->data();
    
            NSLog(@"1111");
        }
        return 0;
    }
    

    面试题

    1. 对象的isa指针指向哪里?
      • instance对象的isa指向class对象
      • class对象的isa指向meta-class对象
      • meta-class对象的isa指向基类meta-class对象
    2. OC的类信息存放在哪里?
      • 对象方法,属性,成员变量,协议信息,存放在class对象中
      • 类方法,存放在meta-class对象中
      • 成员变量的具体值,存放在instance对象

    相关文章

      网友评论

        本文标题:iOS面试题-OC对象的isa指针

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