Knowledge and understanding of Runtime

21320954_182303674000_2.jpg

The article is selected from: Sina blog: http://blog.sina.com.cn/s/blog_9cd71e570102wn2j.html
I feel that the content written by the blog owner has gained a lot of inspiration, and it has also given me a better understanding of Runtime.
What is Runtime?

Runtime is runtime, mainly based on the message mechanism; in C, the calling order of functions will be completely determined at the compile stage, without any ambiguity; while in Objective_C, it will be truly determined at runtime, and OC method calls are based on method selectors (SEL, a structure of C, pointers of int type), and each SEL stores a unique method name (note: only the method name, not including the parameter list), so the method name is unique in a class of OC (there is no method overload);

​First look at the representation of method calls in OC in Runtime:
the code we write will be converted into runtime C code execution during the running of the program, for example [target doSomething]; will be converted into objc_msgSend(target, @selector(doSomething));.

Let's take a look at the representation of objects in Runtime:
everything in OC is designed as objects, we all know that a class is initialized into an instance, and this instance is an object. In fact, a class is essentially an object, represented by a structure in the runtime.

Related definition:
/// Description A method
typeDef Struct Objc_Method *Method;
/// instance variables
Typedef Struct Objc_ivar *Ivar;
/// Category
Typedef Struct Objc_category * Category;
/// The attributes declared in class
TypeDef Struct Objc_property *Objc_property_t;

Representation of a class in runtime:
struct objc_class { Class isa;//pointer, as the name suggests, represents what it is, //isa of the instance points to the class object, and isa of the class object points to the metaclass

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;

Get the list
Sometimes there is such a requirement, we need to know the name of each attribute in the current class;

For example: when converting a dictionary to a model, the Key of the dictionary does not match the attribute name of the model object. We can use setValueForkeyWithDitionary: to convert the matching KEY; setValue:forUndefinedKey: ignore the unmatched KEY, for the unmatched KEY, use Runtime to get the list of model attributes, process the unmatched Key separately, and encapsulate your own method;

We can get some information about the class (including attribute list, method list, member variable list, and protocol list) through a series of runtime methods.

unsigned int count;
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; 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 
    Method method = methodList[i]; 
    NSLog(@"method---->%@", NSStringFromSelector(method_getName(method))); 
} 

//获取成员变量列表 
Ivar *ivarList = class_copyIvarList([self class], &count); 
for (unsigned int i; i 
    Ivar myIvar = ivarList[i]; 
    const char *ivarName = ivar_getName(myIvar); 
    NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]); 
}

方法调用
让我们看一下方法调用在运行时的过程

如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。

首先,在相应操作的对象中的缓存方法列表(Cache)中找调用的方法,如果找到,转向相应实现并执行。
如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行
如果没找到,去父类指针所指向的对象中执行1,2.
如果找到,则将method加 入到Cache中,以方便下次查找,并通过method中的函数指针(保存在方法选择器当中)跳转到对应的函数中去执行。
以此类推,如果一直到根类还没找到,转向拦截调用。
如果没有重写拦截调用的方法,程序报错(NSAssert)。
以上的过程给我带来的启发:

重写父类的方法,并没有覆盖掉父类的方法,只是在当前类对象中找到了这个方法后就不会再去父类中找了。
如果想调用已经重写过的方法的父类的实现,只需使用super这个编译器标识符,它会在运行时跳过在当前的类对象中寻找方法的过程。

拦截调用
在方法调用中说到了,如果没有找到方法就会转向拦截调用。那么什么是拦截调用呢。拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。

  • (BOOL)resolveClassMethod:(SEL)sel;
  • (BOOL)resolveInstanceMethod:(SEL)sel;
    //后两个方法需要转发到其他的类处理
  • (id)forwardingTargetForSelector:(SEL)aSelector;

  • (void)forwardInvocation:(NSInvocation *)anInvocation;
    第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
    第二个方法和第一个方法相似,只不过处理的是实例方法。
    第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
    第四个方法是将你调用的不存在的方法打包成NSInvocation
    传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。

动态添加方法
重写了拦截调用的方法并且返回了YES,我们要怎么处理呢?有一个办法是根据传进来的SEL类型的selector动态添加一个方法。

