第3课-OC对象原理下

第3课-OC对象原理下

[TOC]

1.对象的本质

在之前的课程中我们讲过了编译器,在探索对象的本质之前,我们需要将源文件通过编译器编译成指定文件

1.1 clang编译器的常见用法

1、将目标文件编译成指定文件 例如下面我们将main.m文件编译成c++文件,首先我们在终端进入main.m所在的目录,然后执行clang -rewrite-objc main.m -o main.cpp命令

此时我们会报如下错误: 解决办法:

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m

执行完成后,会生成一个main.cpp的c++文件

除了通过clang生成目标文件,我们还可以通过xcode自带的xcrun生成目标文件。

xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进⾏了⼀些封装,要更好⽤⼀些 xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp (模拟器) xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc XXXX.m -o XXX.cpp (⼿机)

例如将ViewController.m文件编译成vc.cpp文件

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc ViewController.m -o vc.cpp

通过查看编译之后的cpp文件,我们就能够查看源代码经过编译器编译之后的底层代码

1.2 探索对象的本质

先上结论: 对象在底层的本质就是结构体

1.2.1 简单对象

假设我们有main.m函数代码如下:

// 对象在底层的本质就是结构体
@interface ZBPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@end

@implementation ZBPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}

通过上面clang编译指令生成main.cpp文件,打开main.cpp文件我们搜索ZBPerson,可以得到如下关键内容。

struct NSObject_IMPL {
    Class isa;
};

#ifndef _REWRITER_typedef_ZBPerson
#define _REWRITER_typedef_ZBPerson
typedef struct objc_object ZBPerson;
typedef struct {} _objc_exc_ZBPerson;
#endif
struct ZBPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_name;
    NSString *_nickName;
};

分析上面的代码我们可以发现:

  • typedef struct objc_object ZBPerson:ZBPerson被编译成了结构体struct objc_object

    • 首先struct objc_object是一个结构体,里面有一个Class类型的变量isa,而Class的本质就是一个结构体指针struct objc_class *

        typedef struct objc_class *Class;
        struct objc_object {
            Class _Nonnull isa __attribute__((deprecated));
        };
      • 接下来我们再看一下struct objc_class是个什么东西,通过查询objc源码我们可以得到struct objc_class是继承自objc_object的结构体,也就是说它本质上是struct objc_object的子结构体

        // 通过查询objc源码我们可以得到struct objc_class是继承自objc_object的结构体
        struct objc_class : objc_object {
            // Class ISA;
            Class superclass;
            cache_t cache;   
            ....
        }
  • ZBPerson类的成员变量编译之后变成了结构体ZBPerson_IMPL里面的变量,该结构体有三个成员变量

      struct NSObject_IMPL {
          Class isa;
      };
      struct ZBPerson_IMPL {
          struct NSObject_IMPL NSObject_IVARS;
          NSString *_name;
          NSString *_nickName;
      };
    • 变量_name,也就是对应ZBPerson类name属性自动生成的_name成员变量
    • 变量_nickName,也就是对应ZBPerson类nickName属性自动生成的_nickName成员变量
    • 变量NSObject_IVARS是一个结构体,此结构体内部有一个Class类型的变量isa

所以我们可以总结出ZBPerson对象的底层实现图:

再来查看一下内存分配情况 一共占用24字节,isa,_name,_nickName各自占用8字节。 内存布局大概是这样,person 指针指向结构体地址,结构体地址即为首个成员变量的地址。

1.2.2 继承对象

实例1: 如果是 ZBPerson 继承自 NSObject ,ZBStudent 继承自 ZBPerson 类,ZBPerson包含name和nickName两个属性, Student包含age属性,则底层实现结构如下图。

@interface ZBPerson : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@end

@implementation ZBPerson
@end

@interface ZBStudent : ZBPerson
@property (nonatomic, assign) int age;
@end

@implementation ZBStudent
@end

编译之后的对应关系如下

再来看内存分配情况:

首先ZBPerson一共24字节:

  • isa指针占据8字节;
  • _name变量,字符串指针类型占据8字节
  • _nickName成员变量,字符串类型占据8字节

ZBStudent一共8字节:

  • _age成员变量,int类型,占据4字节

所以一共分配24+4=28字节。字节对齐后32字节

实例2: 我们将上面的Person对象修改一下

// 对象在底层的本质就是结构体
@interface ZBPerson : NSObject
@property (nonatomic, assign) int height;
@end

@implementation ZBPerson
@end

@interface ZBStudent : ZBPerson
@property (nonatomic, assign) int age;
@end

@implementation ZBStudent
@end

编译之后的对应关系如下:

分析结构体ZBStudent_IMPL字节对齐之后,占用24字节。那么ZBPerson对象占用多少字节呢?

ZBStudent *student = [[ZBStudent alloc] init];
student.height = 180;
student.age = 18;
NSLog(@"student = %p, sizeof = %lu, mallocSize = %lu", student, class_getInstanceSize([student class]), malloc_size((__bridge const void *)(student)));// student = 0x10100f490, sizeof = 16, mallocSize = 16

这里有个疑问?为什么sizeof = 16, mallocSize = 16,不应该是sizeof = 24, mallocSize = 32

我们查看一下class_getInstanceSize方法的源码:

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() const {
    return word_align(unalignedInstanceSize());
}
// May be unaligned depending on class's ivars.
uint32_t unalignedInstanceSize() const {
    ASSERT(isRealized());
    return data()->ro()->instanceSize;
}
static inline uint32_t word_align(uint32_t x) {
    return (x + WORD_MASK) & ~WORD_MASK;
}

由源码可以得知,class_getInstanceSize底层就是走的成员变量的8字节对齐算法。注意这里是先计算所有成员变量的大小,然后再执行字节对齐算法。

  • ZBStudent对象一共包含isa, int height,int age 3个成员变量,总计8+4+4=16字节
  • 经过验证data()->ro()->instanceSize确是=16

1.2.3 id对象的本质

typedef struct objc_object *id;

id类型的本质就是结构体指针类型 struct objc_object *

也就是为什么我们OC代码使用id声明变量的时候,不需要添加*

id person = [ZBPerson alloc] ====> struct objc_object *person = [ZBPerson alloc]

1.2.4 对象的setter和getter方法

上面是name和nickName对应的setter和getter方法,里面有很多参数,我们在OC代码中是看不到的,这里就是隐藏参数。

注意这里的getter方法中,计算返回值是通过(char *)self + OBJC_IVAR_$_ZBPerson$_name这里实际上通过self首地址+成员变量在对象内存中的偏移量OBJC_IVAR_$_ZBPerson$_name来计算它的内存地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值