Runtime01对象的本质

查看NSObject的对象在内存的内容,有两种方法

方法一:

第1步
在这里插入图片描述
第2步
在这里插入图片描述
第3步

在这里插入图片描述

方法二,用lldb命令来实现

在这里插入图片描述
打印对象:p(等价于print)表示打印、po表示打印对象。

读取内存:

  • memory read/数量+格式+字节数 内存地址
  • x/数量+格式+字节数 内存地址
    //举例:x/3xw 0x10010 ,其中,第一个x表示读内存,3表示输出的内容的个数,第二个x表示16进制,w表示4个字节。

格式:x是16进制,f是浮点,d是10进制
字节大小: b(byte):表示1字节; h(half):half world,表示2字节; w(word):4字节;g(giant word):8字节。

在这里插入图片描述

修改内存:

  • memory write 内存地址 数值
    举例,如下图:

在这里插入图片描述

查看Student对象在内存中的内容(Student直接继承NSObject)

Student类的代码如下:

//生成x64位(MAC的cpu架构),执行命令: clang -rewrite-objc main.m -o main.cpp
//生成arm64位(即iphone64位cpu架构的cpp)执行命令:  xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

//struct NSObject_IMPL { //占用8个byte,用来存放isa指针。OC文件被转换成C++文件时,NSObject类就变成了该结构体
//    Class isa;
//}; //由于该结构体只有一个变量,所以该结构体的地址就是isa变量的地址,即NSObject_IMPL结构体等价于isa指针
//struct Student_IMPL {
//    struct NSObject_IMPL NSObject_IVARS; //因为NSObject_IMPL只有一个isa变量,所以这一行代码会等价于Class isa;
//    int _no;
//    int _age;
//};

struct Student_IMPL {
    Class isa;  //等价于 struct NSObject_IMPL NSObject_IVARS;  占用8个byte
    int _no;  //占用4个byte
    int _age; //占用4个byte
}; //该结构体一个占用16个byte

@interface Student : NSObject { //Student的本质就是上面的Student_IMPL结构体,所以该类实例一个占用16个byte
 
    @public
    int _no; 
    int _age;
}
@end
@implementation Student

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [[Student alloc] init];
        stu->_age = 4;
        stu->_no = 3;
        
        //获取Student类实例的大小(包含isa、_no、_age变量,其中isa是一个指针,指针在64位系统占用8个byte)
        NSLog(@"%@, size = %zd", [Student class], class_getInstanceSize([Student class])); //16 = 8 + 4 + 4
        
        //获取obj指针所指向内存的大小. __bridge表示将OC的指针类型转为C类型的指针
        NSLog(@"%zd", malloc_size((__bridge const void *)stu)); //16
        
        struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
        NSLog(@"age = %d, no = %d", stuImpl->_age, stuImpl->_no);
        
        
    }
    return 0;
}

打印结果如下图:其中,Student里面的isa变量占用8个byte,_age变量占用 4个byte,_no变量占用4个byte。
在这里插入图片描述

总结:如下图,Student的本质就是Student_IMPL结构体,NSObject的本质就是NSObject_IMPL结构体。Student继承NSObject类,所以Student_IMPL结构体里面就有一个NSObject_IMPL结构体。
在这里插入图片描述

查看Student对象在内存中的内容(Student继承Person,Person继承NSObject)

Student类的代码如下:

//生成x64位(MAC的cpu架构),执行命令: clang -rewrite-objc main.m -o main.cpp
//生成arm64位(即iphone64位cpu架构的cpp)执行命令:  xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

struct NSObject_IMPL {
    Class isa;
}; //由于该结构体只有一个变量,所以该结构体的地址就是isa变量的地址,即NSObject_IMPL结构体等价于isa指针
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS; //8 byte
    int age; //4byte
}; //内存对齐的其中一条规则是:结构体的大小必须是最大成员大小的倍数,所以该结构体的占用内存大小就是16byte,最后4个byte是空的。
struct Student_IMPL {
    struct Person_IMPL Person_IVARS; //占用16byte,但这16个byte里面的最后4个byte是空的
    int no; //4byte
}; //由于Person_IMPL结构体因为字节对齐而占用16byte,但其里面的最后4个byte是空的,所以no变量就存放在这空的4个byte里面,所以Student_IMPL的大小是16byte

