OC类编译成cpp文件的分析

oc代码在编写完成后,编译器会将源码编译成可执行程序。apple使用的编译器统称llvm。其中编译前段的工具是clang。
clang -rewrite-objc XXX.m 是clang工具中提供了一个将oc源码转为 c 代码的工具。今天就是来学习一下oc在编译成c++的具体形式以及和objc源码中的对应关系。
更多内容请查看

OC的类在编译时经过哪些步骤

为了编译情况,需要了解一些常见的 clang 命令

// 查看编译源文件需要的几个不同的阶段:yo
$ clang -ccc-print-phases LGPerson.m
0: input, "LGPerson.m", objective-c  //
1: preprocessor, {0}, objective-c-cpp-output //  预处理
2: compiler, {1}, ir //编译器前端生成IR中间代码
3: backend, {2}, assembler  // 编译器后端生成汇编代码
4: assembler, {3}, object  // 生成目标代码
5: linker, {4}, image  //   链接一些动态库
6: bind-arch, "x86_64", {5}, image  //生成适合某个架构的代码

// 将.m 文件编译成.cpp文件 对应上面的 1: preprocessor, {0}, objective-c-cpp-output 
$ clang -rewrite-objc LGPerson.m 

$ clang -rewrite-objc LGPerson.m 编译之后的cpp文件查看类的信息

//可以根据类定义的名称LGPerson去查询,找到了定义
struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};
typedef struct objc_object LGPerson; // 可以看出LGPerson就只有一个isa的指针,类似于id的返回

struct _class_t { // 对应与objc原码中的 objc_class
    struct _class_t *isa;  // isa指针
    struct _class_t *superclass; // 父类
    void *cache;   // 方法缓存(用于快速查找方法)
    void *vtable;  // ..
    struct _class_ro_t *ro; // 数据的存储
};
struct objc_class : objc_object { // objc_object是只含一个isa指针的结构体
    // Class ISA; // 在objc_object结构体中定义了
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable  是之前的cache和vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags (根据class_ro_t生成的 class_rw_t的信息)
    ...
}

// 也可以根据查到 _class_t类型的 OBJC_CLASS_$_LGPerson 定义。
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_LGPerson __attribute__ ((used, section ("__DATA,__objc_data"))) = {
    0, // &OBJC_METACLASS_$_LGPerson, // 元类
    0, // &OBJC_CLASS_$_NSObject, // 父类
    0, // (void *)&_objc_empty_cache, // 缓存
    0, // unused, was (void *)&_objc_empty_vtable, // 
    &_OBJC_CLASS_RO_$_LGPerson,
};

可以看出类的内存:

  1. isa指针
  2. supercls指针
  3. 用于存储方法缓存的cache
  4. 类的实例信息如:实例成员变量,实例方法,协议等
class_t中的 cache

在OBJC_CLASS_SETUP_$_LGPerson LGPerson的赋值函数中。
cache被赋值了_objc_empty_cache

static void OBJC_CLASS_SETUP_$_LGPerson(void ) {
    OBJC_METACLASS_$_LGPerson.isa = &OBJC_METACLASS_$_NSObject; 
    OBJC_METACLASS_$_LGPerson.superclass = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_LGPerson.cache = &_objc_empty_cache;
    OBJC_CLASS_$_LGPerson.isa = &OBJC_METACLASS_$_LGPerson;
    OBJC_CLASS_$_LGPerson.superclass = &OBJC_CLASS_$_NSObject;
    OBJC_CLASS_$_LGPerson.cache = &_objc_empty_cache;
}
// _objc_empty_cache 的定义 结构体objc_cache 的一个实例.因该是一个空的结构体。主要用来保存方法缓存。具体可以查看 发布方法查找的源码 
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;

// 如果LGPerson 继承自Person
static void OBJC_CLASS_SETUP_$_LGPerson(void ) {
    OBJC_METACLASS_$_LGPerson.isa = &OBJC_METACLASS_$_NSObject;
    OBJC_METACLASS_$_LGPerson.superclass = &OBJC_METACLASS_$_Person;
    OBJC_METACLASS_$_LGPerson.cache = &_objc_empty_cache;
    OBJC_CLASS_$_LGPerson.isa = &OBJC_METACLASS_$_LGPerson;
    OBJC_CLASS_$_LGPerson.superclass = &OBJC_CLASS_$_Person;
    OBJC_CLASS_$_LGPerson.cache = &_objc_empty_cache;
}
可以得知:对象的isa只想类。类的isa指向对应的 mataclass。 而mataclass的isa 则直接只想 NSObject的mataClass。同样NSObject的mataClass 的isa指向自己。
isa指向

相关参考和验证:isa的探究

  1. 实例对象的isa指向对应的类
  2. 类的isa指向对应的 mataclass
  3. mataclass的isa 则直接只想 NSObject的mataClass。SObject的mataClass 的isa只想自己
