美文网首页iOS底层收集
iOS进阶-03isa & 对象本质

iOS进阶-03isa & 对象本质

作者: ricefun | 来源:发表于2020-02-04 15:45 被阅读0次

    isa验证:对象的第一个属性必然是isa

    先上代码

    #########  Person 类定义###########
    @interface Person : NSObject
    @property (nonatomic,assign) int age;
    @property (nonatomic,copy) NSString *name;
    @property (nonatomic,assign) int height;
    
    @end
    
    #######  调试代码  ######
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *p = [Person alloc];
            Class cls = object_getClass(p);//获取当前对象的类
    
            NSLog(@"hello world!");
        }
        return 0;
    }
    
    ##########  object_getClass()方法源码 ########### 
    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();//getIsa()方法里面返回了Class
        else return Nil;
    }
    
    inline Class 
    objc_object::getIsa() 
    {
        if (!isTaggedPointer()) return ISA();////ISA()方法里面返回了Class
    
        uintptr_t ptr = (uintptr_t)this;
        if (isExtTaggedPointer()) {
            uintptr_t slot = 
                (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
            return objc_tag_ext_classes[slot];
        } else {
            uintptr_t slot = 
                (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
            return objc_tag_classes[slot];
        }
    }
    
    inline Class 
    objc_object::ISA() 
    {
        assert(!isTaggedPointer()); 
    #if SUPPORT_INDEXED_ISA
        if (isa.nonpointer) {
            uintptr_t slot = isa.indexcls;
            return classForIndex((unsigned)slot);
        }
        return (Class)isa.bits;
    #else
        return (Class)(isa.bits & ISA_MASK);//Class是isa.bits & ISA_MASK得来的,ISA_MASK是一个宏定义的常数,在不同的架构中值会不同
    #endif
    }
    
    ########  ISA_MASK 定义  ######
    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    #   define ISA_MAGIC_MASK  0x000003f000000001ULL
    #   define ISA_MAGIC_VALUE 0x000001a000000001ULL
    #   define ISA_BITFIELD                                                      \
          uintptr_t nonpointer        : 1;                                       \
          uintptr_t has_assoc         : 1;                                       \
          uintptr_t has_cxx_dtor      : 1;                                       \
          uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
          uintptr_t magic             : 6;                                       \
          uintptr_t weakly_referenced : 1;                                       \
          uintptr_t deallocating      : 1;                                       \
          uintptr_t has_sidetable_rc  : 1;                                       \
          uintptr_t extra_rc          : 19
    #   define RC_ONE   (1ULL<<45)
    #   define RC_HALF  (1ULL<<18)
    
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    #   define ISA_MAGIC_MASK  0x001f800000000001ULL
    #   define ISA_MAGIC_VALUE 0x001d800000000001ULL
    #   define ISA_BITFIELD                                                        \
          uintptr_t nonpointer        : 1;                                         \
          uintptr_t has_assoc         : 1;                                         \
          uintptr_t has_cxx_dtor      : 1;                                         \
          uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
          uintptr_t magic             : 6;                                         \
          uintptr_t weakly_referenced : 1;                                         \
          uintptr_t deallocating      : 1;                                         \
          uintptr_t has_sidetable_rc  : 1;                                         \
          uintptr_t extra_rc          : 8
    #   define RC_ONE   (1ULL<<56)
    #   define RC_HALF  (1ULL<<7)
    
    # else
    #   error unknown architecture for packed isa
    # endif
    
    

    NSLog处打断点,进行LLDB调试

    (lldb) x/4gx p
    0x101e2f490: 0x001d8001000015fd 0x0000000000000000
    0x101e2f4a0: 0x0000000000000000 0x0000000000000000
    

    在之前的章节中我都是直接标写 0x001d8001000015f5就是isa,如何证明?继续LLDB调试

    (lldb) x/4gx p //打印p对象内存情况
    0x101e2f490: 0x001d8001000015fd 0x0000000000000000
    0x101e2f4a0: 0x0000000000000000 0x0000000000000000
    (lldb) p/x Person.class//先获取Person类的内存地址 ,用于下面比对
    (Class) $1 = 0x00000001000015f8 Person
    (lldb) p/x 0x001d8001000015fd & 0x00007ffffffffff8//用isa&ISA_MASK ,得到$2
    (long) $2 = 0x00000001000015f8//和$1地址是一样的,不就是person类吗
    (lldb) po 0x00000001000015f8//我们继续打印$2 ,可以看到就是Person
    Person
    

    细心的你应该已经明白,在object_getClass()方法源码中class是通过isa.bits & ISA_MASK得来的,所以上面的LLDB调试其实就是反推证明x/4gx p第一个内存地址就是isa

    ISA_MASK 是一个常数宏,在不同的架构中值是不一样的,在x86_64架构中是 # define ISA_MASK 0x00007ffffffffff8ULL;在arm64架构中是 # define ISA_MASK 0x0000000ffffffff8ULL;详见上面的ISA_MASK 定义源码

    类的创建的个数

    我们知道对象可以创建多个,那类是否也会创建多个?,直接上代码

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *p = [Person alloc];
            p.age = 5;
            p.name = @"lee";
            p.height = 185;
    
            Person *p1 = [Person alloc];
            p1.age = 23;
            p1.name = @"Bao";
            p1.height = 199;
            NSLog(@"hello world!");
    
            NSLog(@"hello world!");
        }
        return 0;
    }
    

    还是在NSLog处打断点,进行LLDB调试

    (lldb) po p
    <Person: 0x101f5e440>
    
    (lldb) po p1
    <Person: 0x101f5f570>
    
    (lldb) x/4gx p
    0x101f5e440: 0x001d800100001655 0x000000b900000005
    0x101f5e450: 0x0000000100001050 0x0000000000000000
    (lldb) x/4gx p1
    0x101f5f570: 0x001d800100001655 0x000000c700000017
    0x101f5f580: 0x0000000100001070 0x0000000000000000
    

    上面的LLDB调试可以看到,对象p和p1的地址是不同的,但是其对象内存地址打印的第一段都是0x001d800100001655,说明isa指向同一个类;
    再看下面的代码

    void testClassNum() {
        Class cls1 = [Person class];
        Class cls2 = [Person alloc].class;
        Class cls3 = object_getClass([Person alloc]);
        NSLog(@"\n%p-\n%p-\n%p-",cls1,cls2,cls3);
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            testClassNum();
        }
        return 0;
    }
    
    //testClassNum()方法打印结果
    2020-02-04 18:17:16.811327+0800 Test[44422:1154780] 
    0x100002668-
    0x100002668-
    0x100002668-
    

    可以看到,三个地址都是一样的;
    结论:

    • 对象可以创建多个,但类只创建一个

    isa走位

    调试代码一

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *p = [Person alloc];
            p.age = 5;
            p.name = @"lee";
            p.height = 185;
            NSLog(@"hello world!");  
        }
        return 0;
    }
    

    还是在NSLog处打断点,进行LLDB调试

    (lldb) x/4gx p//打印对象内存地址
    0x101ebac30: 0x001d80010000163d 0x000000b900000005
    0x101ebac40: 0x0000000100001058 0x0000000000000000
    (lldb) p/x 0x001d80010000163d & 0x00007ffffffffff8
    (long) $9 = 0x0000000100001638
    (lldb) po $9//$9是类
    Person
    
    (lldb) x/4gx 0x0000000100001638//打印类对象内存地址
    0x100001638: 0x001d800100001611 0x0000000100b36140
    0x100001648: 0x0000000101ebf250 0x0000000300000003
    (lldb) p/x 0x001d800100001611 & 0x00007ffffffffff8
    (long) $10 = 0x0000000100001610
    (lldb) po $10//$10是Person元类
    Person
    
    (lldb) x/4gx 0x0000000100001610//打印Person元类对象内存地址
    0x100001610: 0x001d800100b360f1 0x0000000100b360f0
    0x100001620: 0x0000000100f3f0b0 0x0000000300000007
    (lldb) p/x 0x001d800100b360f1 & 0x00007ffffffffff8
    (long) $11 = 0x0000000100b360f0
    (lldb) po $11//$11是NSObject根元类
    NSObject
    
    (lldb) x/4gx 0x0000000100b360f0//打印NSObject根元类对象内存地址
    0x100b360f0: 0x001d800100b360f1 0x0000000100b36140
    0x100b36100: 0x0000000100f3cfc0 0x0000000400000007
    (lldb) p/x 0x001d800100b360f1 & 0x00007ffffffffff8
    (long) $12 = 0x0000000100b360f0
    (lldb) po $12//$12是NSObject根元类
    NSObject
    

    调试代码二:

    void testisa() {
        //NSObject 实例对象
        NSObject *object1 = [NSObject alloc];
        //NSObject 类
        Class class = object_getClass(object1);
        //NSObject 元类
        Class metaClass = object_getClass(class);
        //NSObject 根元类
        Class rootMetaClass = object_getClass(metaClass);
        //NSObject 根根元类
        Class rootRootMetaClass = object_getClass(rootMetaClass);
        NSLog(@"\n%p- 实例对象\n%p- 类\n%p- 元类\n%p- 根元类\n%p- 根根元类",object1,class,metaClass,rootMetaClass,rootRootMetaClass);
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
           testisa();
        }
        return 0;
    }
    
    //打印结果
    2020-02-04 18:30:42.651273+0800 Test[44597:1164375] 
    0x1035012b0- 实例对象
    0x100b38140- 类
    0x100b380f0- 元类
    0x100b380f0- 根元类
    0x100b380f0- 根根元类
    
    这时你再看这张apple的图,是不是清楚很多了,注意看标红的部分 isa走位&继承关系图

    结论
    1.isa走位:

    • 对象--> 类 --> 元类--> 根元类 --> 根元类
    • 根元类 isa还是指向根元类

    2.继承关系:

    • 根元类的父类是NSObject
    • NSObject的父类是nil

    对象的本质

    我们先写两个类Animal和Person

    #############  Animal  ###############
    
    #import <Foundation/Foundation.h>
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Animal : NSObject
    
    @end
    
    NS_ASSUME_NONNULL_END
    
    #############  Person  ###############
    #import <Foundation/Foundation.h>
    #import "Animal.h"
    
    NS_ASSUME_NONNULL_BEGIN
    
    @interface Person : NSObject {
        NSString *nickName;//成员变量 -- 基本类型
        Animal *cat;//实例变量 -- 由其他类声明而来
    }
    @property (nonatomic,copy) NSString *tureName;//属性
    
    @end
    
    NS_ASSUME_NONNULL_END
    

    然后将使用终端将Person.m文件编译成Person.cpp文件,具体终端命令如下:

    //cd到包含person.m文件的目录下
    $ cd /Users/baofan/Desktop/RFDemo/RFMVC_MVP_MVVM/Object 
    //通过clang命令将person.m文件编译成person.cpp文件
    $ clang -rewrite-objc Person.m -o Person.cpp
    
    在原有包含person.m文件的目录下会新增一个Person.cpp文件,双击打开 截屏2020-02-0419.24.20.png

    你会发现这是个拥有10000多行代码的文件,头痛!不要急,直接拉倒文件最底部,然后慢慢往上看,我这里挑选了部分代码进行展示

    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        NSString *nickName;
        Animal *cat;
        NSString * _Nonnull _tureName;
    };
    
    // @property (nonatomic,copy) NSString *tureName;
    
    /* @end */
    
    #pragma clang assume_nonnull end
    
    // @implementation Person
    
    
    static NSString * _Nonnull _I_Person_tureName(Person * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_tureName)); }
    extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
    
    static void _I_Person_setTureName_(Person * self, SEL _cmd, NSString * _Nonnull tureName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _tureName), (id)tureName, 0, 1); }
    // @end
    
    struct _prop_t {
        const char *name;
        const char *attributes;
    };
    
    struct _protocol_t;
    
    struct _objc_method {
        struct objc_selector * _cmd;
        const char *method_type;
        void  *_imp;
    };
    
    struct _protocol_t {
        void * isa;  // NULL
        const char *protocol_name;
        const struct _protocol_list_t * protocol_list; // super protocols
        const struct method_list_t *instance_methods;
        const struct method_list_t *class_methods;
        const struct method_list_t *optionalInstanceMethods;
        const struct method_list_t *optionalClassMethods;
        const struct _prop_list_t * properties;
        const unsigned int size;  // sizeof(struct _protocol_t)
        const unsigned int flags;  // = 0
        const char ** extendedMethodTypes;
    };
    
    struct _ivar_t {
        unsigned long int *offset;  // pointer to ivar offset location
        const char *name;
        const char *type;
        unsigned int alignment;
        unsigned int  size;
    };
    
    struct _class_ro_t {
        unsigned int flags;
        unsigned int instanceStart;
        unsigned int instanceSize;
        unsigned int reserved;
        const unsigned char *ivarLayout;
        const char *name;
        const struct _method_list_t *baseMethods;
        const struct _objc_protocol_list *baseProtocols;
        const struct _ivar_list_t *ivars;
        const unsigned char *weakIvarLayout;
        const struct _prop_list_t *properties;
    };
    
    struct _class_t {
        struct _class_t *isa;
        struct _class_t *superclass;
        void *cache;
        void *vtable;
        struct _class_ro_t *ro;
    };
    
    struct _category_t {
        const char *name;
        struct _class_t *cls;
        const struct _method_list_t *instance_methods;
        const struct _method_list_t *class_methods;
        const struct _protocol_list_t *protocols;
        const struct _prop_list_t *properties;
    };
    

    上面代码显示:Person.cpp中有Person_IMPL 结构体,并且还有_objc_method、 _protocol_t、 _class_ro_t等结构;

    Person_IMPL 及 属性
    struct Person_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        NSString *nickName;
        Animal *cat;
        NSString * _Nonnull _tureName;
    };
    
    // @property (nonatomic,copy) NSString *tureName;
    
    /* @end */
    
    #pragma clang assume_nonnull end
    
    // @implementation Person
    
    
    static NSString * _Nonnull _I_Person_tureName(Person * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_Person$_tureName)); }
    
    static void _I_Person_setTureName_(Person * self, SEL _cmd, NSString * _Nonnull tureName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _tureName), (id)tureName, 0, 1); }
    // @end
    

    可以看到之前在Person.h中的属性和成员变量都被编译在了Person_IMPL结构体中;细心的你肯定发现了,只有属性tureName自动生成了_I_Person_tureName _I_Person_setTureName_方法,而实例方法并没有;
    结论:

    • 对象在编译之后是一个结构体
    • 其属性会生成对应的带下划线的_成员变量,set get方法
    • 成员变量不会自动生成get、set方法
    方法名简析
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[2];
    } _OBJC_$_INSTANCE_METHODS_Person __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        {{(struct objc_selector *)"tureName", "@16@0:8", (void *)_I_Person_tureName},
        {(struct objc_selector *)"setTureName:", "v24@0:8@16", (void *)_I_Person_setTureName_}}
    };
    

    _I_Person_tureName : 函数名(函数指针)用于找到函数的具体实现
    tureName: 方法名
    @16@0:8 方法签名:

    • @:返回值类型 - id类型
    • 16:总共的量(偏移量)
    • @:参数一类型 - id类型 参数偏移范围0-7
    • : 参数二类型 - sel类型 参数偏移范围8-15

    为什么@ 代表id类型,: 代表sel类型?apple就是这么规定的# Type Encodings

    Objective-C type encodings
    数据类型占用字节大小
    类和元类的创建时期

    我们知道load方法早main方法加载,所以我做一个测试,新建一个PersonPerson+createTime分类,图片如下:

    Person类 Person+createTime分类

    然后在分类的load方法中打断点

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *p = [Person alloc];
       }
        return 0;
    }
    

    运行上述代码,程序会断在Person分类的load方法中;此时进行LLDB调试:

    (lldb) x/4gx Person.class//能打印出Person类的内存信息,说明类在这之前已经创建
    0x100002648: 0x001d800100002621 0x0000000100b37140
    0x100002658: 0x00000001003da270 0x0000000000000000
    (lldb) p/x 0x001d800100002621 & 0x00007ffffffffff8
    (long) $1 = 0x0000000100002620
    (lldb) po $1//$1 是Person元类
    Person
    
    (lldb) x/4gx 0x0000000100002620
    0x100002620: 0x001d800100b370f1 0x0000000100b370f0
    0x100002630: 0x0000000100f3ce80 0x0000000200000003
    (lldb) p/x 0x001d800100b370f1 & 0x00007ffffffffff8
    (long) $2 = 0x0000000100b370f0
    (lldb) po $2//$2是NSObject根元类
    NSObject
    

    在上述LLDB调试中我们发现,在load的方法之前,内存中就已经含有类、元类、根元类的内存信息,说明类、元类、根元类在编译时期就已经创建。
    还有一种方法也能证明:

    • command + b 只进行编译
    • 将product中的可执行文件拖入MachOView(坏苹果)中查看,可以发现Person类已经存在于可执行文件中 MachOView 可执行文件查看
    isa在不同架构中的结构
    isa结构

    注意看 shiftcls占的内存大小在不同架构中是不同的,所以上文中 ISA_MASK 是个根据架构变化的常数值。

    相关文章

      网友评论

        本文标题:iOS进阶-03isa & 对象本质

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