OC是基于C语言的面向对象的语言。C语言中没有对象的概念,为了便于开发者理解和使用面向对象的思想,OC将C语言中的结构体进行了封装,创造了oc中的类和对象等概念。
Class
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
从定义看,Class其实就是一个结构体objc_class的指针。Class指向了结构体objc_class的内存地址。
而结构体objc_class的里面,包含了:
- isa指针
类本身也是一个对象,类对象的isa指针指向的是元类meta class。 - 指向父类的指针
类可以通过这个指针,找到其父类。 - 类名
- 版本
- 信息
- 实例大小
- 指向结构体bjc_ivar_list的指针(属性列表)
- 方法列表
- 缓存
- 协议
对象
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
代表了一个类的实例对象。
她只有一个isa指针,指向了它所属的类。
当用这个类调用方法的时候,实例对象会通过isa指针找到它所属的类,然后在该类的方法缓存列表里去查找对应的方法,如果找不到就去方法列表里找,如果还找不到就去父类的里面找……
对于对象的实例变量之前一直不能理解。因为类的结构体里存放了一个ivars,一直以为变量的指针也是在类里,但是每一个对象都有自己的实例变量,所以一直不明白这个变量的值到底是怎么存的。
现在我的理解是,类的结构体里存放的都是实例变量的名字,偏移量(编译的时候已经固定好了。)信息。然后我们给实例对象的变量赋值的时候,系统是根据我们的名字,找到对应的ivar,然后通过ivar里的偏移量,计算出来该变量的内存地址(实例对象的内存地址+偏移量),然后进行存取。
分类
struct objc_category {
char * _Nonnull category_name OBJC2_UNAVAILABLE;
char * _Nonnull class_name OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable instance_methods OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable class_methods OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
分类是运行时才去加载的。从结构看,它支持添加实例方法,类方法以及属性。
需要注意的是,分类添加的方法会和之前的类的方法放在一起,并且会放在之前的方法的前面。因为运行时查找方法的时候是按照顺序查找的,所以当分类的方法和类的方法重复的时候,会优先使用分类的方法。
分类是可以添加属性的。
我们正常情况下给类生成的属性,编译器会默认做三件事:1.生成一个实例变量 2.生成对应的setter和getter方法。
然而分类是无法添加实例变量的。因为类的内存布局是在编译的时候就完成了,类的结构体里有instance_size,并且每个变量都有对应的偏移地址,这些是在编译后就已经确定的了。系统使用变量的时候是根据这个偏移量来查找的。所以苹果是不允许分类添加变量的。
分类的好处是:1.可以将一些库的私有化方法暴露出来使用。2.分散代码逻辑 3.减少类文件的体积。
method
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
method,iOS开发都是成为方法,和其他语言的函数是一个意思。
不过iOS的method内容还是比较独特的。从结构体看,一个method,包含了三个东西:SEL,Types,IMP。
SEL 方法名(或称为方法编号)
一般可以通过@selector(selector) 和 NSSelectorFromString(@"customClick") 来创建。其实就是类似于函数指针。因为oc不支持函数指针作为参数传递,因此使用SEL包裹一层来传递
IMP 方法的实现 函数指针,指向了方法的地址。
感觉SEL就是类似于key,IMP是value。 objc_msgSend发送消息的时候,通过SEL找到value,去调用。
runtime提供了一些api可以对imp进行操作。比如获取imp,交互两个方法的imp ,设置一个方法的imp。
我们可以利用这些,做到方法替换等等骚操作。
网友评论