superclass
  1. 类的superclass 指向他的父类。
  2. NSObject的superclass 位0x01 位nil
isa流程图.png
OBJC_CLASS_RO$_LGPerson

存储着累定义的成员变量,属性,方法,协议数据。相当于源码中的class_ro_t。 由此可以理解 class_ro_t 是编译过程中已经确定的了,可以计算类的空间大小。

// 类实例的结构体,以及可以用来计算类实例的大小。成员对应的顺序和属性定义的顺序并不相同。做了结构体的内存优化,但是只有属性做了优化
struct LGPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int a_a;
    char b_b;
    NSString *c_c;
    int _bbbbb;
    int _ccccc;
    NSString * _Nonnull _aaaaaa;
    NSString * _Nonnull _ddddd;
    NSString * _Nonnull _eeeee;
};
// 主要存储着类的原始信息。 对应OC源码中的 class_ro_t
struct _class_ro_t {
    unsigned int flags;
    unsigned int instanceStart; // 首页成员变量的偏移位置
    unsigned int instanceSize; // 实力对象的大小
    unsigned int reserved; // 源码中有着条件  #ifdef __LP64__ 及其是否64位
    const unsigned char *ivarLayout;
    const char *name;
    const struct _method_list_t *baseMethods;
    const struct _objc_protocol_list *baseProtocols;
    const struct _ivar_list_t *ivars;
    const unsigned char *weakIvarLayout; //
    const struct _prop_list_t *properties; 
};
// 编译是确定的类的数据
static struct _class_ro_t _OBJC_CLASS_RO_$_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    0, 
    __OFFSETOFIVAR__(struct LGPerson, a_a),  // instanceStart a_a是第一个属性
    sizeof(struct LGPerson_IMPL), // instanceSize 类实例的大小计算 结构体的大小
    (unsigned int)0,  // reserved 
    0, // ivarLayout 
    "LGPerson", // 类名
    (const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_LGPerson, // 方法的列表
    0, // 协议列表
    (const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_LGPerson,// 成员变量列表。所有的变量
    0, //  
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LGPerson, // 属性列表 使用property修饰的
};

// _objc_empty_vtable的定义可以在源码中找到。是一个的IMP类型(代码块闭包类型)
OBJC_EXPORT IMP _Nonnull _objc_empty_vtable

struct LGPerson_IMPL 可以看作一个实例对象的世纪内存:

  1. 在源码中最终是继承 objc_object。包含有一个isa指针
  2. 包含父类对象的成员变量
  3. 自身的成员变量
  4. 属性的定义的顺序被优化,不是按照定义的顺序参考结构体的内存布局
  5. 实例对象的大小 instanceSize 是根据这个struct来计算的。sizeof(struct LGPerson_IMPL),
OBJC$_INSTANCE_VARIABLES_LGPerson 成员变量列表

属性会在数量列表中,也会在成员列表中。另外属性也会生成对应的get/set方法。根据修饰词的不同,调用objc_setProperty 并传入不同的参数

    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count;
    struct _ivar_t ivar_list[8];
} _OBJC_$_INSTANCE_VARIABLES_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_ivar_t),  // 对应的 entsize 大小
    8, // 8 count 成员变量数量
    {{(unsigned long int *)&OBJC_IVAR_$_LGPerson$a_a, "a_a", "i", 2, 4},  // int 对应1  4个字节
     {(unsigned long int *)&OBJC_IVAR_$_LGPerson$b_b, "b_b", "c", 0, 1},  // char 对应0  1个字节
     {(unsigned long int *)&OBJC_IVAR_$_LGPerson$c_c, "c_c", "@\"NSString\"", 3, 8}, // NSString 对应3  8个字节
     {(unsigned long int *)&OBJC_IVAR_$_LGPerson$_bbbbb, "_bbbbb", "i", 2, 4},
     ...
};

OBJC$_PROP_LIST_LGPerson property修饰的属性列表
static struct /*_ivar_list_t*/ {
// property 修饰的属性列表
static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[5];
} _OBJC_$_PROP_LIST_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    5,
    {{"aaaaaa","T@\"NSString\",&,N,V_aaaaaa"},
    {"bbbbb","Ti,N,V_bbbbb"},
    {"ccccc","Ti,N,V_ccccc"},
    {"ddddd","T@\"NSString\",&,V_ddddd"},
    {"eeeee","T@\"NSString\",C,N,V_eeeee"}}
};
//例如
@property (copy, nonatomic)NSString * eeeee;
// 对应的生成的get/set方法
static NSString * _Nonnull _I_LGPerson_eeeee(LGPerson * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_LGPerson$_eeeee)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setEeeee_(LGPerson * self, SEL _cmd, NSString * _Nonnull eeeee) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _eeeee), (id)eeeee, 0, 1); } // 0 对应是 是否atomic。 1对应的是 是否 copy

