本文已参与「新人创作礼」活动,一起开启掘金创作之路。
几乎每个 iOS 程序员都知道 OC 对象就是一个结构体;那为什么说它是一个结构体?这个结构体里都包含哪些信息?OC 对象的大小具体和哪些因素有关 …………下面我们带着上边的几个问题简单分析下 OC 对象
OC 对象的本质
我们先创建一个空的工程文件并在 main.m 声明一个内部类,代码如下
@interface TY_Dog : NSObject
@end
@implementation TY_Dog
@end
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
TY_Dog * dog = [TY_Dog alloc];
// 此处需要引入 #import <malloc/malloc.h>
NSLog(@"malloc_size((__bridge const void *)(dog)): %lu", malloc_size(( __bridge const void *)(dog)));
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
运行这段代码输出信息为:
LinkDemo_1[17153:1451717] malloc_size((__bridge const void *)(dog)): 16
然后我们再将这个main.m 文件 使用 clang 编译成 C++ 的源代码 .cpp 格式,具体命令如下:
//1、将 xxx.m 编译成 xxx.cpp
clang -rewrite-objc xxx.m -o xxx.cpp
//2、将 ViewController.m 编译成 ViewController.cpp
// 其中 ios-15.0.0 为系统版本
// /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator15.0.0.sdk 为当前模拟器的 sdk 路径
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-15.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.0.sdk ViewController.m
//以下两种方式是通过指定架构模式的命令行,使用xcode工具 xcrun
//3、模拟器文件编译 arm 64 架构
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc xxx.m -o xxx.cpp
//4、真机文件编译 arm 64 架构
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp
打开 xxx.cpp 然后神奇的一幕就出现了
#ifndef _REWRITER_typedef_TY_Dog
#define _REWRITER_typedef_TY_Dog
typedef struct objc_object TY_Dog;
typedef struct {} _objc_exc_TY_Dog;
#endif
// 这里就是 代码中的 TY_Dog
// struct 就是一个结构体
struct TY_Dog_IMPL {
// 这个结构体里边还有一个结构体
// NSObject_IMPL 这个结构体里边的信息
/*
* struct NSObject_IMPL {
* Class isa;
* };
*/
struct NSObject_IMPL NSObject_IVARS;
};
/* @end */
// @implementation TY_Dog
// @end
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 这里就是 TY_Dog * dog = [TY_Dog alloc];
TY_Dog * dog = ((TY_Dog *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TY_Dog"), sel_registerName("alloc"));
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
通过上面的代码我们可以知道 OC 的对象就是一个结构体,这个结构体里面存了另外一个 叫 struct NSObject_IMPL NSObject_IVARS;
结构体。
struct NSObject_IMPL NSObject_IVARS;
这个结构体里边放了一个 Class isa;
(这里的 Class isa
其实是一个结构体指针,后边我们有时间会继续研究的。)
我们还知道一个 OC 对象在没有任何属性和方法的情况下 大小为 16 字节。
OC 对象内的属性和对象的大小有什么关系
基于上面的代码我们继续添加几个成员变量。如下
@interface TY_Dog : NSObject
@property (nonatomic, copy) NSString * name;
@property (nonatomic, copy) NSString * birthday;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic) char level;
@end
运行这段代码输出信息为:
LinkDemo_1[17281:1453881] malloc_size((__bridge const void *)(dog)): 48
然后我们再将它编译成 .cpp 去看看在之前的基础上多了哪些内容
#ifndef _REWRITER_typedef_TY_Dog
#define _REWRITER_typedef_TY_Dog
typedef struct objc_object TY_Dog;
typedef struct {} _objc_exc_TY_Dog;
#endif
extern "C" unsigned long OBJC_IVAR_$_TY_Dog$_name;
extern "C" unsigned long OBJC_IVAR_$_TY_Dog$_birthday;
extern "C" unsigned long OBJC_IVAR_$_TY_Dog$_age;
extern "C" unsigned long OBJC_IVAR_$_TY_Dog$_level;
// 原来结构体里边除了一个结构体指针还有对象的所有属性信息
struct TY_Dog_IMPL {
struct NSObject_IMPL NSObject_IVARS;
char _level;
NSString *_name;
NSString *_birthday;
NSInteger _age;
};
// @property (nonatomic, copy) NSString * name;
// @property (nonatomic, copy) NSString * birthday;
// @property (nonatomic, assign) NSInteger age;
// @property (nonatomic) char level;
/* @end */
//这里竟然还自动生成了每个属性的 get 和 set 方法
// @implementation TY_Dog
static NSString * _I_TY_Dog_name(TY_Dog * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_TY_Dog$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_TY_Dog_setName_(TY_Dog * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct TY_Dog, _name), (id)name, 0, 1); }
static NSString * _I_TY_Dog_birthday(TY_Dog * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_TY_Dog$_birthday)); }
static void _I_TY_Dog_setBirthday_(TY_Dog * self, SEL _cmd, NSString *birthday) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct TY_Dog, _birthday), (id)birthday, 0, 1); }
static NSInteger _I_TY_Dog_age(TY_Dog * self, SEL _cmd) { return (*(NSInteger *)((char *)self + OBJC_IVAR_$_TY_Dog$_age)); }
static void _I_TY_Dog_setAge_(TY_Dog * self, SEL _cmd, NSInteger age) { (*(NSInteger *)((char *)self + OBJC_IVAR_$_TY_Dog$_age)) = age; }
static char _I_TY_Dog_level(TY_Dog * self, SEL _cmd) { return (*(char *)((char *)self + OBJC_IVAR_$_TY_Dog$_level)); }
static void _I_TY_Dog_setLevel_(TY_Dog * self, SEL _cmd, char level) { (*(char *)((char *)self + OBJC_IVAR_$_TY_Dog$_level)) = level; }
// @end
通过上面一顿猛如虎的操作,我们发现再添加了几个属性之后这个对象的大小由原来的 16 字节变成了 48 字节。 添加了属性之后对象 TY_Dog 在编译过程中给每个属性都自动添加了 get 和 Set 方法
static NSString * _I_TY_Dog_name(TY_Dog * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_TY_Dog$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_TY_Dog_setName_(TY_Dog * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct TY_Dog, _name), (id)name, 0, 1); }
OC 对象内的方法和对象的大小有什么关系
按照上述步骤继续添加
@interface TY_Dog : NSObject
@property (nonatomic, copy) NSString * name;
@property (nonatomic, copy) NSString * birthday;
@property (nonatomic, assign) NSInteger age;
@property (nonatomic) char level;
- (void)dogRun;
- (BOOL)dogCanFly;
- (void)dogEatBones:(NSInteger)bonesNum;
@end
运行这段代码输出信息为:
LinkDemo_1[17837:1462364] malloc_size((__bridge const void *)(dog)): 48
然后我们再将它编译成 .cpp 去看看在之前的基础上多了哪些内容
// @property (nonatomic, copy) NSString * name;
// @property (nonatomic, copy) NSString * birthday;
// @property (nonatomic, assign) NSInteger age;
// @property (nonatomic) char level;
// 新增的方法在这
// - (void)dogRun;
// - (BOOL)dogCanFly;
// - (void)dogEatBones:(NSInteger)bonesNum;
/* @end */
// @implementation TY_Dog
static void _I_TY_Dog_dogRun(TY_Dog * self, SEL _cmd) {
}
static BOOL _I_TY_Dog_dogCanFly(TY_Dog * self, SEL _cmd) {
return ((bool)0);
}
static void _I_TY_Dog_dogEatBones_(TY_Dog * self, SEL _cmd, NSInteger bonesNum) {
}
很明显的发现 通过这一顿操作,对象的大小任然是 48 字节没有发生变化。
结论
通过上面的一大堆代码,我们验证了 OC 对象是一个结构体;这个结构体里边包含一个 结构体指针还有对象中声明的各个属性。对象中属性值的大小会直接影响到对象占用内存的多少而对象中添加的方法却对队对象的大小没有什么影响。一个空白对象在内存中占用 16 字节;