深入理解Objective-C中类的数据结构

一、类的结构

OC 中的代码在底层实现,使用的是 C、C++,所以要研究 OC 中的类结构,可以将 OC 的代码转成 C++的代码即可。首先看一下 NSObject 的结构是什么样子的,创建一个文件并简单的编写如下代码:

// CustomFile.m
#import <Foundation/Foundation.h>
void test() {
 [NSObject alloc];
}

进入终端,输入指令:

clang -rewrite-objc CustomFile.m

默认生成一个 CustomFile.cpp 文件。这个指令生成的代码会很多,也可以使用 xcrun 指令来指定一个特定的架构,这样的:

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

这样在 CustomFile_arm64.cpp 文件中会生成一个 真机下的运行代码。相比之下 CustomFile_arm64.cpp 文件会比 CustomFile.cpp 小了很多,但是对于查看 NSObject 的实际结构都是可以的。

打开任意一个 .cpp 文件,都可以找到这样的定义:

struct NSObject_IMPL {
 Class isa;
};

其中 Class 的定义如下:

typedef struct objc_class *Class;

再来看一下在实际中的 NSObject 类的声明是什么样的:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
 Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

简化后是这样的:

@interface NSObject {
 Class isa;
}

总之Class 是一个指针,NSObject_IMPL是一个结构体,与 NSObject 在结构上极为相似。

二、类继承后的结构

创建一个 Person.m 文件,弄一个继承于 NSObject 的 Person 类。代码编写如下:

// Person.m
#import <Foundation/Foundation.h>

// 类的申明
@interface Person : NSObject
@end

// 类的实现
@implementation Person
@end

// 类的申明
@interface Student : Person
@end

// 类的实现
@implementation Student
@end

其中 Person 继承于 NSObject,Student 继承于 Person 于是在 .cpp 文件中找到这样的定义:

struct Person_IMPL {
 struct NSObject_IMPL NSObject_IVARS;
};

struct Student_IMPL {
 struct Person_IMPL Person_IVARS;
};

NSObject_IVARS 看着这个命名就可以猜到是将父类的所有 ivar 都继承过来了。

似乎明白了一个套路

在 NSObject 中只有一个 Class 类型的成员变量 isa,在没有自定义任何的成员属性的情况下,继承的子类中的 ivar 都来自于父类。

如果说给 Person 与 Student 都定义一个成员变量,是这样的:

struct Person_IMPL {
 struct NSObject_IMPL NSObject_IVARS;
 int _no;
};

struct Student_IMPL {
 struct Person_IMPL Person_IVARS;
 int _age;
};

终于对 Class 的一些套路有进一步的理解了。

三、添加方法后的结构

创建一个 FunClass.m 文件,编写代码如下:

// FunClass.m
#import <Foundation/Foundation.h>

// 类的申明
@interface FunClass : NSObject
- (void)testInstance;
+ (void)testClass;
@end

// 类的实现
@implementation FunClass
- (void)testInstance {

}

+ (void)testClass {

}
@end

最后发现在 .cpp 中类的结构没有任何的改变,是这样的:

struct FunClass_IMPL {
 struct NSObject_IMPL NSObject_IVARS;
};

但是我们会发现另外一个问题,在 OC 中的方法变成这样的了:

// 实例方法
_OBJC_$_INSTANCE_METHODS_FunClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
 sizeof(_objc_method),
 1,
 {{(struct objc_selector *)"testInstance", "v16@0:8", (void *)_I_FunClass_testInstance}}
static void _I_FunClass_testInstance(FunClass * self, SEL _cmd) {
}

// 类方法
_OBJC_$_CLASS_METHODS_FunClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
 sizeof(_objc_method),
 1,
 {{(struct objc_selector *)"testClass", "v16@0:8", (void *)_C_FunClass_testClass}}
static void _C_FunClass_testClass(Class self, SEL _cmd) {

}

发现这几个特点:

1、实例方法有这个:_INSTANCE_METHODS_FunClass,类方法的是这个:_CLASS_METHODS_FunClass

2、两个方法都是 static 方法

3、方法都多了两个参数:self 与_cmd,这也回答了为什么 self 与 _cmd 只能在方法中有的根本原因。

关于 方法 的这部分先介绍到这里,后期会有专门的专题。

