标签:存在 invoke 属性 成员 key instance count 列表 microsoft
前言
runtime的资料网上有很多了,部分有些晦涩难懂,我通过自己的学习方法总结一遍,主要讲一些常用的方法功能,以实用为主,我觉得用到印象才是最深刻的。
另外runtime的知识还有很多,想要了解更多可以一些翻译的官方文档(有点枯燥)
什么是runtime?
runtime 是 OC底层的一套C语言的API(引入 <objc/runtime.h> 或<objc/message.h>),编译器最终都会将OC代码转化为运行时代码,通过终端命令编译.m 文件:clang -rewrite-objc xxx.m可以看到编译后的xxx.cpp(C++文件)。
比如我们创建了一个对象 [[NSObject alloc]init],最终被转换为几万行代码,截取最关键的一句可以看到底层是通过runtime创建的对象,OC中一切都被设计成了对象,我们都知道一个类被初始化成一个实例,这个实例是一个对象。实际上一个类本质上也是一个对象,在runtime中用结构体表示。如下:
//类在runtime中的表示 struct objc_class { Class isa;//指针,顾名思义,表示是一个什么, //实例的isa指向类对象,类对象的isa指向元类 #if !__OBJC2__ Class super_class; //指向父类 const char *name; //类名 long version; long info; long instance_size struct objc_ivar_list *ivars //成员变量列表 struct objc_method_list **methodLists; //方法列表 struct objc_cache *cache;//缓存 //一种优化,调用过的方法存入缓存列表,下次调用先找缓存 struct objc_protocol_list *protocols //协议列表 #endif } OBJC2_UNAVAILABLE; /* Use `Class` instead of `struct objc_class *` */
消息机制
另外利用runtime 可以做一些OC不容易实现的功能
动态交换两个方法的实现(特别是交换系统自带的方法)
动态添加对象的成员变量和成员方法
获得某个类的所有成员方法、所有成员变量
如何应用运行时?
1.将某些OC代码转为运行时代码,探究底层,比如block的实现原理;
2.拦截系统自带的方法调用(Swizzle 黑魔法),比如拦截imageNamed:、viewDidLoad、alloc;
3.实现分类也可以增加属性;
4.实现NSCoding的自动归档和自动解档;
5.实现字典和模型的自动转换。
创建一个Person类,类中实现以下两个类方法,并在.h 文件中声明
+ (void)eat{ NSLog(@"吃"); } + (void)study { NSLog(@"学习"); }
控制器中调用,则先打印吃,后打印学习
[Person run];
[Person study];
下面通过runtime 实现方法交换,类方法用class_getClassMethod ,对象方法用class_getInstanceMethod
// 获取两个类的类方法 Method m1 = class_getClassMethod([Person class], @selector(run)); Method m2 = class_getClassMethod([Person class], @selector(study)); // 开始交换方法实现 method_exchangeImplementations(m1, m2); // 交换后,先打印学习,再打印跑! [Person run]; [Person study];
可以看到输出的是2个交换过的信息
有时候会有这样的需求,我们需要知道当前类中每个属性的名字(比如字典转模型,字典的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]); }
在Xcode上看下输出吧,需要给你当前的类写几个属性,成员变量,方法和协议,不然获取的列表是没有东西的。
让我们看一下方法调用在运行时的过程吧
如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。
如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。
以上的过程给我带来的启发:
super
这个编译器标识,它会在运行时跳过在当前的类对象中寻找方法的过程。
在方法调用中说到了,如果没有找到方法就会转向拦截调用。
那么什么是拦截调用呢。
拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject
的四个方法来处理。
+ (BOOL)resolveClassMethod:(SEL)sel; + (BOOL)resolveInstanceMethod:(SEL)sel; //后两个方法需要转发到其他的类处理 - (id)forwardingTargetForSelector:(SEL)aSelector; - (void)forwardInvocation:(NSInvocation *)anInvocation;
NSInvocation
传给你。做完你自己的处理后,调用invokeWithTarget:
方法让某个target触发这个方法。重写了拦截调用的方法并且返回了YES,我们要怎么处理呢?
有一个办法是根据传进来的SEL
类型的selector动态添加一个方法。
首先从外部隐式调用一个不存在的方法:
/隐式调用方法 [target performSelector:@selector(resolveAdd:) withObject:@"111"];
然后,在target对象内部重写拦截调用的方法,动态添加方法。
void runAddMethod(id self, SEL _cmd, NSString *string){ NSLog(@"add C IMP ", string); } + (BOOL)resolveInstanceMethod:(SEL)sel{ //给本类动态添加一个方法 if ([NSStringFromSelector(sel) isEqualToString:@"resolveAdd:"]) { class_addMethod(self, sel, (IMP)runAddMethod, "v@:*"); } return YES; }
其中class_addMethod
的四个参数分别是:
Class cls 给哪个类添加方法,本例中是self SEL name 添加的方法,本例中是重写的拦截调用传进来的selector。 IMP imp 方法的实现,C方法的方法实现可以直接获得。如果是OC方法,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;获得方法的实现。 "v@:*" 方法的签名,代表有一个参数的方法。
众所周知,分类中是无法设置属性的,如果在分类的声明中写@property 只能为其生成get 和 set 方法的声明,但无法生成成员变量,就是虽然点语法能调用出来,但程序执行后会crash,有人会想到使用全局变量呢?比如这样:
int _age; - (int )age { return _age; } - (void)setAge:(int)age { _age = age; }
但是全局变量程序整个执行过程中内存中只有一份,我们创建多个对象修改其属性值都会修改同一个变量,这样就无法保证像属性一样每个对象都拥有其自己的属性值。这时我们就需要借助runtime为分类增加属性的功能了。
set方法,将值value 跟对象object 关联起来(将值value 存储到对象object 中)
参数 object:给哪个对象设置属性 参数 key: 一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节 参数 value: 给属性设置的值 参数 policy:存储策略 (assign 、copy 、 retain就是strong)
关联策略有以下几种:
enum { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403 };
关联步骤:先在.h 中@property 声明出get 和 set 方法,方便点语法调用;
@property(nonatomic,copy)NSString *name;
在.m 中重写set 和 get 方法,内部利用runtime 给属性赋值和取值
char nameKey; - (void)setName:(NSString *)name { // 将某个值跟某个对象关联起来,将某个值存储到某个对象中 objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)name { return objc_getAssociatedObject(self, &nameKey); }
标签:存在 invoke 属性 成员 key instance count 列表 microsoft
原文地址:http://www.cnblogs.com/sy-scholar/p/6250265.html