一、首先,从 runtime.h头文件中找到对 class 与 object 的定义
typedef struct objc_class *Class;
struct objc_object {
Class isa;
};
typedef struct objc_object *id;
由此可见,Class是一个指向objc_class结构体的指针,而id是一个指向objc_object结构体的指针,其成员isa是一个指向objec_class结构体的指针。
二、下面我们再看看头文件中关于objc_class的定义
truct objc_class {
Class isa; // 指向metaclass
Class super_class ; // 指向其父类
const char *name ; // 类名
long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
struct objc_method_list **methodLists ; // 方法列表,与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
struct objc_cache *cache; // 缓存,一种优化,调用过的方法存入缓存列表,下次调用先找缓存
struct objc_protocol_list *protocols; // 存储该类遵守的协议 协议列表
}
由此可见,类比对象的结构体中多了众多的成员,下面详细介绍下objec_class中各成员:
isa:objec_object(对象)中isa指针指向的类结构称为class(也就是该对象所属的类),其中存放着普通成员变量与对象方法 (“-”开头的方法);然而此处isa指针指向的类结构称为metaclass,其中存放着static类型的成员变量与static类型的方法 (“+”开头的方法)。
super_class: 指向该类的父类的指针,如果该类是根类(如NSObject或NSProxy),那么super_class就为NULL。
1、当我们调用某个对象的对象方法时,它会首先在自身isa指针指向的类(class)methodLists中查找该方法,如果找不到则会通过class的super_class指针找到其父类,然后从其methodLists中查找该方法,如果仍然找不到,则继续通过 super_class向上一级父类结构体中查找,直至根class;
2、当我们调用某个类方法时,它会首先通过自己的isa指针找到metaclass,并从其methodLists中查找该类方法,如果找不到则会通过metaclass的super_class指针找到父类的metaclass结构体,然后从methodLists中查找该方法,如果仍然找不到,则继续通过super_class向上一级父类结构体中查 找,直至根metaclass;
三.我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)
首先创建一个Person类。.h和.m文件代码如下:
#import <Foundation/Foundation.h>
@interface Person : NSObject
-(void)ButtonClicked1;
+(void)ButtonClicked2;
@end
#import "Person.h"
@interface Person (){
NSString *_age;
}
@property (nonatomic, strong)NSString *name;
@property (nonatomic, assign)int no;
@end
@implementation Person
-(void)ButtonClicked1{
NSLog(@"ButtonClicked1");
}
+(void)ButtonClicked2{
NSLog(@"ButtonClicked2");
}
- (id)init
{
if (self = [super init]) {
self.name = @"wengzilin";
_age = @"27";
}
return self;
}
- (NSString *)description
{
NSLog(@"name:%@, age:%@", self.name, _age);
return [NSString stringWithFormat:@"name:%@, age:%@", self.name, _age];
}
@end
然后在ViewController中,引入:
#import <objc/runtime.h>
#import "Person.h"
1.获取一个类的所有属性:
unsigned int count = 0 ;
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([Person class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}
/* 打印结果:
2016-03-08 14:57:21.471 RunTimeDemo1[3108:936300] property---->name
2016-03-08 14:57:21.471 RunTimeDemo1[3108:936300] property---->no
*/
2.获取成员变量列表
Person *per = [[Person alloc]init];
NSLog(@"before runtime:%@",[per description]);
Ivar *ivarLists = class_copyIvarList([Person class], &count);
for (unsigned int i; i<count; i++) {
Ivar myIvar = ivarLists[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
/* 打印结果:
2016-03-08 15:23:11.702 RunTimeDemo1[3731:1091064] before runtime:name:wengzilin, age:27
2016-03-08 15:23:11.702 RunTimeDemo1[3731:1091064] Ivar---->_age
2016-03-08 15:23:11.702 RunTimeDemo1[3731:1091064] Ivar---->_no
2016-03-08 15:23:11.702 RunTimeDemo1[3731:1091064] Ivar---->_name */
/*我们知道,正常情况下,在这个控制器里面是访问不到Person中的私有变量和属性的,但是对于runtime,不管是私有的还是公有的成员变量和属性,runtime都可以将之访问并修改掉,例如:修改掉person的私有成员变量age的值
为什么会有上面的输出结果,因为@property会做三份工作:
1.生成一个带下划线的成员变量
2.生成这个成员变量的get方法
3.生成这个成员变量的set方法
因此会输出三个成员变量_no、_age和_name。需要注意的是属性名是不带下划线的,和定义时的名字一样。因此可以说:ivarList可以获取到@property关键字定义的属性 ,而propertyList不可以获取到成员变量。也就是:使用ivarList是可以将所有的成员变量和属性都获取的。
*/
//修改person的私有成员变量的值
Ivar age = ivarLists[0];
object_setIvar(per, age, @"20");
NSLog(@"after runtime:%@",[per description]);
/*
打印结果:
2016-03-08 15:23:11.702 RunTimeDemo1[3731:1091064] after runtime:name:wengzilin, age:20
即成功的将person的私有成员变量的值给改变了
*/
3.获取方法列表
unsigned int count = 0;
//获取方法列表 遍历某个类所有的方法
Method *methodList = class_copyMethodList([Person class], &count);//所有在.m文件显式实现的方法都会被找到
for (unsigned int i; i<count; i++) {
SEL name = method_getName(methodList[i]);//读取一个Method类型的变量,输出我们在上层中很熟悉的SEL
NSString *methodName = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
NSLog(@"member method:%@", methodName);
/* 打印结果:
2016-03-08 15:32:42.588 RunTimeDemo1[3883:1144407] member method:ButtonClicked1
2016-03-08 15:32:42.588 RunTimeDemo1[3883:1144407] member method:no
2016-03-08 15:32:42.588 RunTimeDemo1[3883:1144407] member method:setNo:
2016-03-08 15:32:42.588 RunTimeDemo1[3883:1144407] member method:.cxx_destruct
2016-03-08 15:32:42.588 RunTimeDemo1[3883:1144407] member method:description
2016-03-08 15:32:42.589 RunTimeDemo1[3883:1144407] member method:name
2016-03-08 15:32:42.589 RunTimeDemo1[3883:1144407] member method:setName:
2016-03-08 15:32:42.589 RunTimeDemo1[3883:1144407] member method:init
*/
不但能得到普通方法,还能将生成的get方法和set方法也打印出来
4.获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([Person class], &count);
for (unsigned int i; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}