// get方法是直接读取对象对应偏移位置的属性
static NSString * _Nonnull _I_LGPerson_eeeee(LGPerson * self, SEL _cmd) { return (*(NSString * _Nonnull *)((char *)self + OBJC_IVAR_$_LGPerson$_eeeee)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
// objc_setProperty 源码 cmd就是可以自定义方法名,offset是变量偏移值。 还有是否原子操作以及是否 copy
void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{
    bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);
    bool mutableCopy = (shouldCopy == MUTABLE_COPY);
    reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy);
}
// 具体的设置方法
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }
    objc_release(oldValue);
}
OBJC$_INSTANCE_METHODS_LGPerson 方法列表

在oc中定义的方法,生成了对应的C函数。第一个参数是对象地址。然后会被存储到class_ro_t 的方法列表中

static instancetype _I_LGPerson_init(LGPerson * self, SEL _cmd) {
    self = ((LGPerson *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("LGPerson"))}, sel_registerName("init"));
    if (self) {
        (*(int *)((char *)self + OBJC_IVAR_$_LGPerson$a_a)) = 100;
        ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)self, sel_registerName("setAaaaaa:"), (NSString *)&__NSConstantStringImpl__var_folders_gw_flhw88953gg0m9rcpg_y974w0000gn_T_LGPerson_35da91_mi_0);
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)self, sel_registerName("setBbbbb:"), 10);
        ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)self, sel_registerName("setCcccc:"), 101);
        ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)self, sel_registerName("setDdddd:"), (NSString *)&__NSConstantStringImpl__var_folders_gw_flhw88953gg0m9rcpg_y974w0000gn_T_LGPerson_35da91_mi_1);
    }
    return self;
}
static void _I_LGPerson_test(LGPerson * self, SEL _cmd) {
    LGPerson * test = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc")), sel_registerName("init"));
    sizeof(&test);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_gw_flhw88953gg0m9rcpg_y974w0000gn_T_LGPerson_35da91_mi_2);
}
// 方法列表
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[23];
} _OBJC_$_INSTANCE_METHODS_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    23,
    {{(struct objc_selector *)"init", "@16@0:8", (void *)_I_LGPerson_init},  // C函数的地址
    {(struct objc_selector *)"test", "v16@0:8", (void *)_I_LGPerson_test},
    {(struct objc_selector *)"dealloc", "v16@0:8", (void *)_I_LGPerson_dealloc},
    {(struct objc_selector *)"aaaaaa", "@16@0:8", (void *)_I_LGPerson_aaaaaa},
    {(struct objc_selector *)"setAaaaaa:", "v24@0:8@16", (void *)_I_LGPerson_setAaaaaa_},
    ...
};

成员变量

vtable

_class_t 的总结

_class_t是类的结构体。有四个成员

  1. isa是mataclass地址。
  2. superclass 是父类地址。
  3. cache 缓存的地址
  4. vtable //
  5. _class_ro_t 类编译期间确定的数据。比如成员变量,方法列表等。
struct _class_ro_t {
    unsigned int flags;
    unsigned int instanceStart;
    unsigned int instanceSize;
    unsigned int reserved; // #ifdef __LP64__
    const unsigned char *ivarLayout;
    const char *name;
    const struct _method_list_t *baseMethods;
    const struct _objc_protocol_list *baseProtocols;
    const struct _ivar_list_t *ivars;
    const unsigned char *weakIvarLayout;
    const struct _prop_list_t *properties;
};
// 对应objc源码class_ro_t
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
    ...
}

static struct _class_ro_t _OBJC_CLASS_RO_$_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    0, 
    __OFFSETOFIVAR__(struct LGPerson, _age), 
    sizeof(struct LGPerson_IMPL), 
    (unsigned int)0, 
    0, 
    "LGPerson",
    (const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_LGPerson,
    0, 
    (const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_LGPerson,
    0, 
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LGPerson,
};

属性偏移量的计算

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)

typedef struct objc_object LGPerson;

extern "C" unsigned long int OBJC_IVAR_$_LGPerson$a_a __attribute__ ((used, section ("__DATA,__objc_ivar"))) = __OFFSETOFIVAR__(struct LGPerson, a_a);
// define 替换之后
((long long) &((struct LGPerson *)0)->a_a)
// 
((long long) &((struct objc_object *)0)->a_a)

总结

使用 clang -rewrite-objc 名称生成oc类对应的编译的cpp文件,主要是为了配合源码学习和了解类的属性,成员变量,方法,协议是怎么存储以及对应到oc源码中的。

  1. 实例,类,mata的isa和superclass 指向
  2. 类的存储结构布局 _class_t 的数据 isa,supercls, cache,vtable,class_ro_t
  3. class_ro_t中存储了类的flag,首位偏移量,实例对象内存的大小(在alloc中有过介绍),表方法列表,成员变量列,协议列表
  4. 类会生成对应的mataclass。其中存储着对应的类变量,类方法
  5. 类和元类在编译器的数据存储,协议的定义(方法,协议的存储)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343