注册

OC 对象、位域、isa

一、对象的本质

1.1 clang

1.1.1clang 概述

Clang是一个C语言C++Objective-C语言的轻量级编译器。源代码发布于BSD协议下。 Clang将支持其普通lambda表达式、返回类型的简化处理以及更好的处理constexpr关键字。

Clang是一个由Apple主导编写,基于LLVMC/C++/Objective-C编译器。
它与GNU C语言规范几乎完全兼容(当然,也有部分不兼容的内容, 包括编译命令选项也会有点差异),并在此基础上增加了额外的语法特性,比如C函数重载 (通过__attribute__((overloadable))来修饰函数),其目标(之一)就是超越GCC

1.1.2 clang与xcrun命令

1.1.2.1 clang

把目标文件编译成c++文件,最简单的方式:

clang -rewrite-objc main.m -o main.cpp

如果包含其它SDK,比如UIKit则需要指定isysroot

clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m -o main.cpp

  • 如果找不到Foundation则需要排查clang版本设置是否正确。使用which clang可以直接查看路径。有些公司会使用clang-format来进行代码格式化,需要排查环境变量中是否导出了相关路径(如果导出先屏蔽掉)。正常路径为/usr/bin/clang
  • isysroot也可以导出环境变量进行配置方便使用。

1.1.2.2 xcrun(推荐)

xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进行了 一些封装,比clang更好用。


模拟器命令:

xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64simulator.cpp

真机命令:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

1.2 对象c++代码分析

main.m文件如下,直接生成对应的.cpp文件对HotpotCat进行分析。

#import <Foundation/Foundation.h>

@interface HotpotCat : NSObject

@end

@implementation HotpotCat

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}

1.2.1 对象在底层是结构体

直接搜索HotpotCat

可以看到生成了HotpotCat_IMPL是一个结构体,那么HotpotCat_IMPL就是HotpotCat的底层实现么?对HotpotCat增加属性hp_name:

@property(nonatomic, copy) NSString *hp_name;

重新生成.cpp文件:

这也就验证了HotpotCat_IMPL就是HotpotCat的底层实现,那么说明: 对象在底层的本质就是结构体

HotpotCat_IMPL结构体中又嵌套了NSObject_IMPL结构体,这可以理解为继承。
NSObject_IMPL定义如下:


struct NSObject_IMPL {
Class isa;
};

所以NSObject_IVARS就是成员变量isa

1.2.2 objc_object & objc_class

HotpotCat_IMPL上面有如下代码:

typedef struct objc_object HotpotCat;

为什么HotpotCatobjc_object类型?这是因为NSObject的底层实现就是objc_object

同样的Class定义如下:

typedef struct objc_class *Class;

objc_class类型的结构体指针。

同样可以看到idobjc_object结构体类型指针。

typedef struct objc_object *id;

这也就是id声明的时候不需要*的原因。


1.2.3 setter & getter

.cpp文件中有以下代码:


// @property(nonatomic, copy) NSString *hp_name;


/* @end */


// @implementation HotpotCat

//这里是setter和getter 参数self _cmd 隐藏参数
static NSString * _I_HotpotCat_hp_name(HotpotCat * self, SEL _cmd) {
//return self + 成员变量偏移
return (*(NSString **)((char *)self + OBJC_IVAR_$_HotpotCat$_hp_name));
}
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_HotpotCat_setHp_name_(HotpotCat * self, SEL _cmd, NSString *hp_name) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct HotpotCat, _hp_name), (id)hp_name, 0, 1);
}
// @end
  • 根据系统默认注释和函数名称确认这里是hp_namesettergetter方法。
  • getter方法返回hp_name是通过self + 成员变量偏移获取的。getter同理。

二、位域

struct Direction {
BOOL left;
BOOL right;
BOOL front;
BOOL back;
};

上面是一个记录方向的结构体。这个结构体占用4字节32位:00000000 00000000 00000000 00000000。但是对于BOOL值只有两种情况YES/NO。那么如果能用40000来代替前后左右,就只需要0.5个字节就能表示这个数据结构了(虽然只需要0.5字节,但是数据单元最小为1字节)。那么Direction的实现显然浪费了3倍的空间。有什么优化方式呢?位域

2.1 结构体位域

修改DirectionHPDirection:


struct HPDirection {
BOOL left : 1;
BOOL right : 1;
BOOL front : 1;
BOOL back : 1;
};

格式为:数据类型 位域名称:位域长度

验证:

struct Direction dir;
dir.left = YES;
dir.right = YES;
dir.front = YES;
dir.back = YES;
struct HPDirection hpDir;
hpDir.left = YES;
hpDir.right = YES;
hpDir.front = YES;
hpDir.back = YES;
printf("\nDirection size:%zu\nHPDirection size:%zu\n",sizeof(dir),sizeof(hpDir));

