Objective-C 语言是一门面向对象的语言,Objectvie-C语言的底层是由c/c++和部分汇编语言实现的,所以Objetive-C的语言特性及其能够应用的场景也由这些具体的实现的结构所决定。在我看来,学习iOS编程,要深入学习这三条主线:(1)以objective-C 语言特性为主的底层原理学习。(2)由底层的Runtime机制实现的 Objective-C的动态性的学习(3)学习由底层由RunLoop机制维护iOS 程序的正常运行 的相关知识。
其实仔细想想,我们在编写app程序的过程中或者说编写任意的程序的实现中,假如使用的是面向对象的语言,其过程无非是(1)创建类,(2)设计类的接口(3)实现类的接口(4)完成类和类之间如何交互的设计。(5)将设计好的类结合程序所运行的环境所对象的 适配,或者说结合或利用程序所运营的环境的机制,去设计我们的类。 我们要想创建好并且管理好一个类,就要清晰明白的理解一种语言在其底层是如何来形容描述一个类的,类的结构是如何的,这个结构是为了实现怎样的语言特性而设计的,这样我们才能创建类,并设计好类的接口。而一个完成的应用程序的构建,不止需要对语言特性的理解,还需要对程序所运行的环境的机制又一个完整的理解,这样我们设计好的类,设计好的交互才能更好的适应运行的环境,并出色的运行。所以要创建好一个类就需要了解一种语言对如何实现一个类,这样我们能才能正确的编写出一个类;要设计好一个类的接口,就需要明白语言的特性,利用语言特性设计出更好用的类。至于类和类之间如何交互的问题,其实最主要的原则就是要设计出一套高内聚,低耦合的类的关系,这需要我们学习并了解常用的设计模式,以此来完成这个目标。在设计模式的基础上,在开发大型的应用程序时,我们还要选用三层或四层架构的架构方式,来更好的管理和拓展我们的程序。可以说架构设计是为了合理管理各个模块,从而使整个app实现高内聚,低耦合;而设计模式的使用是为了更好的设计一个模块,使一个模块的内部更好的实现高内聚,低耦合。而类的设计则是设计模式实现的基础。
Objective-C 语言中包含三种对象,instance对象(实例对象),类对象,元类对象。其中元类对象与类对象在Objective-C中数据结构基本一致,可认为是同一种对象。这三种对象的底层的是由c语言的结构体实现的。
一:instance 对象(实例对象)
instance 对象是我们在编程中最常用的对象之一,使用频率非常高,弄清楚instance对象的底层结构,更容易使我们在编写代码的过程中更好的使用实例对象编写出高质量的代码。

Instance对象中并不保存成员变量的类型和对象方法,这些相关信息保存在类的对象的结构中,因为这些类型信息和方法对于所有的instance对象来说是一样的,并不存在区别,所以不需要保存区别保存,只需要在内存中保存一份即可。
Instance对象c语言结构体大致如下:
struct NSObject_IMPL {
Class isa;
成员变量1;
成员变量2;
…
成员变量n;
}
(1)isa 指针
结构体的isa指针指向类对象。他的值即是类对象在堆中的具体地址。另外需要注意的是,iOS 在64bit环境中(iPhone 5s,2013年,iOS 7,a7处理器(64位架构)),对isa 指针做了内存优化,使其8个字节的内存中保存了更多的信息,所以类对象在内存中实际的地址现在是isa &ISA_MASK。ISA_MASK是一个掩码,通过位操作,可以得到33位的地址值(后三位为000,具体原因由掩码结构决定)。
在arm64 架构之前,isa就是一个普通的指针,存储着Class,meta-class对象的内存地址
从arm64 架构开始,对isa进行了优化,变成了一个共用体(union)结构。还使用位域来存储更多的信息 。
Arm64架构,isa指针构成如下:
union isa_t {
Class cls;
uintptr bits;
struct {
uintptr_t nonpointer :1; 0 代表普通指针,1代表优化过的指针
unitptr_t has_assoc. :1; 代表有没有关联对象,有释放比较慢
unitptr_t has_cxx_dtor :1; 代表是否有c++析构函数
unitptr_t shiftcls :33; 存放着class,meta-clas的地址
unitptr_t magic :6; 在调试是分析对象是否进行过初始化
unitptr_t weakly_referenced :1; 是否有弱指向 指向过,
unitptr_t reallocating :1; 对象是否正在释放
unitptr_t extra_rc :19; 里面存储的值时引用计数减1;
}
}
(2)isa 指针相关->Tagged Pointer 技术 理解

从64bit开始,iOS 引入了Tagged Pointer技术,用于优化NSNumer,NSDate,NSString等等小对象的 存储。
在没有使用tagged pointer之前,NSNumber等对象需要动态分配内存,维护引用计数等,NSNumber指针存储的是堆中的NSNumber对象的地址值。
使用tagged Pointer之后,NSNumber指针里面存储的数据变成了 tag+data,也就是将数据直接存储在了指针中。
当指针不够存储数据时,才会使用动态分配内存的方式来存储数据
objc_msgSend能识别tagged pointed ,比如NSNumber的initValue方法,节省了以前的调用开销。
如何判断一个指针是否为tagged Pointer?
iOS 平台,最高有效位 是1 (第64bit)
(3)instance 对象内存理解:
一个NSObject 对象占用多少内存可由class_getInstanceSize函数获取,其他对象具体占用多少内存,取决于对象成员变量的多少以及系统内存对齐机制所决定。
在程序中,当我们生成一个instance对象时(发送alloc消息时,相当于c语言的malloc函数)。 系统会在堆空间中开辟一块内存用来保存我们生成的对象的结构体。
二:class 类对象
类对象是我们程序组成的最主要部分,程序的架构模式,以及设计模式的使用都依赖与对类的结构的设计,我认为开发中如果 要想利用语言的写出高耦合,低内聚的程序,必须要首先理解Objective-C中一个class中究竟包含了什么,一个class可以提供给我们哪些接口,我们可以运用类的特性做那些事情,这样我们才能在编写代码的过程中更有的放矢,更有效的完成OC代码的编写。

(1)class中存放了些什么?
类对象结构如下:
struct objc_class {
Class isa;
Class superclass;
cache_t cache; //方法缓存
class_data_bits_t bits; // 用于获取具体的类信息
}
每个类在内存中有且只有一个class 对象。
类对象存放有isa 指针,superclass 指针,类的属性信息,类的对象方法信息,类的协议信息,类的成员变量信息。
(2)isa
isa指针 存储类对象在内存中的地址(和其他类相关的信息),同样需要通过位运算& &ISA_MASK 获取实际地址。

(3)superclass
superclass 存储该类对象的父类的地址。在消息发送的过程中,如果当前类没有一个消息的对应实现,可以通过superclass 去父类的缓存和方法列表中查找。

(4)cache 结构体
cache结构体存放的是方法缓存的结构,是一个散列表,在消息发送过程中会使用这个结构进行方法查找,便于方法的快速查找,调用。

cache_t 的结构如下:
struct cache_t {
struct bucket_t *_buckets; // 散列表
mask_t _mask: // 散列表的长度-1
mask_t _occupied;// 已经缓存的方法数量
}
struct bucket_t 结构
struct bucket_t {
cache_key_t _key;//SEL 作为key
IMP _imp;// 函数内存地址
}
(5)class_data_bits_t bits; // 用于获取具体的类信息
bits 变量是一个地址值,通过位运算& FAST_DATA_MASK 可以得到类的具体信息。
通过位运算 可以得到一个class_rw_t的结构体,类的相关结构存储在这个结构体中。结构体中具体组成如下:
struct class_rw_t {
unit32_t Flags;
unint32_t version;
const class_ro_t *ro; // 该结构体指针指向一个class_ro_t 的结构体。class_ro_t结构体可理解为类对象在编译后,未经过runtime 处理的类的类结构体。
method_list_t *methods; // 存放方法列表
property_list_t *properties; //存放属性列表
const protocol_list_t *protocols; // 存放协议列表
Class fistSubclas;
Class nextSIblingClass;
char * demangledName;
}
c lass_rw_t里面的methods,properties,protocols是二维数组,是可读可写的,包含了 类的初始内容,分类的内容。
(6)方法列表结构及其实现