四、自定义一个 Class 与对应的结构体

上面啰嗦了这么多,到底是对不对呢?!那就来亲自试一下吧。

这里的自定义是指不再继承于 NSObject 了,自己搞一个结构体。为了证明其正确性,分别定义一个 HGNObject 类 与 HGNObject_IMPL 结构体。编写的代码如下:

// ==== 类的定义部分 ====
// 类的申明
@interface HGNObject : NSObject {
 @public
 int _no;
 int _age;
}
@end

// 类的实现
@implementation HGNObject
@end

// ==== 结构体 ====
struct HGNObject_IMPL {
 Class isa_hg;
 int _no_hg;
 int _age_hg;
};

做两个试验:

1、类转结构体

2、结构体转类

1、类转结构体

示例代码如下:

// 类转结构体
- (void)class2Struct {
 // 创建一个对象
 HGNObject* nObj = [[HGNObject alloc] init];
 // 成员变量赋值
 nObj->_no = 771722918;
 nObj->_age = 18;

 { // 类对象直接转成一个结构体
 struct HGNObject_IMPL* nObj_s = (__bridge struct HGNObject_IMPL*)nObj;
 // 打印结构体中的值
 NSLog(@"%zd, %zd", nObj_s->_no_hg, nObj_s->_age_hg);
 // 打印结果: 771722918, 18
 }
}

通过结构体指针能打印出在类对象中设置的值,说明在 类转结构体的过程是有效的。

2、结构体转类

示例代码如下:

// 结构体转类
- (void)struct2Class {
 NSLog(@"结构体转类");
 // 生成一个结构体
 struct HGNObject_IMPL nObj_s = {0, 771722918, 20};
 // 结构体中的值打印
 NSLog(@"isa_hg = %zd, _no_hg = %zd, _age_hg = %zd", nObj_s.isa_hg, nObj_s._no_hg, nObj_s._age_hg);

 struct HGNObject_IMPL* nObj_sPointer = &nObj_s;

 // 结构体转成对象
 HGNObject* nObj = (__bridge HGNObject *)(nObj_sPointer);

 NSLog(@"_no_hg = %zd, _age_hg = %zd", nObj->_no, nObj->_age);
}

运行代码,直接 crash 了:

由于 Block 解开多年来的误解 的惨痛教训,所以对遇到的 crash 就会很敏感。看一下上面的这张图,有一个关键的点是不可以忽视的,就是这里的值:

简单的分析(我这里的分析都是猜的,暂时我也不知道,【抱歉抱歉抱歉】)

nObj_s 是有正确的值的,说明 nObj_sPointer 指针也是没有问题的,但是为什么会报一个坏地址访问错误呢?并且 address 的值每次都是一样的 0x20。我猜想:在转化的过程中不仅仅是一个简单的赋值操作、可能还做了其他的地址访问操作,这个操作很有可能与 +alloc 方法中操作有关,毕竟在 OC 中正常的创建一个对象必先 +alloc方法,对于 +alloc中都做了什么事,暂时我还不清楚,所以这里我是蒙的。各位大神若有新的理解,望指教!

所以在第一个实验中的 类转结构体 中是有效的,也许是一个偶然。毕竟我们在上面的 .cpp 文件中查看数据结构的时候也只是看了一个大概,并没有看全部的。

OK,忘记本节(自定义一个 Class 与对应的结构体)中遇到的不愉快, 至少 类转结构体 是有效的,也能说明一些问题。

本系列的文章,有:

1、Objective-C 中类的数据结构