首先从外部隐式调用一个不存在的方法:

//隐式调用方法
[target performSelector:@selector(resolveAdd:) withObject:@"test"];

然后,在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@:*"
方法的签名,代表有一个参数的方法。

那么问题来了,如果我动态添加的方法名并不是缺失的方法名呢?手动改一改就好了,那么如多及时上百处调用了呢? 重写缺失方法,调用我们的方法也可以; 介绍一个高大上的方法:方法交换(稍后讲解);

关联对象
现在你准备用一个系统的类,但是系统的类并不能满足你的需求,你需要额外添加一个属性。这种情况的一般解决办法就是继承。但是,只增加一个属性,就去继承一个类,总是觉得太麻烦类。这个时候,runtime的关联属性就发挥它的作用了。

//首先定义一个全局变量,用它的地址作为关联对象的key
static char associatedObjectKey;
//设置关联对象
objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串属性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); //获取关联对象
NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);
NSLog(@"AssociatedObject = %@", string);
objc_setAssociatedObject

的四个参数:

id object
给谁设置关联对象。

const void *key
关联对象唯一的key,获取时会用到。

id value
关联对象。

objc_AssociationPolicy
关联策略,有以下几种策略:

enum
{ OBJC_ASSOCIATION_ASSIGN = 0,

OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,

OBJC_ASSOCIATION_COPY_NONATOMIC = 3,

OBJC_ASSOCIATION_RETAIN = 01401,

OBJC_ASSOCIATION_COPY = 01403 };

如果你熟悉OC,看名字应该知道这几种策略的意思了吧。

objc_getAssociatedObject

的两个参数。

id object
获取谁的关联对象。

const void *key
根据这个唯一的key获取关联对象。

其实,你还可以把添加和获取关联对象的方法写在你需要用到这个功能的类的类别中,方便使用。

//添加关联对象

  • (void)addAssociatedObject:(id)object{ objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }//获取关联对象- (id)getAssociatedObject{ return objc_getAssociatedObject(self, _cmd); }

注意:

这里面我们把getAssociatedObject方法的地址作为唯一的key,_cmd代表当前调用方法的地址。

方法交换

方法交换,顾名思义,就是将两个方法的实现交换。例如,将A方法和B方法交换,调用A方法的时候,就会执行B方法中的代码;

import "UIViewController+swizzling.h"

import

@implementation UIViewController (swizzling)

//load方法会在类第一次加载的时候被调用
//调用的时间比较靠前,适合在这个方法里做方法交换

  • (void)load{
    //方法交换应该被保证,在程序中只会执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

    //获得viewController的生命周期方法的selector 
      SEL systemSel = @selector(viewWillAppear:); 
      //自己实现的将要被交换的方法的selector 
      SEL swizzSel = @selector(swiz_viewWillAppear:); 
      //两个方法的Method 
      Method systemMethod = class_getInstanceMethod([self class], systemSel); 
      Method swizzMethod = class_getInstanceMethod([self class], swizzSel); 
    
      //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败 
      BOOL isAdd = class_addMethod(self, systemSel,    method_getImplementation(swizzMethod),      method_getTypeEncoding(swizzMethod)); 
      if (isAdd) { 
             //如果成功,说明类中不存在这个方法的实现 
             //将被交换方法的实现替换到这个并不存在的实现 
              class_replaceMethod(self, swizzSel,          method_getImplementation(systemMethod),            method_getTypeEncoding(systemMethod)); 
      }else{ 
          //否则,交换两个方法的实现 
          method_exchangeImplementations(systemMethod, swizzMethod);
      }

    });
    }

  • (void)swiz_viewWillAppear:(BOOL)animated{
    //这时候调用自己,看起来像是死循环
    //但是其实自己的实现已经被替换了
    [self swiz_viewWillAppear:animated];
    NSLog(@"swizzle");
    }
    @end
    在一个自己定义的viewController中重写viewWillAppear

  • (void
    )viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; NSLog(@"viewWillAppear"); }

Run起来看看输出吧!

我的理解:

特别注意::: 方法交换实际交换的是方法的实现部分;

Guess you like

Origin blog.csdn.net/hpyhn0204/article/details/130429695
Recommended