可理解为 数组中的每个元素都是一个数组,每个元素里面存放的是 类的原始方法列表 或者 某个分类的所有方法列表。第二维数组中的每个元素的类型为method_t;
(7)method_t 结构体
method_t的底层结构为:
struct method_t {
SEL name; // 函数名
const char *types;//函数编码(返回值类型,参数类型)
IMP imp ; // 指向函数的指针(函数地址)
}
SEL代表方法\函数名,一般叫做选择器,底层结构跟char *类似
可以通过@selector()和sel_registerName()获得
可以通过sel_getName()和NSStringFromSelector()转成字符串
不同类中相同名字的方法,所对应的方法选择器是相同的

三:元类对象 meta-class
每个类在内存中有且只有一个meta-class 对象
Meta-class 对象和class 对象的内存结构是一样的,但是用途不一样。
元类对象中,保存有isa指针,superclass 指针,以及类的类方法 信息。具体结构参看类结构信息。
可以通过class_isMetaClass()函数查看一个对象是否是元类对象。
关于isa 和superclas的总结

(1)instance对象的isa指向class对象
(2)class对象的isa指向meta-class 对象
(3)meta-class对象的isa指向基类的meta-calss对象
(4)NSObject 基类的meta-class指向自身。
(5)meta-class的superclass指向父类的meta-class
基类的meta-class的superclass 指向基类的class
instance 调用对象的轨迹
isa 找到class,方法不存在,就通过superclass 找父类
Class 调用类方法的轨迹
isa 找meta-class,方法不存在,就通过superclass找父类
四:对象的内存管理理解,以NSObject * obj = [[NSObject alloc]init];语句为例。
关于 NSObject * obj = [[NSObject alloc]init];语句详介。
前半句NSObject * obj ,仅仅是声明了1个指针变量而已.这个指针变量的类型是NSObject*.
obj是1个局部的变量.所以obj指针变量是存储在栈区的.
obj是1个指针变量,所以这个变量中只能存储地址.
本质上来讲.obj是1个指针变量 不是1个对象.
后半句[[NSObject alloc]init];
这句话,才是在真正的创建对象.
(1)alloc消息做的事情.
a. 在堆内存中申请一块合适大小的空间.
b. 在申请的这块空间中根据类的模板创建对象.
类中有哪些属性.就把类的属性依次的挨个的一个不落的声明在这个对象中.
对象中除了有类中定义的属性之外,还有1个属性叫做isa 这是1个指针.
这个isa指针指向代码段中的类.
(2)init消息做的事情
初始化对象的属性.为对象的属性赋默认值
-> 如果属性的类型是基本数据类型.就赋值为0
-> 如果属性的类型是C指针类型.就赋值为NULL
-> 如果属性的类型是OC指针类型.就赋值为nil
当 alloc 和init消息工作完成后,会返回这个对象在堆空间中的地址,并将这个地址,赋值给obj指针。 obj指针指向了堆空间中的NSObject对象.obj指针的值是对象在堆中的地址。
NSObject对象中的isa指针指向类对象。他的值即时类对象在堆中的具体地址。另外需要注意的是,iOS 在64bit环境中,对isa 指针做了内存优化,使其8个字节的内存中保存了更多的信息,所以类对象在内存中实际的地址现在是isa &ISA_MASK。ISA_MASK是一个掩码,通过位操作,可以得到33位的地址值(后三位为000,具体原因由掩码结构决定)。
一个NSObject 对象占用多少内存可由class_getInstanceSize函数获取,其他对象具体占用多少内存,取决于对象成员变量的多少以及系统内存对齐机制所决定。
网友评论