- struct objc_class {
- Class isa OBJC_ISA_AVAILABILITY;
- #if !__OBJC2__
- Class super_class OBJC2_UNAVAILABLE; // 父类
-
- const char *name OBJC2_UNAVAILABLE; // 类名
-
- long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
-
- long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
-
- long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
-
- struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
-
- struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
-
- struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
-
- struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
-
- #endif
- } OBJC2_UNAVAILABLE;
1.成员变量操作函数,主要包含以下函数:
- // 获取类中指定名称实例成员变量的信息
- Ivar class_getInstanceVariable ( Class cls, const char *name );
- // 获取类成员变量的信息
- Ivar class_getClassVariable ( Class cls, const char *name );
- // 添加成员变量
- BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
- // 获取整个成员变量列表
- Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
class_getInstanceVariable函数,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。
class_getClassVariable函数,目前没有找到关于Objective-C中类变量的信息,一般认为Objective-C不支持类变量。注意,返回的列表不包含父类的成员变量和属性。
Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。但如果我们通过运行时来创建一个类的话,又应该如何给它添加成员变量呢?这时我们就可以使用class_addIvar函数了。不过需要注意的是,这个方法只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<<alignment。这取决于ivar的类型和机器的架构。如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))。
class_copyIvarList函数,它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。
2.属性操作函数,主要包含以下函数:
- // 获取指定的属性
- objc_property_t class_getProperty ( Class cls, const char *name );
// 获取属性列表- objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
- // 为类添加属性
- BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
- // 替换类的属性
- void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
这一种方法也是针对ivars来操作,不过只操作那些是属性的值。
方法(methodLists)
objc_method_list方法链表中存放的是该类的成员方法(-方法),类方法(+方法)存在meta-class的objc_method_list链表中。
方法操作主要有以下函数:
- // 添加方法
- BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
- // 获取实例方法
- Method class_getInstanceMethod ( Class cls, SEL name );
- // 获取类方法
- Method class_getClassMethod ( Class cls, SEL name );
- // 获取所有方法的数组
- Method * class_copyMethodList ( Class cls, unsigned int *outCount );
- // 替代方法的实现
- IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
- // 返回方法的具体实现
- IMP class_getMethodImplementation ( Class cls, SEL name );
- IMP class_getMethodImplementation_stret ( Class cls, SEL name );
- // 类实例是否响应指定的selector
- BOOL class_respondsToSelector ( Class cls, SEL sel );
class_addMethod的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用method_setImplementation。一个Objective-C方法是一个简单的C函数,它至少包含两个参数—self和_cmd。所以,我们的实现函数(IMP参数指向的函数)至少需要两个参数,如下所示:
- void myMethodIMP(id self, SEL _cmd)
- {
- // implementation ....
- }
与成员变量不同的是,我们可以为类动态添加方法,不管这个类是否已存在。
另外,参数types是一个描述传递给方法的参数类型的字符数组,这就涉及到类型编码,我们将在后面介绍。
这里我们的void的前面没有+、-号,因为只是C的代码。
class_getInstanceMethod、class_getClassMethod函数,与class_copyMethodList不同的是,这两个函数都会去搜索父类的实现。
class_copyMethodList函数,返回包含所有实例方法的数组,如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)(一个类的实例方法是定义在元类里面)。该列表不包含父类实现的方法。outCount参数返回方法的个数。在获取到列表后,我们需要使用free()方法来释放它。
class_replaceMethod函数,该函数的行为可以分为两种:如果类中不存在name指定的方法,则类似于class_addMethod函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation一样替代原方法的实现。
class_getMethodImplementation函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分。
class_respondsToSelector函数,我们通常使用NSObject类的respondsToSelector:或instancesRespondToSelector:方法来达到相同目的。
缓存(cache)
用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,提高了调用的效率。
协议(objc_protocol_list)
协议相关的操作包含以下函数:
- // 添加协议
- BOOL class_addProtocol ( Class cls, Protocol *protocol );
- // 返回类是否实现指定的协议
- BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
- // 返回类实现的协议列表
- Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
-
class_conformsToProtocol函数可以使用NSObject类的conformsToProtocol:方法来替代。
class_copyProtocolList函数返回的是一个数组,在使用后我们需要使用free()手动释放。
objc_class结构体的应用
没有实际应用的知识讲解都是耍流氓
@property的本质
这里有一个孙源的面试题是:@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的。
简单点说就是:@property = ivar + getter + setter;
也就是生成实例变量及对应的存取方法。
详细的回答请看面试题第六题。
那这跟我们这里所讲的objc_class结构体有什么关系呢?
因为@property对应的ivar、getter和setter都会对应添加到我们结构体中的ivar_list、method_list中。也就是说我们每次增加一个属性,系统都会在ivar_list添加一个成员变量的描述,在 method_list 中增加 setter 与 getter 方法的描述。
其他Runtime结构体
objc_object结构体
除了类有对应的结构体,对象也有对应的结构体。
typedef struct objc_object *id;
id就是指向对象对应的结构体。对象的结构体只有 isa 指针,指向它所属的类。而类的结构体也有 isa 指针指向它的元类。
所以在OC中objc_class 结构体是继承自 objc_object:
- struct objc_object {
- Class isa OBJC_ISA_AVAILABILITY;
- };
- struct objc_class : objc_object {
- Class superclass;
- cache_t cache; // formerly cache pointer and vtable
- class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
- class_rw_t *data() {
- return bits.data();
- }
- };
Category结构体
Category的定义如下:
- typedef struct objc_category *Category;
Category是一个objc_category结构体的指针,objc_category的定义如下:
- struct objc_category {
- char *category_name OBJC2_UNAVAILABLE;
- char *class_name OBJC2_UNAVAILABLE;
- struct objc_method_list *instance_methods OBJC2_UNAVAILABLE;
- struct objc_method_list *class_methods OBJC2_UNAVAILABLE;
- struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
- } OBJC2_UNAVAILABLE;
通过上面的结构体,大家可以很清楚的看出存储的内容。我们继续往下看,打开objc源代码,在 objc-runtime-new.h中我们可以发现如下定义:
- struct category_t {
- const char *name;
- classref_t cls;
- struct method_list_t *instanceMethods;
- struct method_list_t *classMethods;
- struct protocol_list_t *protocols;
- struct property_list_t *instanceProperties;
- };
上面的定义需要提到的地方有三点:
name 是指 class_name 而不是 category_name
cls是要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对应到对应的类对象
instanceProperties表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的
作者:齐滇大圣
链接:https://www.jianshu.com/p/73e454178e77
來源:简书