面向对象
(本文所运行的Demo是在集成了objc4源码基础上的,详见:gitHub)
本文所写的项目详见:OCBasicDemo
面试题:
1. 一个NSObject对象占用多少内存?
析:
系统分配了16个字节给NSobject对象(通过malloc函数来获得)
但NSObject对象内部只使用了8个字节的空间(64bit环境下,通过class_getInstanceSize函数获得)
OC对象的本质(一)
其实我们平时所写的OC代码,底层的实现都是C/C++来完成的
即:OC-->C\C++-->汇编语言-->机器语言
所以Obiect-C的面向对象都是基于C\C++的数据结构来实现的(也就是**结构体**)
1.一个NSObject对象占用多少内存?
(1)系统分配了16个字节给NSobject对象(通过malloc函数来获得)
(2)但NSObject对象内部只使用了8个字节的空间(64bit环境下,通过class_getInstanceSize函数获得)
验证(至于为什么是8和16我们在下面的时候在来论证):
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
//获得NSObject实例对象的成员变量所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
//获得obj指针所指向内存的大小 >> 16
//malloc_size(const void *ptr):Returns size of given ptr
NSLog(@"%zd", malloc_size((__bridge const void *)obj));
}
return 0;
}
分析:
1.我们创建一个obj的实例对象,通过class_getInstanceSize获得的字节数是8(实际所需的最小的内存大小)
2.通过malloc_size我们获取的字节数是16(系统给分配的内存大小)
2.一个NSObject实例对象,转为C/C++
补充:将一个OC文件转为C/C++代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的.cpp文件名
如果需要链接到其他框架,使用-framework参数,比如-framework UIKit
使用:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o myMain.cpp
得到的myMain.cpp
分析:在cpp文件里面发现了这样一个结构体
struct NSObject_IMPL {
Class isa;
};
对比原来的.m文件
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
}
return 0;
我们在改变下.m文件的内容:
执行:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o myMain2.cpp
#import <Foundation/Foundation.h>
@interface Person:NSObject
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
}
return 0;
}
我们在myMain2.cpp中发现:有这么一个结构体
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
};
我们按住command+鼠标左键 点击NSObject后发现
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0)
OBJC_ROOT_CLASS
OBJC_EXPORT
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
去掉宏等定义
@interface NSObject <NSObject> {
Class isa;
}
加上上述的:我们可以知道
NSObject的第一个小底层:
struct NSObject_IMPL {
Class isa;
};
其中:Class 为:typedef struct objc_class *Class;
具体的里面的isa是啥,我们在后面讨论
我们再来分析下8个字节(实例至少需要的内存的大小)
16个字节的问题(系统实际上分配的内存的大小)
我们通过打断点:
![](https://img.haomeiwen.com/i9540884/370d854957a4db80.png)
然后实时查看内存:Debug -> Debug Workfllow -> View Memory (Shift + Command + M)
发现如下:
![](https://img.haomeiwen.com/i9540884/4bbadd6dbdcacd88.png)
我们可以猜测:
前8个字节是有值的,后8个字节为0值(8个字节是存放实例真正的大小)(系统实际上给该实例分配了16个字节的大小)
接下来我们通过打断点来论证下为什么会是16个字节(集成了objc4源码github)
第1步:
![](https://img.haomeiwen.com/i9540884/e17f4a21f1f75966.png)
第2步:(通过单步走发现会走到下面的方法)
+ (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 summary
if (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
//此处代码省略。。。
我们发现走到了id obj = class_createInstance(cls, 0);该方法
单步走进入:
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 performance
bool hasCxxCtor = cls->hasCxxCtor();
bool hasCxxDtor = cls->hasCxxDtor();
bool fast = cls->canAllocNonpointer();
size_t size = cls->instanceSize(extraBytes); //打个断点走到了这里
if (outAllocatedSize) *outAllocatedSize = size;
//此处代码省略。。。
我们发现走到了size_t size = cls->instanceSize(extraBytes);该方法
单步走进入:
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;
}
这里我们发现:
size_t size:内存对齐值+实例对象的extraBytes
这边系统对于小于16的进行了返回16个字节
单步走进入:
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
进入:
uint32_t unalignedInstanceSize() {
assert(isRealized());
return data()->ro->instanceSize;//打个断点
}
我们发现:
data()->ro->instanceSize
data():函数为:
class_rw_t *data() {
return bits.data();
}
返回的是class_rw_t(类的可读可写列表)
它是一个结构体:(点击进入command+鼠标左键)
如下
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;
#if SUPPORT_INDEXED_ISA
uint32_t index;
#endif
//此处代码省略。。。
里面我们发现了class_ro_t一个结构体(类只读列表)
结构体如下:
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;//重点
#ifdef __LP64__
uint32_t reserved;
#endif
const 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;
}
};
里面有一个系统存储的:instanceSize属性值(实例大小)
综上:我们发现NSObject实例对象所需8个字节,但是苹果系统为它分配了16个字节(可能是为了方便系统去更好的计算,分配内存)
友情链接:
网友评论