概述
iOS运行时(RunTime)系统以前一直听说,也自己看到各种大牛的技术博客学习过,自己也曾经敲过相关的代码,但是发现因为自己没有好好记录,因为学习不是一次性就可以完成的,导致效率并不高,不能很快的从上次学习的位置继续。我个人不擅长理论知识论述,关于运行时系统的理论相信网上已经有很多大牛已经详细介绍过了,我自己更多的是总结学习。所以,自己建立了个可以长久积累知识的工程,用来记录知识点,并且以代码为导向,通过用运行时的方法实现一些功能,达到验证理论知识和学习的目的,以后还是会增加新的代码的,希望和大家一起分享。因为工程内容主要是对理论知识的验证和运用,所以没有华丽的界面,所以的内容都是在控制台输出的。相信自己的注释在逻辑方法足够详细,也许对理论知识注释有所欠缺,但是相信每个人都能够根据上下文猜懂
链接:https://github.com/xiaobai1993/RunTimeTest
下面的类是BOOK类,这个类是自己主要验证运行时的一些方法所用的测试类,比如下面代码注释可以看到的,获取,修改类的属性,方法个数,参数和返回值等,并且通过这个类只声明了一些方法,并没有实现,通过运行时动态的转发机制处理错误,在不同的阶段分别解决崩溃问题。
@interface BOOK : NSObject
{
NSString * ivarMember;//成员变量
}
@property (nonatomic,strong) NSString * name;
@property (nonatomic,assign) CGFloat price;
@property (nonatomic,strong) NSArray * authors;
@property (nonatomic,assign) int pubishYear;
//---本类定义并且已经实现了的方法
-(void)printMessage;
//------运行时方法
-(NSArray*)allPropertyNames;//获取所有的属性变量
-(NSArray*)allPropertyAttr;//获取所有的属性变量的具体信息
-(NSArray*)allIvarNames;//所有的成员变量
-(void)allMethods;
//--测试运行时调用类中存在并实现的方法,这三个方法可以不再这里声明。运行时也能找到,并调用.
//第一层次
-(void)executeMethodByRunTime_00;
-(void)executeMethodByRunTime_01:(NSString *)param;
-(NSString *)executeMethodByRunTime_02:(NSString *)param;
//第二层次,调用没有实现的方法,利用运行时解决崩溃问题。
//这个房没有实现,只有声明,包含了两个参数和返回值,正常在外面调用时崩溃的,现在尝试用运行时采用多种不同的方式处理崩溃。并执行某些方法
// resolveInstanceMethod 阶段解决未定义的方法
-(NSString *)onlyDeclareMethod:(NSString *)first andWithSec:(NSString*)second;
//测试在 - (id)forwardingTargetForSelector:(SEL)aSelector由其他类处理响应这个方法
-(NSString *)onlyDeclareMethod2:(NSString *)first andWithSec:(NSString*)second;
//测试在
-(NSString *)onlyDeclareMethod3:(NSString *)first andWithSec:(NSString*)second;
下面是具体的实现,实验的验证和原理已经在过程中注释,相信可以自己再过较长的时间也可以记得。代码的实现过程中遇到问题主要是查阅苹果文档,因为发现这部分的英文文档貌似没有自己想像的晦涩难懂
#import "BOOK.h"
#import <objc/runtime.h>
#import <objc/message.h>
#import "Pen.h"
#define MAXLength 100
@interface BOOK ()
{
@private
//私有的实例变量
NSString * privateIvar;
}
//私有属性
@property (nonatomic,strong) NSString * privateProperty;
@end
@implementation BOOK
//类方法,本类定义的方法
//-------------------------------------------------------
-(void)printMessage
{
NSLog(@"私有成员变量 privateIvar = %@",privateIvar);
}
//--------------------------------------------------------
//----属性和方法获取相关的内容 读和改
//获取所有的属性变量
/**
property_getName获取属性名字
*/
-(NSArray*) allPropertyNames{
NSMutableArray * pNames = [[NSMutableArray alloc]init];
unsigned int count ;
objc_property_t * propertys=class_copyPropertyList([self class], &count);
for (int i = 0; i<count; i++) {
const char * pty = property_getName(propertys[i]);
[pNames addObject:[NSString stringWithUTF8String:pty]];
}
free(propertys);//必须释放
return [pNames copy];
}
-(NSArray*)allIvarNames //这里读取的值包括了property声明的属性,这些属性以_开头,所以这里可以更改所有的变量的值
{
NSMutableArray * ivarNames = [[NSMutableArray alloc]init];
unsigned int count ;
Ivar * iVars=class_copyIvarList([self class], &count);
for (int i = 0; i<count; i++) {
const char * var = ivar_getName(iVars[i]);
//选择性赋值.
if (strcmp(var , "privateIvar")==0) {
object_setIvar(self, iVars[i], @"runTime");//赋值
object_getIvar(self, iVars[i]);//这里是读取值
}
[ivarNames addObject:[NSString stringWithUTF8String:var]];
}
free(iVars);//必须释放
return [ivarNames copy];
}
//获取所有的属性变量的具体信息
/**:
>property_getAttributes 方法是关键
"T@\"NSString\",&,N,V_name",
"Td,N,V_price",
"T@\"NSArray\",&,N,V_authors",
"Ti,N,V_pubishYear"
You can use the property_getAttributes function to discover the name, the @encode type string of a property, and other attributes of the property.
The string starts with a T followed by the @encode type and a comma, and finishes with a V followed by the name of the backing instance variable. Between these, the attributes are specified by the following descriptors, separated by commas:
--------
T表示属性字符串开始,V表示结束,T后面是数据类型d表示的是double,逗号后面表示的是属性变量的类型,N表示nonatomic
其他类型 Objective-C Runtime Programming Guide --> Type Encodings 和 Property Type String
*/
-(NSArray*)allPropertyAttr{
NSMutableArray * pNamesAttr = [[NSMutableArray alloc]init];
unsigned int count ;
objc_property_t * propertys=class_copyPropertyList([self class], &count);
for (int i = 0; i<count; i++) {
const char * pty = property_getAttributes(propertys[i]);
[pNamesAttr addObject:[NSString stringWithUTF8String:pty]];
}
free(propertys);//必须释放
return [pNamesAttr copy];
}
//获取所有的方法
/**
*
Method:http://blog.csdn.net/woaifen3344/article/details/50505808
SEL:
Method selectors are used to represent the name of a method at runtime. A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.
You can add new selectors at runtime and retrieve existing selectors using the function sel_registerName.
When using selectors, you must use the value returned from sel_registerName or the Objective-C compiler directive @selector(). You cannot simply cast a C string to SEL.
*/
-(void)allMethods
{
//MAXLenth 其实没有必要为100,只是系统给的方法必须要传递一个长度,自己只能根据返回值估计一个。防止复制的长度不够,实际上10也许就够了
unsigned int methodCount;
//Method方法默认的参数有两个的
#pragma mark -- OC里面的方法在运行时都是转换为了objc_send函数,这个方法第一个参数是调用者,第二个是方法名。默认的两个参数就是调用者和方法名
Method *methods = class_copyMethodList([self class], &methodCount);
for (int i=0; i<methodCount; i++) {
SEL methodSel = method_getName(methods[i]);//先转换为SEL类型,消息选择器
NSLog(@"方法的名字 = %s",sel_getName(methodSel));
unsigned argumentCnt = method_getNumberOfArguments(methods[i]);
NSLog(@"方法的参数个数 number = %d ",argumentCnt);
for (int j = 0 ; j< argumentCnt;j++) {
char argumentsType[MAXLength];
method_getArgumentType(methods[j], j, argumentsType, MAXLength);
NSLog(@"第%d个参数是%s",j,argumentsType);
}
char returnType[MAXLength];
method_getReturnType(methods[i], returnType, MAXLength);
NSLog(@"方法的返回类型%s",returnType);
//方法的实现
/**
A pointer to the start of a method implementation.
Declaration
id (*IMP)(id, SEL, ...)
指向方法的函数指针
*/
//#pragma waring --- IMP暂时不会用,后面的class_addMethod方法用到了动态添加方法
IMP imp = method_getImplementation(methods[i]);
}
free(methods);
}
//--------------------------------------------------SET和GET方法
//获取get方法
-(SEL)createGetMethodWithName:(NSString *)pName
{
return NSSelectorFromString(pName);
}
//这是根据字符串拼接生成的set方法
- (SEL)propertySetterWithKey:(NSString *)key {
NSString *propertySetter = key.capitalizedString;
propertySetter = [NSString stringWithFormat:@"set%@:", propertySetter];
//生成setter方法
SEL setter = NSSelectorFromString(propertySetter);//set方法
if ([self respondsToSelector:setter]) {
return setter;
}
return nil;
}
//--- 通过运行时执行方法
//第一层次
-(void)executeMethodByRunTime_00
{
//通过objc_msgSend方法
NSLog(@"这个方法用来通过运行时验证一个类已经存在的一个方法,在main调用");
//OC的方法调用的时候会在运行时系统转换为特定的格式。
}
-(void)executeMethodByRunTime_01:(NSString *)param
{
//通过objc_msgSend方法
NSLog(@"这个方法用来通过运行时验证一个带有参数:%@",param);
//OC的方法调用的时候会在运行时系统转换为特定的格式。
}
//带有方法和返回值
-(NSString *)executeMethodByRunTime_02:(NSString *)param
{
//通过objc_msgSend方法
NSLog(@"这个方法用来通过运行时验证一个带有参数和返回值");
//OC的方法调用的时候会在运行时系统转换为特定的格式。
return [NSString stringWithFormat:@"%@runtime",param];
}
// 第二层次,通过解决不存在的方法调用导致的崩溃,探索方法调用流程,这里仅说实例方法,类方法应该一样。会一次调用下面的方法
// 1. 只声明未定义 先执行这里如果这里执行了也算成功
/**
This method is called before the Objective-C forwarding mechanism is invoked. If respondsToSelector: or instancesRespondToSelector: is invoked, the dynamic method resolver is given the opportunity to provide an IMP for the given selector first.
这个方法会在OC的转发机制前被调用,respondsToSelector,instancesRespondToSelector方法被调用了,这个方法会首先给一个机会提供一个方法的实现
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
// you can use resolveInstanceMethod: to dynamically add it to a class as a method (called resolveThisMethodDynamically) like this:
//根据苹果的文档,自己解决在,函数
if (sel == @selector(onlyDeclareMethod:andWithSec:))//可以在为没有实现的方法添加动态添加一个方法
{
//最后一个参数是函数的参数和返回值类型,可以根据文档查阅具体的,从左到右依次是函数返回值和各个参数
//第一个@表示返回值是对象(NSString*)是对象的一种,第二个@表示的是第一个参数id类型,":"表示参数SEL类型,后面两个表示
//first和second
class_addMethod([self class], sel, (IMP)implementOnlyDeclare, "@@:@@");
return YES;
}
return [super resolveInstanceMethod:sel];
//最后一位是函数的编码格式,需要查看手册
//class_addMethod(self, sel, (IMP)implementMethod, "v");
}
//--resolveInstanceMethod阶段
//--- 自己动态为没有实现的方法添加一个方法,class_addMethod方法添加的方法至少包含两个参数,id,_cmd,自己猜测是因为这里添加的是方法是
//OC的方法,OC的方法在运行时中的C语言方法执行时用的objc_msgSend函数调用格式有关
NSString *implementOnlyDeclare(id class ,SEL sel,NSString * first,NSString * second)
{
return [NSString stringWithFormat:@"在resolveInstanceMethod中通过addMethod方法实现方法:%@+%@",first,second];
}
// 2.resolveInstanceMethod:(SEL)sel 未找到正确的方法会执行,会走到这个方法
//用于指定哪个对象响应这个selector。不能指定为self。若返回nil,表示没有响应者,则会进入第三步。若返回某个对象,则会调用该对象的方法
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (aSelector == @selector(onlyDeclareMethod2:andWithSec:)) {
return [[Pen alloc]init];//由Pen类代替自己去处理
}
return nil;
}
//3.forwardingTargetForSelector:(SEL)aSelector 处理不了 到这里
-(NSString *)implementOnlyDeclare3:(NSString *) first second:(NSString *)second
{
return [NSString stringWithFormat:@"在methodSignatureForSelector中实现方法:%@+%@",first,second];
}
//3.在forwardingTargetForSelector方法返回nil的情况下,会转发到这里来。通过这里我们可以做进一步处理,这里要返回一个方法的签名,
//如果签名方法根据方法确实能够得到方法签名,也就是方法存在,在所选择的类存在,就会进入4,如果方法签名无效就会进入
//doesNotRecognizeSelector
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector==@selector(onlyDeclareMethod3:andWithSec:)) {
//这里不一定要return 本类的,其他类的方法一样可以。
#pragma mark 返回这里就崩溃了,目测是循环调用本方法导致的
//return [self methodSignatureForSelector:@selector(implementOnlyDeclare3:second:)];
#pragma mark 根据类型返回一个签名没有错误,会进入 - (void)forwardInvocation:(NSInvocation *)anInvocation 处理
//return [NSMethodSignature signatureWithObjCTypes:"@@:@@"]; //这里返回的是函数类型和原来函数一致的
return [NSMethodSignature signatureWithObjCTypes:"v@:"];//也可以返回和原来函数不一致的类型
}
return [super methodSignatureForSelector:aSelector];
}
//4.在第三步返回的方法签名有效的情况下,就会进入这里
- (void)forwardInvocation:(NSInvocation *)anInvocation //NSInvocation用来包装方法调用的对象,见下面的案例。
{
//在methodSignatureForSelector 中 return [NSMethodSignature signatureWithObjCTypes:"@@:@@"];上面返回的类型要和下面的
//一致,下面的三种任意一种操作都是可以的
/*1. 这样写可以,表示执行本类的其他方法
[anInvocation setTarget:self];
[anInvocation setSelector:@selector(implementOnlyDeclare3:second:)]; //在这里实现。
[anInvocation invoke];
*/
//2.这里也可以转发给Pen类去实现调用的方法
/*Pen * pen = [[Pen alloc]init];
[anInvocation setTarget:pen];
[anInvocation invoke];
*/
///3.在methodSignatureForSelector 中 return [NSMethodSignature signatureWithObjCTypes:"v@:"];上面返回的类型要和下面的
//一致
[anInvocation setTarget:[[Pen alloc]init]];
[anInvocation setSelector:@selector(executeDefalut)];
[anInvocation invoke];
}
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"在这里系统表示不识别这个方法,%@",NSStringFromSelector(aSelector));
}
/**
* 这个例子实现了系统performSelector withObject的方法的扩展,也展现了NSInvocation的使用
*
* @param selector 消息选择器
* @param objects 参数数组
*
* @return 返回值
*/
- (id)performSelector:(SEL)selector withObjects:(NSArray *)objects
{
// 方法签名(方法的描述),方法签名和方法一样对应
NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
if (signature == nil) {
//可以抛出异常也可以不操作
return nil;
}
// NSInvocation : 利用一个NSInvocation对象包装一次方法调用(方法调用者、方法名、方法参数、方法返回值)
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = selector;
// 设置参数
NSInteger paramsCount = signature.numberOfArguments - 2; // 除self、_cmd以外的参数个数,获取当前的方法真正的参数个数
paramsCount = MIN(paramsCount, objects.count);//两者之间取小值,不然会参数个数不一样崩溃
for (NSInteger i = 0; i < paramsCount; i++) {
id object = objects[i];
if ([object isKindOfClass:[NSNull class]])
continue;
[invocation setArgument:&object atIndex:i + 2];//用invocation对象设置参数的位置
}
// 调用方法
[invocation invoke];//调用
// 获取返回值
id returnValue = nil;
if (signature.methodReturnLength) { // 有返回值类型,才去获得返回值
[invocation getReturnValue:&returnValue];
}
//原本以为调用和获取返回值是一体的 类似于 returnValue = [invocation invoke],其实是上面的
return returnValue;
}
@end
Pen类主要的所有是验证运行时的消息转发,其他类替代自己处理事件,动态添加属性。
@interface Pen : NSObject
-(NSString *)onlyDeclareMethod2:(NSString *)first andWithSec:(NSString*)second;
-(NSString*)onlyDeclareMethod3:(NSString *)first andWithSec:(NSString *)second;
//替代BOOK实现的方法和这个方法参数不一致
-(NSString *)executeDefalut;
@end
@implementation Pen
-(NSString*)onlyDeclareMethod2:(NSString *)first andWithSec:(NSString *)second
{
return @"这是Pen类替代Book类实现了未实现的方法";
}
-(NSString*)onlyDeclareMethod3:(NSString *)first andWithSec:(NSString *)second
{
return @"这是在forwardInvocation方法中实现由Pen对象替代BOOK对象执行未定义方法";
}
-(NSString*)executeDefalut
{
return @"Pen代替Book实现了参数类型不一致的方法";
}
@end
@interface Pen (PenCate)
@property (nonatomic,strong) NSString * catePen;//类扩展属性
@end
static const char * catePen_Key ="cate_nameKey";
@implementation Pen (PenCate)
- (void)setCatePen:(NSString *)catePen
{
//这个函数:Sets an associated value for a given object using a given key and association policy.
//把一个值和一个对象用一个key和关联规则关联起来。第一个参数关联对象,第二个是key,第三个是关联的值,第四个关联规则,类似于
//nonatomic,assin,copy,retain
objc_setAssociatedObject(self, catePen_Key, catePen, OBJC_ASSOCIATION_RETAIN);
}
-(NSString*)catePen
{
NSString * cateName= objc_getAssociatedObject(self, catePen_Key);//通过一个键值和对象取得对应的值。
return cateName;
}
@end