2、Objective-C 中实例所占内存的大小

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Objective-C中类和方法的定义以及协议的使用

    类和方法的定义 一般类都是继承NSObject,新建一个类时父类选择NSObject即可,oc只有单继承,没有多继承. 下面以一个简单的oc工程来讲解(需配置好Xcode工具以相应的SDK),会建工程的请直接跳到'六'步骤. 一.新建一个oc编译环境,打开xcode->File->New->Project(快捷键 command+shift+N). 二.选择OS X--Application--Command Line Tool,选择next 三.填写项目名称(最好英文),你自己的名字,

  • Objective-C中使用NSString类操作字符串的方法小结

    一.字符串切割 1.带节点的字符串,如@"<p>讨厌的节点<br/></p>"我们只想要中间的中文 处理方法: 复制代码 代码如下: NSString *string1 = @"<p>讨厌的节点<br/></p>";   /*此处将不想要的字符全部放进characterSet1中,不需另外加逗号或空格之类的,除非字符串中有你想要去除的空格,此处< p /等都是单独存在,不作为整个字符*/

  • 在Swift中使用Objective-C编写类、继承Objective-C类

    互用性(互操作性)使开发者可以定义融合了 Objective-C 语言特性的Swift类.编写 Swift 类时,不仅可以继承 Objective-C 语言编写的父类,采用 Objective-C 的协议,还可以利用 Objective-C 的一些其它功能.这意味着,开发者可以基于 Objective-C 中已有的熟悉.可靠的类.方法和框架来创建 Swift 类,并结合 Swift 提供的现代化和更有效的语言特点对其进行优化. 继承Objective-C的类 在 Swift 中,开发者可以定义一

  • Objective-C的NSOperation多线程类基本使用指南

    NSOperation 一.NSOperation 1.简介 NSOperation实例封装了需要执行的操作和执行操作所需的数据,并且能够以并发或非并发的方式执行这个操作. NSOperation本身是抽象基类,因此必须使用它的子类,使用NSOperation子类的方式有2种: 1> Foundation框架提供了两个具体子类直接供我们使用:NSInvocationOperation和NSBlockOperation 2> 自定义子类继承NSOperation,实现内部相应的方法 2.执行操作

  • Objective-C 实现2048算法类

    参数model有一个二维数组data,及阶数matrix // .h文件 @class DataModel; @interface Algorithm : NSObject @property (nonatomic,assign) int addScore; // 加分 - (void)caculateTop:(DataModel *)model; // 上滑规则 - (void)caculateBottom:(DataModel *)model; // 下滑规则 - (void)caculat

  • 深入理解Objective-C中类的数据结构

    一.类的结构 OC 中的代码在底层实现,使用的是 C.C++,所以要研究 OC 中的类结构,可以将 OC 的代码转成 C++的代码即可.首先看一下 NSObject 的结构是什么样子的,创建一个文件并简单的编写如下代码: // CustomFile.m #import <Foundation/Foundation.h> void test() { [NSObject alloc]; } 进入终端,输入指令: clang -rewrite-objc CustomFile.m 默认生成一个 Cus

  • 理解数据结构

    从宏观上理解数据结构     1.数据结构对编程为什么如此重要? 现在就根据我自己的体会来为大家阐述一下数据结构对我们编程为什么如此重要.记得在开始学习编程的时候,对数据结构没什么概念,感觉编程就是那么回事,不用数据结构也能编出一大堆程序,然而我只能说那都是些小孩子过家家玩的小程序而已,程序中几乎没有用到多少数据,无论你怎么存储,程序运行起来都是很快的.然而当你为工程应用去编写程序的时候,那都是处理大批的数据,那时候就不能随便乱存储数据了,必须根据实际情况选择一种合适的数据结构来存储数据,从而能

  • 深入理解Java 对象和类

    Java作为一种面向对象语言.支持以下基本概念: •多态 •继承 •封装 •抽象 •类 •对象 •实例 •方法 •消息解析 本节我们重点研究对象和类的概念. •对象:对象是类的一个实例,有状态和行为.例如,一条狗是一个对象,它的状态有:颜色.名字.品种:行为有:摇尾巴.叫.吃等. •类:类是一个模板,它描述一类对象的行为和状态. Java中的对象 现在让我们深入了解什么是对象.看看周围真实的世界,会发现身边有很多对象,车,狗,人等等.所有这些对象都有自己的状态和行为. 拿一条狗来举例,它的状态有

  • 一步步带你学习设计MySQL索引数据结构

    目录 前言 索引介绍 索引设计目标 索引设计迭代 迭代一 迭代二 迭代三 迭代四 迭代小结 索引结构总结 聚簇索引 非聚簇索引 联合索引 索引优点和缺点 优点 缺点 总结 前言 MySQL的索引是一个非常重要的知识点,也基本上是面试必考的一个技术点,所以非常重要.那你了解MySQL索引的数据结构是怎么样的吗?为什么要采用这样的数据结构? 现在化身为MySQL的架构师,一步步迭代设计出MySQL的索引结构,保证你再也忘记不了索引的结构了,轻松通过面试. 索引介绍 MySQL表中存储的数据量非常大,

  • Python中一些自然语言工具的使用的入门教程

    NLTK 是使用 Python 教学以及实践计算语言学的极好工具.此外,计算语言学与人工 智能.语言/专门语言识别.翻译以及语法检查等领域关系密切. NLTK 包括什么 NLTK 会被自然地看作是具有栈结构的一系列层,这些层构建于彼此基础之上.那些熟悉人工语言(比如 Python)的文法 和解析的读者来说,理解自然语言模型中类似的 -- 但更深奥的 -- 层不会有太大困难. 术语表 全集(Corpora):相关文本的集合.例如,莎士比亚的作品可能被统称为一个 文集(corpus): 而若干个作者

  • sqlserver中drop、truncate和delete语句的用法

    虽然小编不建议大家去用命令删除数据库表中的东西,但是这些删除命令总有用的着的地方. 说到删除表数据的关键字,大家记得最多的可能就是delete了 然而我们做数据库开发,读取数据库数据.对另外的两兄弟用得就比较少了 现在来介绍另外两个兄弟,都是删除表数据的,其实也是很容易理解的 老大------drop 出没场合:drop table  tb --tb表示数据表的名字,下同 绝招:删除内容和定义,释放空间.简单来说就是把整个表去掉.以后要新增数据是不可能的,除非新增一个表, 例如:一个班就是一个表

  • 详解Swift编程中的方法与属性的概念

    方法 在 Swift 中特定类型的相关联功能被称为方法.在 Objective C 中类是用来定义方法,其中作为 Swift 语言为用户提供了灵活性,类,结构和枚举中可以定义使用方法. 实例方法 在 Swift 语言,类,结构和枚举实例通过实例方法访问. 实例方法提供的功能 访问和修改实例属性 函数关联实例的需要 实例方法可以写在花括号 {} 内.它隐含的访问方法和类实例的属性.当该类型指定具体实例它调用获得访问该特定实例. 语法 复制代码 代码如下: func funcname(Paramet

  • 老生常谈进程线程协程那些事儿

    一.进程与线程 1.进程 我们电脑的应用程序,都是进程,假设我们用的电脑是单核的,cpu同时只能执行一个进程.当程序出于I/O阻塞的时候,CPU如果和程序一起等待,那就太浪费了,cpu会去执行其他的程序,此时就涉及到切换,切换前要保存上一个程序运行的状态,才能恢复,所以就需要有个东西来记录这个东西,就可以引出进程的概念了. 进程就是一个程序在一个数据集上的一次动态执行过程.进程由程序,数据集,进程控制块三部分组成.程序用来描述进程哪些功能以及如何完成:数据集是程序执行过程中所使用的资源:进程控制

  • bootstrap-treeview实现多级树形菜单 后台JSON格式如何组织?

    树形列表菜单的数据组织形式一般有两种:一种是一次性向服务器请求所有节点的数据,一种是先请求根目录的节点,当用户点击该节点时,再去请求该节点的子叶节点的数据.这里我们的需求是第一种. 树形菜单是我们经常会用到的一种菜单展现方式,这里我推荐bootstrap-treeview,它是一款效果非常酷的基于bootstrap的jQuery多级列表树插件.该jQuery插件基于Twitter Bootstrap,以简单和优雅的方式来显示一些继承树结构,如视图树.列表树等. 前端页面的编写,比较简单,故简略描

  • 面向对象三大特性的意义讲解

    面向对象的三大特性:封装.继承和多态.这是任何一本面向对象设计的书里都会介绍的,但鲜有讲清楚的,新手看了之后除了记住几个概念外,并没真正了解他们的意义.前几天在youtube上看了Bob大叔讲解的SOLID原则,其中有一段提到面向对象的三大特性,收获很多,但是我并不完全赞同他的观点,这里谈谈我的想法: 封装 『封装』第一层含义是信息隐藏.这是教科书里都会讲解的,把类或模块的实现细节隐藏起来,对外只提供最小的接口,也就是所谓的『最小知识原则』.有个共识,正常的程序员能理解的代码在一万行左右.这是指

随机推荐