iOS底层收集

iOS进阶-03isa & 对象本质

2020-02-04  本文已影响0人  ricefun

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走位:

2.继承关系:

对象的本质

我们先写两个类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_方法,而实例方法并没有;
结论:

方法名简析
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类型,: 代表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的方法之前,内存中就已经含有类、元类、根元类的内存信息,说明类、元类、根元类在编译时期就已经创建。
还有一种方法也能证明:

isa在不同架构中的结构
isa结构

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

上一篇下一篇

猜你喜欢

热点阅读