-
查看 oc 的底层实现
- 将 .m 文件转成 .cpp 文件,命令如下:
clang -rewrite-objc main.m -o main.cpp
- 将 .m 文件转成 iphoneos 平台 arm64 架构 的 .cpp 文件,命令如下:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm64.cpp //如果需要链接其他框架,使用 -framework参数,比如 -framework UIKit
-
iOS CPU 架构分类: 模拟器(i386)、32位(armv7)、64位(arm64)
-
Objective-C 的 NSObject 类底层实现:
NSObject *obj = [[NSObject alloc] init]; //我们通过鼠标右键跳转到 NSObject 类定义,能看到下面的代码: @interface NSObject<NSObject> { Class isa; } //通过 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main_arm64.cpp 命令转化之后,得到以下代码: struct NSObject_IMPL { Class isa; }; //(1)可以看出OC类的底层实现实际上就是结构体; //(2)isa 成员的地址即该结构体的地址; //(3)Class:typedef struct objc_class *Class; 指向结构体的指针;
-
指针占8个字节;
-
获取某个类实例对象的大小,示例代码如下:
#import <Foundation/Foundation.h> #import <objc/runtime.h> int main(int argc, char * argv[]) { @autoreleasepool { // Setup code that might create autoreleased objects goes here. NSObject *obj = [[NSObject alloc] init]; size_t size = class_getInstanceSize([NSObject class]); NSLog(@"%zd",size); } return 0; }
输出结果:
8
-
获取某个指针指向的内存大小,示例代码如下:
#import <Foundation/Foundation.h> #import <objc/runtime.h> #import <malloc/malloc.h> int main(int argc, char * argv[]) { @autoreleasepool { // Setup code that might create autoreleased objects goes here. NSObject *obj = [[NSObject alloc] init]; size_t size1 = class_getInstanceSize([NSObject class]); size_t size2 = malloc_size((__bridge const void *)(obj)); NSLog(@"%zd",size1); NSLog(@"%zd",size2); } return 0; }
输出结果如下:
8
16小结:
- class_getInstanceSize 是将类作为参数传入,获取实例对象的大小;
- 而 malloc_size 是将指针作为参数传入,获取指针指向的那块内存的大小
-
探究OC对象的本质,可查看苹果 Objective-C 开源的源码,下载链接: https://github.com/apple-oss-distributions/objc4/tags,下载时选最新的,即找数字最大的。如下图所示:
苹果 Objective-C 开源的源码.png
如想查看上面 class_getInstanceSize 的源码,在工程全局搜 class_getInstanceSize,会得到下面的代码:size_t class_getInstanceSize(Class cls) { if (!cls) return 0; return cls->alignedInstanceSize(); }
鼠标右键点进去 alignedInstanceSize(),能看到下面的代码:
// Class's ivar size rounded up to a pointer-size boundary. uint32_t alignedInstanceSize() const { return word_align(unalignedInstanceSize()); }
注释中 Class's ivar size 即类成员变量的大小。所以得出结论:
class_getInstanceSize 返回的是类所有成员变量所占用的内存大小的总和,并且是经过“内存对齐”处理过后返回的大小。我们能看到 NSObject_IMPL 只有一个 Class 指针,该指针只占8个字节的大小。因此可得出:一个 NSObject 对象占用多少内存?解:对象创建时系统会分配16个字节(通过 malloc_size 实现),但是真正利用起来的只有8个字节(通过 class_getInstanceSize 得以验证)。
-
同样的方式,我们查看 alloc 方法的源码。搜索 allocWithZone,可得以下代码:
// Replaced by ObjectAlloc + (id)allocWithZone:(struct _NSZone *)zone { return _objc_rootAllocWithZone(self, (objc_zone_t)zone); }
继续查看 _objc_rootAllocWithZone 的源码:
id _objc_rootAllocWithZone(Class cls, objc_zone_t zone __unused) { // allocWithZone under __OBJC2__ ignores the zone parameter return _class_createInstanceFromZone(cls, 0, nil, OBJECT_CONSTRUCT_CALL_BADALLOC); }
继续查看 _class_createInstanceFromZone 的源码:
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, int construct_flags = OBJECT_CONSTRUCT_NONE, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { ASSERT(cls->isRealized()); // Read class's info bits all at once for performance bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); size_t size; size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; #if SUPPORT_ZONES if (zone) { obj = (id)malloc_zone_calloc((malloc_zone_t *)zone, 1, size); } else { #endif obj = (id)calloc(1, size); #if SUPPORT_ZONES } #endif if (slowpath(!obj)) { if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) { return _objc_callBadAllocHandler(cls); } return nil; } if (!zone && fast) { obj->initInstanceIsa(cls, hasCxxDtor); } else { // Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR. obj->initIsa(cls); } if (fastpath(!hasCxxCtor)) { return obj; } construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE; return object_cxxConstructFromClass(obj, cls, construct_flags); }
能看到有一行代码如下:
obj = (id)calloc(1, size);
这里传入了一个 size参数,这个 size 是前面定义的,即:
size = cls->instanceSize(extraBytes);
继续跳转 instanceSize 方法实现处,可得:
inline size_t instanceSize(size_t extraBytes) const { if (fastpath(cache.hasFastInstanceSize(extraBytes))) { return cache.fastInstanceSize(extraBytes); } size_t size = alignedInstanceSize() + extraBytes; // CF requires all objects be at least 16 bytes. if (size < 16) size = 16; return size; }
可看到注释说明,CF requires all objects be at least 16 bytes. 当 size 小于16,则苹果框架强制把它等于16. CF 即 Cocoa Foundation 框架。
而 alignedInstanceSize() 我们前面已经看过,返回的是类所有成员的大小,extraBytes 一般为0 ,则可得:
size_t size = alignedInstanceSize() + extraBytes;//这里的 size 最终计算出来结果为 8
-
查看对象在内存中如何布局,可使用 Xcode ---> Debug ---> Debug Workflow ---> ViewMemory,main.m 示例代码如下:
我们在创建 obj2 对象下面一行处打下断点,通过xcode自带的 lldb 调试器,输入 p obj ,即可拿到该对象在内存中的起始地址,如下图所示:#import <Foundation/Foundation.h> #import <objc/runtime.h> #import <malloc/malloc.h> int main(int argc, char * argv[]) { @autoreleasepool { // Setup code that might create autoreleased objects goes here. NSObject *obj = [[NSObject alloc] init]; NSObject *obj2 = [[NSObject alloc] init]; size_t size1 = class_getInstanceSize([NSObject class]); size_t size2 = malloc_size((__bridge const void *)(obj)); NSLog(@"%zd",size1); NSLog(@"%zd",size2); } return 0; }
获取对象地址值.png
有了对象了内存地址之后,我们再通过Xcode ---> Debug ---> Debug Workflow ---> ViewMemory,打开内存查看器,在内存查看器中输入刚刚获取到的对象的内存起始地址,如下图所示:
ViewMemory.png
这里,内存查看器以十六进制的形式显示,每一位十六进制数代表4个二进制位,即每两位十六进制数代表8个二进制位,而8位即为一个字节。 即每两位十六进制数代表一个字节。通过内存查看器可以看到 obj 对象在内存中的布局即为上图蓝色框框住的部分,而该部分占据的内存大小刚好也是16个字节。而前面8个字节是有数据的,后面8个字节没有数据则置为0 。同时也论证了,当我们创建一个对象时,系统会先将这块内存置为0 。 -
LLDB 动态调试器的使用
- 将某个对象的地址值直接输出:po 对象名
- 将某个对象的地址值以十六进制的形式输出:p 对象名
- 读取以某个地址值开始的一段连续的内存空间:memory read + 地址值(读内存)
- 与命令 x + 地址值的效果一致。
- x/数量/格式字节数 + 地址值; eg: x/3xg + 地址值(读内存)
- 其中“数量”,eg: x/3xg,这里的3表示输出的结果为3串地址值
- 其中“格式”,eg: x/3xg,这里的x 表示每串地址值均以16进制的形式输出
- x 为16进制,f 是浮点数,d 是10进制
- 其中“字节大小”,eg: x/3xg,这里的g 表示8个字节
- b: byte 1个字节; h: half word 2个字节;
- w: word 4个字节;g: giant word 8个字节;
- memory write + 要修改的精确到某一位的地址值+修改后的数(写内存)
-
OC 转 c++ 代码,示例代码如下:
#import <Foundation/Foundation.h> #import <objc/runtime.h> #import <malloc/malloc.h> @interface Student: NSObject { @public //学号 int _no; //年龄 int _age; } @end @implementation Student @end struct Student_IMPL { Class isa; int _no; int _age; }; int main(int argc, char * argv[]) { @autoreleasepool { // Setup code that might create autoreleased objects goes here. NSObject *obj = [[NSObject alloc] init]; NSObject *obj2 = [[NSObject alloc] init]; size_t size1 = class_getInstanceSize([NSObject class]); size_t size2 = malloc_size((__bridge const void *)(obj)); NSLog(@"%zd",size1); NSLog(@"%zd",size2); Student *stu = [[Student alloc] init]; stu->_no = 4; stu->_age = 5; //OC 代表转 C++ 代表需要桥接 struct Student_IMPL *stu_IMPL = (__bridge struct Student_IMPL *)(stu); NSLog(@"学生的学号是%d,学生的年龄是%d",stu_IMPL->_no,stu_IMPL->_age); } return 0; }
运行结果如下:
8
16
学生的学号是4,学生的年龄是5结论:通过上面的代码,能看出 类的本质实际上就是结构体,因为通过上面代码实现了从OC 到 C++ 结构体的转换,转换后再读取结构体中的成员没有任何问题。
-
iOS 都是小端模式:从高地址开始读取。
-
内存对齐原则:结构体的最终大小必须是该结构体最大成员大小的倍数。
-
关于对象的属性问题,代码如下:
@interface Student: NSObject { @public //学号 int _no; //年龄 int _age; } @property (nonatomic,assign) int height; @end @implementation Student @end
- 对象的属性通过编绎后,会生成 _height 成员变量,同时生成 getter 和 setter 方法;
- 创建出来的对象所占据的那块内存中,只会存放对象的成员变量,不存储对象的成员方法。(原因:不管创建多少个对象,成员方法只需一份)
网友评论