Runtime


Runtime的特性主要是消息(方法)的传递,如果消息(方法)在对象中找不到,就会进行转发。

Runtime的介绍:

Objective-C 是一个基于C语言的扩展,并且在C语言的基础上增加了面向对象特性和Smalltalk 式的消息传递机制。而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库。

高级编程语言想要成为可执行文件能被计算机识别,首先要转化成汇编语言,然后在汇编成机器语言。因为机器语言是计算机能够识别的唯一语言,所以任何语言最终都要转化成机器语言才能被计算机所识别。所以OC语言必须要经过转化成C语言然后才能进行汇编成机器语言从而被识别。从OC到C就是通过runtime来实现的。

而OC是面向对象的,C是面向过程的,所以这也就需要将 面向对象的类  转化成  面向过程的结构体

Runtime消息转发:

   1.1 OC的方法调用流程

   下面以实例对象调用方法[blackDog walk]为例描述方法调用的流程:

1、编译器会把`[blackDog walk]`转化为`objc_msgSend(blackDog,SEL)`,SEL为@selector(walk)。

2、Runtime会在blackDog对象所对应的Dog类的方法缓存列表里查找方法的SEL(先查找缓存表里面的方法,有则直接取,没有则需要遍历整个methodList表)

3、如果没有找到,则在Dog类的方法分发表查找方法的SEL。(类由对象isa指针指向,方法分发表即methodList)

4、如果没有找到,则在其父类(设Dog类的父类为Animal类)的方法分发表里查找方法的SEL(父类由类的superClass指向)

5、如果没有找到,则沿继承体系继续下去,最终到达NSObject类。

6、如果在234的其中一步中找到,则定位了方法实现的入口,执行具体实现

7、如果最后还是没有找到,会面临两种情况:``(1) 如果是使用`[blackDog walk]`的方式调用方法````(2) 使用`[blackDog performSelector:@selector(walk)]`的方式调用方法

1.2 消息转发流程

1、动态方法解析接收到未知消息时(假设blackDog的walk方法尚未实现),runtime会调用+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)(这一步以方法是否被实现为准,如果方法没有被实现,不管返回值是YES or NO ,都会继续传递给下一级)

2、备用接收者如果以上方法没有做处理,runtime会调用- (id)forwardingTargetForSelector:(SEL)aSelector方法。如果该方法返回了一个非nil(也不能是self)的对象,而且该对象实现了这个方法,那么这个对象就成了消息的接收者,消息就被分发到该对象。适用情况:通常在对象内部使用,让内部的另外一个对象处理消息,在外面看起来就像是该对象处理了消息。比如:blackDog让女朋友whiteDog来接收这个消息

3、完整消息转发在- (void)forwardInvocation:(NSInvocation*)anInvocation方法中选择转发消息的对象,其中anInvocation对象封装了未知消息的所有细节,并保留调用结果发送到原始调用者。比如:blackDog将消息完整转发給主人dogOwner来处理

实现一个完整转发的例子如下:

