iOS对象的本质

对象的本质是结构体。这点可以通过编译后的c++代码来加以验证,首先我们先来使用Clang命令来获得编译后的cpp文件。

Clang

clang -rewrite-objc main.m -o main.cpp 把目标文件编译成c++文件

遇到的问题

在编译的过程中,遇到了如下错误:

截屏2021-06-14 下午1.26.47.png

解决方式

找不到UIKit,对于这种问题,我们可以使用下面的命令来解决:

- clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator14.3.sdk main.m
复制代码

或者使用xcrun,更简洁。
xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进行了 一些封装,要更好用一些。

//模拟器
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-xx.cpp
复制代码
//真机
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- xx.cpp
复制代码

如果输出成功,但是找不到对应文件的输出位置,那可以在文件里进行搜索,然后右击选择“在上层文件夹中显示”,就可以找到具体的位置。

验证过程

在main.m中自定义一个类JLObject:

@interface JLObject : NSObject
@property (nonatomic, strong) NSString *name;
@end

//进入NSObject可以看到其成员包含一个isa指针。
@interface NSObject <NSObject> {
    ...
    Class isa  OBJC_ISA_AVAILABILITY;
    ...
}
复制代码

通过clang命令输出main.m的cpp文件,打开文件查看输出内容,发现有几万行代码,所以需要通过搜索找到我们自定义的类,截取代码如下。

...
typedef struct objc_object JLObject;
...
struct JLObject_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
	NSString *_name;
};
// @property (nonatomic, strong) NSString *name;
...
typedef struct objc_object NSObject;
struct NSObject_IMPL {
	Class isa;
};
...
struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};
...
复制代码

可以看出,JLObject在被编后是一个结构体,JLObject_IMPL中包含了一个继承于NSObject_IMPL(NSObject)的成员isa,以及一个自定义成员name。

结论:对象的本质是结构体。

id类型

  • 问题一、为什么id可以修饰对象?
  • 问题二、为什么id修饰对象时不需要*?
JLObject * obj = [[JLObject alloc]init];
id objc1 = [[JLObject alloc]init];
复制代码

在搜索objc_object结构体的同时,发现Class与id都是objc_object的结构体指针。

typedef struct objc_object *id;
typedef struct objc_object JLObject;
复制代码

从编译后的代码可以得到答案,JLObject是objc_object结构体,id是objc_object结构体指针。

对象在内存中的结构

遇到过一个问题,分析的过程中加深了对对象本质的理解,在这里通过这个问题来分析一下。

问题

如图,obj2可不可以调用方法?可不可以访问成员变量?

@interface JLObject : NSObject
@property (nonatomic, strong) NSString *name;
@end

@implementation JLObject
-(void)sayHello{
    NSLog(@"%s - %@",__func__,self.name);
}
@end
复制代码

截屏2021-06-14 下午5.30.37.png

结果

  • 调用方法:obj2可以调用到sayHello方法
  • 属性:obj2的name属性并未赋值,却错误的将obj:<JLObject: 0x6~b0>打印了出来。

分析

1、首先我们要知道,属性及成员变量存于对象开辟的空间中,方法存在于类/元类对象中。

2、对象在栈中存储的只是一个结构体指针,指向堆中对象结构体的起始地址,该地址存储的是isa指针,指向类对象的起始地址。

  • 当访问属性(成员变量)时,需要通过结构体指针先拿到结构体的起始地址,然后通过内存平移去访问到具体的成员变量。
  • 在访问方法时,则需要通过结构体指针先获取到结构体起始地址,这个地址存储的是isa指针,再通过isa指针拿到类在堆中的首地址,然后通过内存平移以及哈希找到对应方法。

3、在iOS alloc 底层原理中我们得知创建对象的三个重要步骤:计算内存大小、开辟空间、赋予isa指针。

  • isa是指向类对象首地址的指针,而obj1同样是指向类对象首地址的指针,所以我们可以认为obj2指向的obj1近似于isa。
计算所需内存大小 开辟空间 赋予isa指针
obj true true true
obj2 false false true

结论

根据以上信息可知:

  • obj2可以通过obj1访问到类对象,所以可以调用方法。
  • obj2没有开辟空间,无法存储/访问属性或成员变量,
  • 当obj2访问name时,会先拿到obj2指向的地址obj1,然后会在obj1的地址上平移8位到obj(0x7~b0 = 0x7~a8 + 8)的位置。所以会打印出<JLObject: 0x6~b0>

补充

obj在内存中的结构:

截屏2021-06-14 下午11.12.24.png

obj2在内存中的结构:

截屏2021-06-14 下午10.47.16.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享