c6cf409843efea425c5d5296061c4583.png


2.2 联合体

2.2.1结构体&联合体对比


//结构体联合体对比
//共存
struct HPStruct {
char *name;
int age;
double height;
};

//互斥
union HPUnion {
char *name;
int age;
double height;
};


void testStructAndUnion() {
struct HPStruct s;
union HPUnion u;
s.name = "HotpotCat";
u.name = "HotpotCat";
s.age = 18;
u.age = 18;
s.height = 180.0;
u.height = 180.0;
}
分别定义了HPStruct结构体和HPUnion共用体,在整个赋值过程中变化如下:
9bd9c415ae4b53dc922559b997c4d692.png

总结:

  • 结构体(struct)中所有变量是“共存”的。
    优点:“有容乃大”, 全面;
    缺点:struct内存空间的分配是粗放的,不管用不用全分配。
  • 联合体/共用体(union)中是各变量是“互斥”的。
    缺点:不够“包容”;
    优点:内存使用更为精细灵活,节省了内存空间。
  • 联合体在未进行赋值前数据成员会存在脏数据。

2.2.2 联合体位域

HPDirectionItem.h

@interface HPDirectionItem : NSObject

@property (nonatomic, assign) BOOL left;
@property (nonatomic, assign) BOOL right;
@property (nonatomic, assign) BOOL front;
@property (nonatomic, assign) BOOL back;

@end
HPDirectionItem.m:

#define HPDirectionLeftMask   (1 << 0)
#define HPDirectionRightMask (1 << 1)
#define HPDirectionFrontMask (1 << 2)
#define HPDirectionBackMask (1 << 3)

#import "HPDirectionItem.h"

@interface HPDirectionItem () {
//这里bits和struct用任一一个就可以,结构体相当于是对bits的解释。因为是共用体用同一块内存。
union {
char bits;
//位域,这里是匿名结构体(anonymous struct)
struct {
char left : 1;
char right : 1;
char front : 1;
char back : 1;
};
}_direction;
}

@end

@implementation HPDirectionItem

- (instancetype)init {
self = [super init];
if (self) {
_direction.bits = 0b00000000;
}
return self;
}

- (void)setLeft:(BOOL)left {
if (left) {
_direction.bits |= HPDirectionLeftMask;
} else {
_direction.bits &= ~HPDirectionLeftMask;
}
}

- (BOOL)left {
return _direction.bits & HPDirectionLeftMask;
}
//……
//其它方向设置同理
//……
@end

  • HPDirectionItem是一个方向类,类中有一个_direction的共用体。
  • _direction中有bitsanonymous struct,这里anonymous struct相当于是对bits的一个解释(因为是共用体,同一个字节内存。下面的调试截图很好的证明力这一点)。
  • 通过对bits位移操作来进行数据的存储,其实就相当于对结构体位域的操作。连这个可以互相操作。

所以可以将settergetter通过结构体去操作,效果和操作bits相同:

- (void)setLeft:(BOOL)left {

    _direction.left = left;
}

- (BOOL)left {
return _direction.left;
}

当然也可以两者混用:

- (void)setLeft:(BOOL)left {
_direction.left = left;
}

- (BOOL)left {
return _direction.bits & HPDirectionLeftMask;
}

根本上还是对同一块内存空间进行操作。

调用:

void testUnionBits() {
HPDirectionItem *item = [HPDirectionItem alloc];
item.left = 1;
item.right = 1;
item.front = 1;
item.back = 1;
item.right = 0;
item.back = 0;
NSLog(@"testUnionBits");
}


1e8c243d4f7393524f1d6fa2347b46e3.png

这样整个赋值流程就符合预期满足需求了。

  • 联合体位域作用:优化内存空间和访问速度。

三、 isa

alloc分析的文章中已经了解到执行完initIsa后将alloc开辟的内存与类进行了关联。在initIsa中首先创建了isa_t也就是isa,去掉方法后它的主要结构如下:

union isa_t {
//……
uintptr_t bits;
private:
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
//……
#endif
//……
};

它是一个union,包含了bitscls(私有)和一个匿名结构体,所以这3个其实是一个内容,不同表现形式罢了。这个结构似曾相识,与2.2.2中联合体位域一样。不同的是isa_t占用8字节64位。

没有关联类时isa分布(默认都是0,没有指向):


8f7648ccfb9cfa5d291faa07f01cc433.png


bitscls分析起来比较困难,既然三者一样,那么isa_t的核心就是ISA_BITFIELD


作者:HotPotCat
链接:https://www.jianshu.com/p/84749f140139

0 个评论

要回复文章请先登录注册