#import "ViewController.h"

 #import "objc/runtime.h" 

 @interface Person: NSObject 

 @end 

 @implementation Person 

 - (void)foo {   

 NSLog(@"Doing foo");//Person的foo函数 

 @end

 @interface ViewController () 

 @end

 @implementation ViewController

 - (void)viewDidLoad {   

 [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    //执行foo函数  

  [self performSelector:@selector(foo)]; 

 + (BOOL)resolveInstanceMethod:(SEL)sel {   

 return YES;//返回YES,进入下一步转发 

 - (id)forwardingTargetForSelector:(SEL)aSelector {   

 return nil;//返回nil,进入下一步转发 

 - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {  

  if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {    

    return [NSMethodSignature signatureWithObjCTypes:"v@:"];//签名,进入forwardInvocation    

}        

return [super methodSignatureForSelector:aSelector]; 

 - (void)forwardInvocation:(NSInvocation *)anInvocation {    

SEL sel = anInvocation.selector;       

 Person *p = [Person new];    

if([p respondsToSelector:sel]) {     

   [anInvocation invokeWithTarget:p];   

 }    else {      

  [self doesNotRecognizeSelector:sel]; 

   }    }

 @end


Runtime的使用:

  1.1 json->model

  原理描述:用runtime提供的函数遍历Model自身所有属性,如果属性在json中有对应的值,则将其赋值。

  核心方法:在NSObject的分类中添加方法

- (instancetype)initWithDict:(NSDictionary*)dict {

    if(self= [selfinit]) {

        //(1)获取类的属性及属性对应的类型

        NSMutableArray * keys = [NSMutableArray array];

        NSMutableArray * attributes = [NSMutableArray array];

        /*

         * 例子

         * name = value3 attribute = T@"NSString",C,N,V_value3

         * name = value4 attribute = T^i,N,V_value4

         */

        unsignedintoutCount;

        objc_property_t * properties = class_copyPropertyList([self class], &outCount);

        for(inti = 0; i < outCount; i ++) {

            objc_property_tproperty = properties[i];

            //通过property_getName函数获得属性的名字

            NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];

            [keysaddObject:propertyName];

            //通过property_getAttributes函数可以获得属性的名字和@encode编码

            NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];

            [attributesaddObject:propertyAttribute];

        }

        //立即释放properties指向的内存

        free(properties);

        //(2)根据类型给属性赋值

        for(NSString* keyinkeys) {

            if([dictvalueForKey:key] ==nil)continue;

            [selfsetValue:[dictvalueForKey:key]forKey:key];

        }

    }

    return self;

}

//注释:::::现在直接使用一下方法也可

[self setValuesForKeysWithDictionary:dic];

 1.2 一键序列化

原理描述:用runtime提供的函数遍历Model自身所有属性,并对属性进行encode和decode操作。

核心方法:在Model的基类中重写方法:

- (id)initWithCoder:(NSCoder*)aDecoder {

    if(self = [super init]) {

        unsigned int outCount;

        Ivar* ivars =class_copyIvarList( [self class] , &outCount);

        for(inti = 0; i < outCount; i ++) {

            Ivar ivar = ivars[i];

            NSString * key = [NSString stringWithUTF8String: ivar_getName(ivar)];

            [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];

        }

    }

    return self;

}

- (void)encodeWithCoder:(NSCoder*)aCoder {

    unsigned int outCount;

    Ivar* ivars =class_copyIvarList ( [self class], &outCount);

    for(inti = 0; i < outCount; i ++) {

        Ivarivar = ivars[i];

        NSString * key = [NSString stringWithUTF8String: ivar_getName(ivar)];

        [aCoder encodeObject:[self valueForKey:key]  forKey:key];

    }

}

打印结果:

 2018-04-01 13:00:45.423385+0800 ocram[88353:23279961] Doing foo

1.3 访问私有变量

我们知道,OC中没有真正意义上的私有变量和方法,要让成员变量私有,要放在m文件中声明,不对外暴露。如果我们知道这个成员变量的名称,可以通过runtime获取成员变量,再通过getIvar来获取它的值。方法:

Ivar ivar = class_getInstanceVariable([Model class],"_str1");

NSString* str1 = object_getIvar(model, ivar);

1.4. 关联对象(Objective-C Associated Objects)给分类增加属性

   我们都是知道分类是不能自定义属性和变量的。下面通过关联对象实现给分类添加属性。

   关联对象Runtime提供了下面几个接口:

 //关联对象 

void objc_setAssociatedObject(id object,const void*key,id value, objc_AssociationPolicy  policy)

//获取关联的对象

id objc_getAssociatedObject(id object,const  void*key)

//移除关联的对象

void objc_removeAssociatedObjects(id  object)

参数解释

id object:被关联的对象

const void*key:关联的key,要求唯一

id value:关联的对象

objc_AssociationPolicy policy:内存管理的策略

下面实现一个UIView的Category添加自定义属性defaultColor。

#import "ViewController.h"

#import "objc/runtime.h"

@interfaceUIView (DefaultColor)

@property (nonatomic, strong) UIColor *defaultColor;

@end

@implementation UIView (DefaultColor)

@dynamic  defaultColor;

//也可以使用方法的指针地址作为唯一的key 例如:@selector(defaultColor)

static char  kDefaultColorKey;

- (void)setDefaultColor:(UIColor*)defaultColor {

    objc_setAssociatedObject(self, &kDefaultColorKey, defaultColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

- (id)defaultColor {

    return objc_getAssociatedObject(self, &kDefaultColorKey);

}

@end

@interfaceViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [superviewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.

     UIView*test = [UIView new];

    test.defaultColor = [UIColor blackColor];

    NSLog(@"%@", test.defaultColor);

}

@end

打印结果:

2018-04-01 15:41:44.977732+0800 ocram[2053:63739]

UIExtendedGrayColorSpace 0 1

1.5 Method Swizzling(俗称黑魔法)

简单说就是进行方法交换

method Swizzling原理

 每个类都维护一个方法(Method)列表,Method则包含SEL和其对应IMP的信息,方法交换做的事情就是把SEL和IMP的对应关系断开,并和新的IMP生成对应关系

*****在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。

****每个类都有一个方法列表,存放着方法的名字和方法实现的映射关系,selector的本质其实就是方法名,IMP有点类似函数指针,指向具体的Method实现,通过selector就可以找到对应的IMP。


交换方法的几种实现方式

  利用 method_exchangeImplementations 交换两个方法的实现

  利用 class_replaceMethod 替换方法的实现

  利用 method_setImplementation 来直接设置某个方法的IMP


使用实例:

     1.5.1方法添加

//class_addMethod(Class _Nullable __unsafe_unretained cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)

class_addMethod([self class], sel, (IMP)fooMethod,"v@:");

@cls 被添加方法的类

@name 添加的方法的名称的SEL

@imp 方法的实现。该函数必须至少要有两个参数,self,_cmd

@类型编码

     1.5.2方法替换

下面实现一个替换ViewController的viewDidLoad方法的例子

@implementation ViewController

+ (void)load {

    static dispatch_once_tonceToken;

    dispatch_once(&onceToken, ^{

        Class class = [self class];

        SEL originalSelector =@selector(viewDidLoad);

        SEL swizzledSelector =@selector(jkviewDidLoad);

        Method  originalMethod = class_getInstanceMethod(class,originalSelector);

        Method swizzledMethod = class_getInstanceMethod(class,swizzledSelector);

       //judge the method named  swizzledMethod is already existed.

        BOOL  didAddMethod =class_addMethod(class, originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));

        // if swizzledMethod is already existed.

        if(didAddMethod) {

            class_replaceMethod(class, swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));

        }    else{

            method_exchangeImplementations(originalMethod, swizzledMethod);

        }

    });

}

- (void)jkviewDidLoad {

    NSLog(@"替换的方法");

 [self jkviewDidLoad];

}

- (void)viewDidLoad {

    NSLog(@"自带的方法");

    [superviewDidLoad];

}

@end

swizzling应该只在+load中完成。 在Objective-C的运行时中,每个类有两个方法都会自动调用。+load是在一个类被初始装载时调用,+initialize是在应用第一次调用该类的类方法或实例方法前调用的。两个方法都是可选的,并且只有在方法被实现的情况下才会被调用。

swizzling应该只在dispatch_once中完成,由于swizzling改变了全局的状态,所以我们需要确保每个预防措施在运行时都是可用的。原子操作就是这样一个用于确保代码只会被执行一次的预防措施,就算是在不同的线程中也能确保代码只执行一次。Grand Central Dispatch 的 dispatch_once满足了所需要的需求,并且应该被当做使用swizzling的初始化单例方法的标准。

1.6   KVO实现

全称是Key-value observing,翻译成键值观察。提供了一种当其它对象属性被修改的时候能通知当前对象的机制。再MVC大行其道的Cocoa中,KVO机制很适合实现model和controller类之间的通讯。

KVO的实现依赖于Objective-C强大的Runtime,当观察某对象A时,KVO机制动态创建一个对象A当前类的子类,并为这个新的子类重写了被观察属性keyPath的setter方法。setter方法随后负责通知观察对象属性的改变状况。

Apple使用了isa-swizzling来实现KVO。当观察对象A时,KVO机制动态创建一个新的名为:NSKVONotifying_A的新类,该类继承自对象A的本类,且KVO为NSKVONotifying_A重写观察属性的setter方法,setter方法会负责在调用原setter方法之前和之后,通知所有观察对象属性值的更改情况。

KVO的键值观察通知依赖于 NSObject 的两个方法:willChangeValueForKey:和didChangeValueForKey:,在存取数值的前后分别调用 2 个方法:

被观察属性发生改变之前,willChangeValueForKey:被调用,通知系统该keyPath的属性值即将变更;

当改变发生后,didChangeValueForKey:被调用,通知系统该keyPath的属性值已经变更;之后,observeValueForKey:ofObject:change:context:也会被调用。且重写观察属性的setter方法这种继承方式的注入是在运行时而不是编译时实现的

- (void)setName:(NSString*)newName { 

  [selfwillChangeValueForKey:@"name"];//KVO 在调用存取方法之前总调用 

[supersetValue:newName forKey:@"name"];//调用父类的存取方法 [selfdidChangeValueForKey:@"name"];//KVO 在调用存取方法之后总调用

}

参考文章:

https://www.jianshu.com/p/07b6c4a40a90

https://www.jianshu.com/p/6ebda3cd8052

https://www.jianshu.com/p/75849d7ea832

你可能感兴趣的:(Runtime)