Runtime概念:
-
OC是一门动态语言,所以它需要一个运行时系统.OC中一切都被设计成了对象,我们都知道一个类被初始化成一个实例,这个实例是一个对象。类的本质上也是一个对象,在runtime中用结构体表示.
对象关联:
-
1.作用:允许开发者对已经存在的类在分类中添加自定义的属性
-
2.重写set方法用到的方法
objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, <#objc_AssociationPolicy policy#>)
实例:
const char *KEY = "key";
- (void)setLastImageAddr:(NSString *)lastImageAddr{
//设置关联对象,就是给该分类增加属性,key是set方法中存入的这个值得key.然后在get方法中利用这个key又可以把这个值给取出来.相当于给该分类增加了 成员变量 和对应的 setter和getter方法
//第一个 :关联的key
NSLog(@"使用了runtime");
objc_setAssociatedObject(self, KEY, lastImageAddr, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
-
3.重写get方法用到的方法
objc_getAssociatedObject(<#id object#>, <#const void *key#>)
实例:
- (NSString *)lastImageAddr{
//
return objc_getAssociatedObject(self, KEY);
}
获取属性列表
我们可以通过runtime的一系列方法获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表,也能获取到一些私有属性和方法)。
unsigned int count;
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}
//获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i<count; i++) {
Method method = methodList[i];
NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
//获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
//获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
方法调用
方法调用在运行时的过程:
-
如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。
-
首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行。
-
如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
-
如果没找到,去父类指针所指向的对象中执行1,2.
-
以此类推,如果一直到根类还没找到,转向拦截调用。
-
如果没有重写拦截调用的方法,程序报错。
-
-
以上的过程给我带来的启发:
-
重写父类的方法,并没有覆盖掉父类的方法,只是在当前类对象中找到了这个方法后就不会再去父类中找了。
-
如果想调用已经重写过的方法的父类的实现,只需使用super这个编译器标识,它会在运行时跳过在当前的类对象中寻找方法的过程。
-
拦截调用
如果没有找到方法就会转向拦截调用。拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后两个方法需要转发到其他的类处理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
-
第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
-
第二个方法和第一个方法相似,只不过处理的是实例方法。
-
第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
-
第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。
动态添加方法
class_addMethod(Class cls, SEL name,IMP imp, "v@:*");
-
1.Class cls 给哪个类添加方法,本例中是self
-
2.SEL name 添加的方法,本例中是重写的拦截调用传进来的selector。
-
3.IMP imp 方法的实现,C方法的方法实现可以直接获得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。
-
4."v@:*"方法的签名,代表有一个参数的方法。
方法交换
自动归档
通过 Runtime 我们就可以轻松解决这个问题:1.使用 class_copyIvarList 方法获取当前 Model 的所有成员变量.2.使用 ivar_getName 方法获取成员变量的名称.3.通过 KVC 来读取 Model 的属性值(encodeWithCoder:),以及给 Model 的属性赋值(initWithCoder:)