@interface Person : NSObject {
    @public
    int age;
}
@end
@implementation Person
@end

@interface Student : Person {
    @public
    int no;
}
@end
@implementation Student
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person new];
        person->age = 2;
        Student *stu = [[Student alloc] init];
        stu->age = 4;
        stu->no = 3;
        //获取Student类实例的大小(包含isa、_no、_age变量,其中isa是一个指针,指针在64位系统占用8个byte)
        NSLog(@"Person类实例:%@, size = %zd", [Person class], class_getInstanceSize([Person class])); //16
        //获取obj指针所指向内存的大小. __bridge表示将OC的指针类型转为C类型的指针
        NSLog(@"person实例: %zd", malloc_size((__bridge const void *)person)); //16
        
        //获取Student类实例的大小(包含isa、_no、_age变量,其中isa是一个指针,指针在64位系统占用8个byte)
        NSLog(@"Student类实例:%@, size = %zd", [Student class], class_getInstanceSize([Student class])); //16
        //获取obj指针所指向内存的大小. __bridge表示将OC的指针类型转为C类型的指针
        NSLog(@"stu实例: %zd", malloc_size((__bridge const void *)stu)); //16
        
        NSLog(@"-----");
    }
    return 0;
}

输出结果:
在这里插入图片描述

sizeof()、class_getInstanceSize()和malloc_size()的区别

//sizeof()不是一个函数(在xcode上明确的“常量”都是红色字体,而函数则是浅蓝色),其参数是类型(比如int类型、int *是指针类型),在代码预编译时,sizeof()会被替换成具体的常数。
//而class_getInstanceSize()在某种程度上等价于sizeof(),但class_getInstanceSize()是一个函数,其参数是类类型,表示该类至少占用内存多少个byte
//而malloc_size()是一个函数,其参数类型是对象类型,表示该对象实际占用多大内存。

代码例子如下:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

struct NSObject_IMPL {
    Class isa;
};

struct Person_IMPL {
    struct NSObject_IMPL NSOBJECT_IVARSAAAA; //8byte
    int age; //4byte
    int height; //4byte
    int no; //4byte
}; //因为字节对齐的一条原则:结构体的占用内存的字节数必须是其内部占用内存最大的变量的整数倍,即必须是8byte的整数倍,所以该结构体的大小是24byte,而不是20byte

@interface Person : NSObject { //本质上就是上面的Person_IMPL结构体
    int age;
    int height;
    int no;
}//因为字节对齐的一条原则:结构体的占用内存的字节数必须是其内部占用内存最大的变量的整数倍,即必须是8byte的整数倍,所以该结构体的大小是24byte,而不是20byte
@end

@implementation Person
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //sizeof()不是一个函数(在xcode上明确的“常量”都是红色字体,而函数则是浅蓝色),其参数是类型(比如int类型、int *是指针类型),在代码预编译时,sizeof()会被替换成具体的常数。
        //而class_getInstanceSize()在某种程度上等价于sizeof(),但class_getInstanceSize()是一个函数,其参数是类类型,表示该类至少占用内存多少个byte
        //而malloc_size()是一个函数,其参数类型是对象类型,表示该对象实际占用多大内存。
        
        NSLog(@"NSObject_IMPL's size = %d, Person_IMPL's size = %d", sizeof(struct NSObject_IMPL), sizeof(struct Person_IMPL));
        
        Person *person = [Person new];
        
        //获取Person类实例的大小(包含isa、age、height、no变量,其中isa是一个指针,指针在64位系统占用8个byte)
        NSLog(@"Person类实例:%@, size = %zd", [Person class], class_getInstanceSize([Person class])); //24,即至少占用内存24个byte
    //为什么下面输出32?因为除了字节对齐的原则之外,mac系统也有自己的对齐方法,mac系统的默认对齐大小有16byte的倍数、32byte的倍数等,在本例中默认是16byte的倍数,所以32是16的整数倍,而24不是16的整数倍
        NSLog(@"person实例: %zd", malloc_size((__bridge const void *)person)); //32,即person对象实际占用32个byte
    }
    return 0;
}

输出结果如下图
在这里插入图片描述

对象分为实例对象(instance对象)、类对象(class对象)和元类对象(meta-class对象)

