1:Objective-C的本质
Objective的底层代码是通过C/C++来实现,所以Objective-C 面向对象是基于C/C++数据结构来实现.
将OC的文件编译成C++文件一共有两种方式:
一种是clang,另一种是xcrun.
- clang:
clang -rewrite-objc main.m -o main.cpp - xcrun:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cppxcode 安装的时候顺带安装了xcrun命令,在clang的基础上进行了一些封装,更好用一些
2:Objective-C的对象的本质
可以新建一个工程,然后在main 函数里面新建一个叫person的对象,并让他具有name的属性,如下图所示.
@interface Person : NSObject
@property (nonatomic, strong) NSString *Name;
@end
@implementation Person
@end
int main(int argc, const char* argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
然后用指令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp将main.m 转换成main.cpp.
接下来,我们在main.cpp里面提取关键信息.
1:对象在底层的本质是结构体
在main.cpp搜索我们刚才创建出来的对象名Person, 会发现如下代码
所以我们推测对象在底层的本质是结构体.
那么NSObject_IMPL又是什么呢?我们继续在main.cpp搜索,发现NSObject_IMPL是装有isa指针的结构体.
2:对象在OC层面里继承NSObject, 在底层实则继承objc_object
我们首先搜索NSObject, 发现如下代码
typedef struct objc_object NSObject;
说明Person在底层继承的是objc_object.
- 与
NSObject对应的, 我们又想到了id类型在底层是什么样子的呢?
接着我们发现如下代码
typedef struct objc_object *id;
所以我们还能得出结论底层里id的本质是一个指针,而NSObject的本质是一个结构体. 这也解释了为什么用NSObject创建对象需要加*,而id类型不需要.
3:Class在底层的本质类型是结构体指针
搜索Class,我们发现如下底层代码
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa __attribute__ ((deprecated));
} __attribute__ ((unavailable));
说明Class在底层是objc_class *类型,是一个结构体指针.
4:get和set方法在底层的实现.
首先我们找到getter和setter参数的实现.
// Get
static NSString * _I_Person_Name(Person * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_Name)); }
// Set
static void _I_Person_setName_(Person * self, SEL _cmd, NSString *Name) { (*(NSString **)((char *)self + OBJC_IVAR_$_Person$_Name)) = Name; }
我们可以注意到:
- 1:在
Get与Set方法中看到了两组参数FFPserson * self、SEL _cmd这是隐藏参数,我们通常创建的方法默认携带这两个参数,这也就是问什么我们在每一个方法里面都可以使用self的原因 - 2:在
Get方法里,可以看出,我们如果要读一个成员变量,首先要拿到对象的首地址, 然后平移OBJC_IVAR_个位置,就能拿到对应的成员变量的地址. 拿到地址再获取值.set方法同理.
3: OC变量的大小和成员变量的关系.
首先我们创建一个继承自NSObject的对象, 再加几个成员变量:
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
@end
然后用xcrun命令将Person.m 转换成C++文件:
上面的代码就可以用C++表示为:
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS; // isa [0,7]
int _age; // [8, 9, 10, 11]
NSString * _Nonnull _name; // (12, 13, 14, 15, [16, 17...23])
NSString * _Nonnull _nickName; // [24....32]
long _height; // (33, 34, 35, [36...40])
};
通过上面代码的的注释可以分析出结构体Person_IMPL的大小是40+8(16字节对齐)= 48字节.
然后我们在main函数里面也验证OC对象Person所占空间大小.
Person *person = [Person alloc];
person.name = @"Cooci";
person.nickName = @"KC";
NSLog(@"class_getInstanceSize([LGPerson class]): %lu", class_getInstanceSize([Person class]));
NSLog(@"malloc_size((__bridge const void *)(person)): %lu", malloc_size(( __bridge const void *)(person)));
打印的结果是:
class_getInstanceSize([LGPerson class]): 40
malloc_size((__bridge const void *)(person)): 48
可见,和我们分析的结果一致. 从上,我们可以得出两个结论:
- 1、
Person对象与Person_IMP结构体大小与占用内存空间大小一致。 - 2、对象内的
成员变量的大小,决定了对象实际占用的内存空间。
4:对象大小和方法的关系
我们给上面的Person对象添加一些对象方法和类方法:
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) long height;
- (void)test:(NSString *)str;
+ (NSString *)test1:(NSString *)str;
@end
然后继续打印一下
class_getInstanceSize([LGPerson class]): 40
malloc_size((__bridge const void *)(person)): 48
- 得出结论: 方法的添加对Person的
大小和占用内存大小,没有任何影响,说明添加方法的数量对OC对象的大小没有影响。 那么为什么方法的添加不影响对象的大小呢,我们分析一下添加完方法之后的main.cpp里的结构体. - 可以看到编译后的方法,都被注释掉,实际参与计算大小的部分,还是
成员变量部分,所以类中添加实例化方法和类方法,都不会影响对象大小和对象占用的内存大小。
Reference: