最近在研究runtime,思考良久觉得好像在项目中并不实用。在研究了一些第三方库,才知道runtime真是黑魔法。
OC的函数调用为消息发送机制,属于动态调用过程。在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
使用场景
利用runtime黑魔法我们可以实现很多意想不到的功能。
下面梳理下runtime的常用的使用场景:
- 动态的添加对象的成员变量和方法
- 动态交换两个方法的实现
- 实现分类也可以添加属性
- 实现NSCoding的自动归档和解档
- 实现字典转模型的自动转换
正式开练
1.Category添加属性
直接在Category中添加属性,是无效的。我们这里需要使用runtime来实现
eg:在写一个图片缓存控件时,需要给UIImageView添加imageURL属性。我们通过setter与getter方法来设置和获取属性,添加属性我们需要完成它的setter与getter方法。
#import "objc/runtime.h"
#import "UIImageView+WebCache.h"
static const void *imageURLKey = &imageURLKey;
@implementation UIImageView (WebCache)
- (NSString *)imageURL {
return objc_getAssociatedObject(self, imageURLKey);
}
- (void)setImageURL:(NSString *)imageURL {
objc_setAssociatedObject(self, imageURLKey, imageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
2.更改系统方法的实现
接手一个新工程,找到页面对应的UIViewController是很困难的(特别是在命名不规范情况下)。想实现进入那个页面就打印哪个页面进来了
创建一个 UIViewController 的分类,在+(void)load
中实现方法的交换。(+(void)load
只要类所在文件被引用就会被调用。所以如果类没有被引用进项目,就不会有load调用,方法只会被调用一次。)
#import "UIViewController+Extension.h"
#import <objc/runtime.h>
@implementation UIViewController (Extension)
+(void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//获取UIViewController的方法,objc_method结构体指针
Method system = class_getInstanceMethod([self class], @selector(viewWillAppear:));
Method change = class_getInstanceMethod([self class], @selector(viewWillAppearNew));
//获取新创建方法
Method systemDis = class_getInstanceMethod([self class], @selector(viewWillDisappear:));
Method changeDis = class_getInstanceMethod([self class], @selector(viewWillDisappearNew));
//交换两个方法的实现
method_exchangeImplementations(system, change);
method_exchangeImplementations(systemDis, changeDis);
});
}
- (void)viewWillAppearNew {
[self viewWillAppearNew];
#if TARGET_IPHONE_SIMULATOR
NSLog(@"AAAA%@页面出现了", [self class]);
#endif
}
- (void)viewWillDisappearNew {
[self viewWillDisappearNew];
#if TARGET_IPHONE_SIMULATOR
NSLog(@"AAAA%@页面消失了", [self class]);
#endif
}
@end
3. objc_msgSend的作用
OC中像对象发送消息id returnValue = [someObject messageName:parameter];
在运行时会将其转换成C语言函数
id returnValue = objc_msgSend(someObject,
@selector(messageName:),
parameter);
void objc_msgSend(id self, SEL cmd, ...)
是消息转发机制的核心函数。
在Objective-C中,如果向某对象传递消息,那就会使用动态绑定机制来决定需要调用的方法。在底层,所有方法都是普通的C语言函数,然而对象收到消息之后,究竟该调用哪个方法则完全于运行期决定,甚至可以在程序运行时改变,这些特性使得Objective-C成为一门真正的动态语言。
由于objc_msgSend函数本身是无返回值无参数的函数, 所以要给它强制转换类型代码如下:
Person *person = [[Person alloc]init];
((void (*) (id, SEL)) (void *)objc_msgSend)(person, @selector(say));
4. runtime实现自定义对象归档
先熟悉两个方法
/**
* Ivar 属性成员
* @param Class 当前对象的类
* @param outCount 无符号整型的变量指针,存储属性的个数
* @return IvarList属性数组
*/
OBJC_EXPORTIvar *class_copyIvarList(Class cls, unsigned int *outCount)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
/**
* 返回一个实例变量的名字.
*
* @param v 实例变量.
*
* @return A c类型的字符串 变量名.
*/
OBJC_EXPORTconst char *ivar_getName(Ivar v)
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);
#import "NSObject+Archiver.h"
#import <objc/runtime.h>
@implementation NSObject (Archiver)
//解档
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [self init]) {
unsigned int nCount = 0;
//获取类中所有成员变量名
Ivar *ivar = class_copyIvarList([self class], &nCount);
for (int i = 0; i<nCount; i++) {
Ivar iva = ivar[i];
const char *name = ivar_getName(iva);
//成员变量名
NSString *strName = [NSString stringWithUTF8String:name];
//进行解档取值
id value = [decoder decodeObjectForKey:strName];
//利用KVC对属性赋值(判断不为空)
if (![value isKindOfClass:[NSNull class]] && value != nil) {
[self setValue:value forKey:strName];
}
}
free(ivar);
}
return self;
}
//归档
- (void)encodeWithCoder:(NSCoder *)encoder
{
unsigned int nCount;
Ivar *ivar = class_copyIvarList([self class], &nCount);
for (int i=0; i<nCount; i++) {
Ivar iva = ivar[i];
const char *name = ivar_getName(iva);
//成员变量名
NSString *strName = [NSString stringWithUTF8String:name];
//利用KVC取值
id value = [self valueForKey:strName];
//把取出的属性值进行归档
[encoder encodeObject:value forKey:strName];
}
free(ivar);
}
@end
使用这个分类 所有的类都可以直接实现NSCodeing协议。
网友评论