本文属笔记性质,主要针对自己理解不太透彻的地方进行记录。

推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。


OC对象本质

基于C与C++结构体实现


OC语言如何被编译器编译:

OC ==> C++ ==> 汇编 ==> 机器语言

而在C++中只有struct(结构体)才能容纳不同类型的内容(比如不同属性)。


将Objective-C代码转换为C\C++代码

  1. clang -rewrite-objc OC源文件 -o 输出的CPP文件

将源文件转写成通用的cpp文件

  1. xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件

通过Xcode将源文件转写成arm64架构下的iphoneos文件,文件内容比第一种要少

  1. 如果需要链接其他框架,使用-framework参数。比如-framework UIKit

NSObject的OC与C++定义

  • 在OC中的定义
@interface NSObject <NSObject> {Class isa;
}
复制代码
  • 转成C++之后的定义
struct NSObject_IMPL {Class isa;
};
复制代码

对于结构体来说,和数组一样。其第一个成员的地址,即为结构体对象的地址。 所以一个OC对象的地址,实际上就是其isa指针的地址。

而这个isa是指向objc_class结构体的指针

// 指针
typedef struct objc_class *Class;
复制代码

而一个指针在64位系统中所占的内存为8字节

所以一个OC对象所占的内存至少为8字节


NSObject对象所占用内存的大小

上面的结论通过class_getInstanceSize函数也可以佐证:

#import <objc/runtime.h>/*
获得NSObject实例对象的
`成员变量`
所占用的大小 >> 8
*/
NSLog(@"%zd", class_getInstanceSize([NSObject class]));//runtime源码中
size_t class_getInstanceSize(Class cls)
{if (!cls) return 0;return cls->alignedInstanceSize();
}// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() {return word_align(unalignedInstanceSize());
}
复制代码

需要注意这个word_align返回的是内存对齐后的大小,以unalignedInstanceSize(为对齐的)大小作为参数。

而对于NSObject *obj指针,我们有另一个函数可以查看其实际被分配的内存大小

#import <malloc/malloc.h>
// 获得obj指针所指向内存的大小 >> 16
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
复制代码

为什么8字节的结构体会被分配16字节

继续看runtime

+ (id)alloc {return _objc_rootAlloc(self);
}id _objc_rootAlloc(Class cls)
{return callAlloc(cls, false/*checkNil*/, true/*allocWithZone*/);
}static ALWAYS_INLINE id
callAlloc(Class cls, bool checkNil, bool allocWithZone=false)
{if (slowpath(checkNil && !cls)) return nil;#if __OBJC2__if (fastpath(!cls->ISA()->hasCustomAWZ())) {// No alloc/allocWithZone implementation. Go straight to the allocator.// fixme store hasCustomAWZ in the non-meta class and // add it to canAllocFast's summaryif (fastpath(cls->canAllocFast())) {// No ctors, raw isa, etc. Go straight to the metal.bool dtor = cls->hasCxxDtor();id obj = (id)calloc(1, cls->bits.fastInstanceSize());if (slowpath(!obj)) return callBadAllocHandler(cls);obj->initInstanceIsa(cls, dtor);return obj;}else {// Has ctor or raw isa or something. Use the slower path.id obj = class_createInstance(cls, 0);if (slowpath(!obj)) return callBadAllocHandler(cls);return obj;}}
#endif// No shortcuts available.if (allocWithZone) return [cls allocWithZone:nil];return [cls alloc];
}// Replaced by ObjectAlloc
+ (id)allocWithZone:(struct _NSZone *)zone {return _objc_rootAllocWithZone(self, (malloc_zone_t *)zone);
}id _objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
{id obj;#if __OBJC2__// allocWithZone under __OBJC2__ ignores the zone parameter(void)zone;obj = class_createInstance(cls, 0);
#elseif (!zone) {obj = class_createInstance(cls, 0);}else {obj = class_createInstanceFromZone(cls, 0, zone);}
#endifif (slowpath(!obj)) obj = callBadAllocHandler(cls);return obj;
}id class_createInstance(Class cls, size_t extraBytes)
{return _class_createInstanceFromZone(cls, extraBytes, nil);
}static __attribute__((always_inline))
id
_class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil)
{if (!cls) return nil;assert(cls->isRealized());// Read class's info bits all at once for performancebool hasCxxCtor = cls->hasCxxCtor();bool hasCxxDtor = cls->hasCxxDtor();bool fast = cls->canAllocNonpointer();size_t size = cls->instanceSize(extraBytes);if (outAllocatedSize) *outAllocatedSize = size;id obj;if (!zone  &&  fast) {obj = (id)calloc(1, size);if (!obj) return nil;obj->initInstanceIsa(cls, hasCxxDtor);} else {if (zone) {obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size);} else {obj = (id)calloc(1, size);}if (!obj) return nil;// Use raw pointer isa on the assumption that they might be // doing something weird with the zone or RR.obj->initIsa(cls);}if (cxxConstruct && hasCxxCtor) {obj = _objc_constructOrFree(obj, cls);}return obj;
}size_t instanceSize(size_t extraBytes) {size_t size = alignedInstanceSize() + extraBytes;// CF requires all objects be at least 16 bytes.if (size < 16) size = 16;return size;
}
复制代码