代码:

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //实例对象(instance对象)
        NSObject *obj1 = [NSObject new];
        NSObject *obj2 = [NSObject new];
        
        //类对象(类对象)
        Class objClass1 = [obj1 class];
        Class objClass2 = [obj2 class];
        Class objClass3 = [NSObject class];
        Class objClass4 = object_getClass(obj1);
        Class objClass5 = object_getClass(obj2);
        Class objClass6 = objc_getClass("NSObject");
        
        //元类对象(meta-class对象)
        Class metaClass1 = object_getClass(objClass1);
        Class metaClass2 = object_getClass(objClass5);
        
        NSLog(@"instance对象:obj1 = %p, obj2 = %p", obj1, obj2);
        NSLog(@"class对象:objClass1 = %p, objClass2 = %p, objClass3 = %p, objClass4 = %p, objClass5 = %p, objClass6 = %p, "
              , objClass1, objClass2, objClass3, objClass4, objClass5, objClass6);
        NSLog(@"meta-Class对象:metaClass1 = %p, metaClass2 = %p", metaClass1, metaClass2);
    }
    return 0;
}

结果图:
在这里插入图片描述

说明:实例对象是通过alloc()出来的,alloc()出来的都是新的独立的对象。而类对象可以通过 [实例对象 class] 或者 [类名 class] 或者 object_getClass(实例对象) 获得。在一个进程中,一个类的所有的实例对象的isa都指向同一个类对象。所以上面的类对象的5个打印地址都是相同的。

实例对象(instance对象)在内存中存储的信息主要包括:
  • isa指针,指向其所属的class对象
  • 各个成员变量的值
类对象(class对象)在内存中存储的信息主要包括:
  • isa指针,指向meta-class
  • supperclass指针,指向父类
  • 类的属性信息(@property)
  • 类的对象方法信息(instance method)
  • 类的协议信息(protocol)
  • 类的成员变量信息(ivar)
元类对象(meta-class对象)在内存中存储的信息主要包括:
  • isa指针,指向NSObject类的元类对象
  • supperclass指针,指向父类对象对应的元类对象
  • 类的类方法信息(class method)
实例对象、类对象和元类对象的关系图如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 看上图,当调用实例对象(instance对象)的方法时,①系统会从类对象(class对象)的对象方法列表中查找目标方法,②如果类对象的对象方法列表中没有目标方法,系统会通过类对象的supperclass指针找到父类的类对象的对象方法列表中找该目标方法,③如果找到了,就执行目标方法;④如果找不到,就会继续重复②③④步骤。如果依然找不到,就走消息转发流程。
  • 看下图,当调用类对象(class对象)的方法时,①系统会从元类对象(meta-class对象)的类方法列表中查找目标方法,②如果元类对象的类方法列表中没有目标方法,系统会通过元类对象的supperclass指针找到父类的元类对象的类方法列表中找该目标方法,③如果找到了,就执行目标方法;④如果找不到,就会继续重复②③④步骤。如果基类的元类里面依然找不到目标方法,此时基类(NSObject)的元类会通过supperclass找到NSObject,然后在NSObject类的对象方法列表里面找目标方法。如果没有找到,就走消息转发流程。如果在NSObject类的对象方法列表里面找到 和目标方法同名的对象方法,就执行该方法(此时执行的方法是和目标方法同名的对象方法,而不是执行类方法)!具体看下面的代码例子。

在这里插入图片描述

NSObject+Test分类的代码如下:

@interface NSObject (Test)
- (void)test;
@end

//---------分割线,分隔.h文件和.m文件

#import "NSObject+Test.h"

@implementation NSObject (Test)
- (void)test {
    NSLog(@"%s, %p", __func__, self);
}
@end

main.m文件的代码如下:

#import <Foundation/Foundation.h>
#import "NSObject+Test.h"

@interface Person : NSObject
@end

@implementation Person
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSLog(@"Person class = %p", [Person class]);
        NSLog(@"NSObject class = %p", [NSObject class]);
        
        NSLog(@"-------");
        
        [Person test];
        [NSObject test];
    }
    return 0;
}

上面的代码的打印结果如下:
在这里插入图片描述
执行[NSObject test];的查找与顺序是:首先找NSObject类的元类中的类方法列表里面找test类方法,此时找不到test类方法,就会通过supperclas在NSObject类的对象方法中找与“test”同名的test对象方法,然后执行test对象方法。
执行[Person test];的查找与执行的顺序如下图:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值