alloc函数最终会根据instanceSize返回的size,然后使用calloc(1, size);函数去分配内存。

instanceSize函数中,alignedInstanceSize方法为成员变量所占内存大小(上面已经贴过一次).extraBytes参数(据我所见)都为0。

CoreFoundation框架在instanceSize函数中硬性规定不足16字节的内存地址会被补成16位字节。

但实际上,NSObject对象只使用了8字节用来存储isa指针


Student对象的本质

@interface Student : NSObject
{@publicint _no;int _age;
}
@end
复制代码

重写成C++之后

struct Student_IMPL {struct NSObject_IMPL NSObject_IVARS;int _no;int _age;
};struct NSObject_IMPL {Class isa;
};//其实就是
struct Student_IMPL {Class isa; //8字节int _no; //4字节int _age; //4字节
};
复制代码

所以一个OC对象的本质实际上是一个包含了所有父类成员变量+自身成员变量的结构体


Student的内存布局及大小

可以通过Debug->Debug workflow->View momory查看指定地址的结构来查证

对于Student实例对象所占内存地址的大小,我们同样可以通过malloc_size函数来确定。

结果是16。8字节父类的isa指针、4字节_age的int、4字节_no的int。

当然如果有兴趣可以用memory write (stu地址+8偏移量) 8的方式,通过直接修改内存的方式对成员变量_no的值进行修改。


内存对齐原则下的OC对象内存分配

alignedInstanceSize()函数的内存对齐

alignedInstanceSize()函数会按照所有成员变量中内存最长的一个做内存对齐。比如

@interface Animal: NSObject
{int weight;int height;int age;
}
复制代码

实际上只需要8+4+4+4=20个字节长度即可,但是内存对其之后会返回8*3=24

malloc()/calloc()函数的内存对齐

在对象实际创建时,先以alignedInstanceSize()返回的大小作为参考。 然后calloc在实际分配内存时为了内存对齐,最终将会根据bucket进行分配。这个bucket是16的整数倍。

#define NANO_MAX_SIZE 256 /* Buckets sized {16, 32, 48, 64, 80, 96, 112, ...} */
复制代码

所以Animal的实例对象实际上会被分配32个字节长度的内存地址。


sizeOf 与 class_getInstanceSize

返回一个参数对象所占的内存大小

sizeOf

sizeOf是运算符,在程序编译阶段将会直接替换成参数类型所占内存具体的常数值。

由于在编译阶段替换,所以有以下这种特性:

MJPerson *p = [[MJPerson alloc] init];
NSLog(@"%zd", sizeof(p)); // 8
复制代码

p在编译时将会被认为成指针,返回8字节的指针内存长度。而不是MJPerson类型的内存长度。

class_getInstanceSize

class_getInstanceSize是一个方法,在程序运行阶段将会进行计算。

他可以在运行阶段计算某个类所需内存大小

class_getInstanceSize([p class]) //24
复制代码

objc_class

runtime.h

OC2.0以前的类结构体。在2.0之后只剩下头文件,并且已经标记成了OBJC2_UNAVAILABLE的弃用状态。

struct objc_class {Class _Nonnull isa  OBJC_ISA_AVAILABILITY;#if !__OBJC2__Class _Nullable super_class                              OBJC2_UNAVAILABLE;const char * _Nonnull name                               OBJC2_UNAVAILABLE;long version                                             OBJC2_UNAVAILABLE;long info                                                OBJC2_UNAVAILABLE;long instance_size                                       OBJC2_UNAVAILABLE;struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif} OBJC2_UNAVAILABLE;
复制代码

objc_runtime_new.h

最新的runtime源码中,优化了类的结构,内部分工更加明确。

在一级结构体中,只保留了isasuperclasscache三个常用的成员

其余信息均转移到了class_data_bits_t这个二级结构体上

struct objc_class : objc_object {// Class ISA;Class superclass;cache_t cache;             // 方法缓存class_data_bits_t bits;    // 具体的类信息class_rw_t *data() { return bits.data();}void setData(class_rw_t *newData) {bits.setData(newData);}...
}
复制代码

class_data_bits_t(类信息列表)内部,还保存着class_rw_t(可读写信息列表),这些信息是可以动态修改的

struct class_rw_t {// Be warned that Symbolication knows the layout of this structure.uint32_t flags;uint32_t version;const class_ro_t *ro;method_array_t methods; //方法property_array_t properties; //属性protocol_array_t protocols; //协议Class firstSubclass;Class nextSiblingClass;char *demangledName;
}
复制代码

class_rw_t(可读写信息列表)内部,还保存着class_ro_t(不可变信息列表),保存着类加载进内存时就需要确定的信息

struct class_ro_t {uint32_t flags;uint32_t instanceStart;uint32_t instanceSize; //实例对象所占内存大小
#ifdef __LP64__uint32_t reserved;
#endifconst 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;method_list_t *baseMethods() const {return baseMethodList;}
};
复制代码

参考资料

小码哥iOS底层原理班

iOS复习笔记:OC对象内存大小问题

MJiOS底层笔记--OC对象本质相关推荐

  1. 【OC底层】OC对象本质,如 isa, super-class

    Objective-C的本质 1.我们编写的Objective-C,底层现实都是C/C++,代码生成步骤如下: 2.在OC中的所有面向对象的实现,都是基于C/C++的数据结构实现的 3.将Object ...

  2. MJiOS底层笔记--KVO本质

    本文属笔记性质,主要针对自己理解不太透彻的地方进行记录. 推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波. NSKVONotifying_Person KVO时,将被监听的 ...

  3. OC对象的本质及分类

    Object-C的底层都是通过C/C++来实现的,所以OC中的对象也会转化成C/C++中的某一个数据结构, 我们在终端里通过指令 xcrun -sdk iphoneos clang -arch arm ...

  4. 视频教程-iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-iOS

    iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化 小码哥教育CEO,曾开发了2个iOS的流行开源框架(MJRefresh.MJExtension),目前在国内的使用率非常高. 李 ...

  5. iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-李明杰-专题视频课程...

    iOS底层原理班(下)/OC对象/关联对象/多线程/内存管理/性能优化-236人已学习 课程介绍         得遇名师,突飞猛进!iOS培训王者MJ(李明杰)老师精心研发,iOS进阶课程,实用技术 ...

  6. java日常笔记、对象的创建完全是由构造方法实现的吗?this的本质

    java日常笔记.对象的创建完全是由构造方法实现的吗 不完全是构造方法是创建java对象重要途径,通过new关键字调用构造器时,构造器也确实返 回了该对象,但这个对象并不是完全由构造器负责创建的,创建 ...

  7. 小码哥iOS学习笔记第二天: OC对象的分类

    Objective-C中的对象, 简称OC对象, 主要可以分为3种 instance对象(实例对象) class对象(类对象) meta-class对象(元类对象) 一.instance instan ...

  8. 第1课-OC对象原理基础

    第1课-OC对象原理基础 [TOC] 在探索OC对象原理之前,我们首先需要了解以下知识点 1. lldb lldb是xcode自带的命令行调试工具. 我们可以通过: help:查看lldb常见命令 h ...

  9. 第2课-OC对象原理上-1

    第2课-OC对象原理上-1 [TOC] 1.1 alloc对象的指针地址和内存 首先我们看下面代码的执行 ZBPerson *p1 = [ZBPerson alloc]; ZBPerson *p2 = ...

最新文章

  1. 【转】Weblogic的集群
  2. 2021年春季学期-信号与系统-第四次作业参考答案-第三小题
  3. python编程课程上课有用吗-Python培训网络课堂|Python编程软件有哪些功能?
  4. 八卦Minsky打压神经网络始末
  5. 百度地图移动端开发和ArcGIS for Android 开发入门
  6. 计算机组成原理实验三报告,计算机组成原理实验三报告
  7. 基于JAVA+SpringMVC+Mybatis+MYSQL的旅游景点酒店预订网站设计
  8. IntelliJ IDEA :解决idea导入项目爆红
  9. 34、linux shell,常用函数strace
  10. tomcat老启动不起来问题
  11. kmean法和dbscan法的直观比较
  12. select完成单线程,多用户
  13. 项目管理之成熟度模型
  14. 手机之家签名工具_手机端自签名续签名 AltStore越狱安装工具AltServer与AltDeploy使用教程iOS13.5...
  15. 证件照换底色(偷懒不专业版,仅供参考)
  16. 简单谈谈自己对前端的感想
  17. 使用 ONLYOFFICE 宏监测空气质量数据
  18. Spring学习笔记-C7-SpringMVC高级技术
  19. DAMA数据管理知识体系指南之数据安全管理
  20. 台式机安装Windows11正式版(跳过TPM)

热门文章

  1. 计算机lg符号,网上总出现LG的符号,是什么意思
  2. XiaoHu日志 9/5~9/6
  3. Thinkpad电脑安装ubuntu,遇到问题“Minimal BASH-like line editing is supported”
  4. Vue3插件推荐和介绍
  5. matlab程序圆形牛栏,Matlab课本计算题.doc
  6. Android与uni-app 互相通信案例(包含源代码)
  7. 初中计算机考试的题型,教师资格证考试中学信息技术科目题型及分值分布,快看!...
  8. springboot集成配置swaggerUI
  9. java jsp面积怎么算,JSP 课后作业:编写 Tag 文件计算矩形、圆形面积
  10. LeetCode: 871. Minimum Number of Refueling Stops