Runtime小结

一、Runtime简介
RunTime简称运行时,是一套底层的 C 语言 API。Objective-C是一门动态编程语言,对于Objective-C的函数,属于动态调用的过程.在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。而C语言是函数的调用在编译的时候会决定调用哪个函数。在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要声明过就不会报错。C语言调用未实现的函数就会报错。

二、Runtime运用
动态获取类名、动态获取类的成员变量、动态获取类的方法列表、动态获取类所遵循的协议列表、实现NSCoding的自动归档和自动解档、动态添加新的方法、类的实例方法实现的交换、动态属性关联、消息发送与消息转发机制等.

  1. 获取类名
 @param class 相应类
 @return NSString:类名
 */
+ (NSString *)fetchClassName:(Class)class {
    const char *className = class_getName(class);
    return [NSString stringWithUTF8String:className];
}
    

2.获得某个类的所有成员变量

/********
 获得某个类的所有成员变量
 *Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
 *参数: 1.哪个类
 *     2.放一个接收值的地址,用来存放属性的个数
 *返回值:存放所有获取到的属性
 ivar_getName(),ivar_getTypeEncoding()可以调出名字和类型
 */
+(NSArray *)getAllVariableValueWithClass:(Class)c
{
    NSMutableArray *mArr=[[NSMutableArray alloc]init];
    unsigned int count=0;
    Ivar *ivars=class_copyIvarList(c, &count);
    for (int i=0; i

3.获取类的实例方法列表

/**
 获取类的实例方法列表:getter, setter, 对象方法等。但不能获取类方法
 
 @param class <#class description#>
 @return <#return value description#>
 */
+ (NSArray *)fetchMethodList:(Class)class {
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(class, &count);
    
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++ ) {
        Method method = methodList[i];
        SEL methodName = method_getName(method);
        [mutableList addObject:NSStringFromSelector(methodName)];
    }
    free(methodList);
    return [NSArray arrayWithArray:mutableList];
}

4.获取协议列表

/**
 获取协议列表
 
 @param class <#class description#>
 @return <#return value description#>
 */
+ (NSArray *)fetchProtocolList:(Class)class {
    unsigned int count = 0;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList(class, &count);
    
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++ ) {
        Protocol *protocol = protocolList[i];
        const char *protocolName = protocol_getName(protocol);
        [mutableList addObject:[NSString stringWithUTF8String: protocolName]];
    }
    
    return [NSArray arrayWithArray:mutableList];
    return nil;
}

5.动态添加方法实现
为什么要动态添加方法?OC都是懒加载,有些方法可能很久不会调用.例如: 电商,视频,社交,收费项目,会员机制,只有会员才拥有这些动能

/**
 往类上添加新的方法与其实现
 
 @param class 相应的类
 @param methodSel 方法的名
 @param methodSelImpl 对应方法实现的方法名
 */
+ (void)addMethod:(Class)class method:(SEL)methodSel method:(SEL)methodSelImpl {
    
    Method method = class_getInstanceMethod(class, methodSelImpl);
    IMP methodIMP = method_getImplementation(method);
    const char *types = method_getTypeEncoding(method);
    class_addMethod(class, methodSel, methodIMP, types);
}

6.交换两个方法的实现,拦截系统自带的方法调用功能

简单方法交换

/**
 方法交换
 
 @param class 交换方法所在的类
 @param method1 方法1
 @param method2 方法2
 */
+ (void)methodSwap:(Class)class firstMethod:(SEL)method1 secondMethod:(SEL)method2 {
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method firstMethod = class_getInstanceMethod(class, method1);
        Method secondMethod = class_getInstanceMethod(class, method2);
        
        //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
        BOOL add=class_addMethod(class, method1, method_getImplementation(secondMethod), method_getTypeEncoding(secondMethod));
        if (add) {
            //如果成功,说明类中不存在这个方法的实现
            //将被交换方法的实现替换到这个并不存在的实现
            class_replaceMethod(class, method2, method_getImplementation(firstMethod), method_getTypeEncoding(firstMethod));
        }else{
            method_exchangeImplementations(firstMethod, secondMethod);
        }
    });

}

系统方法拦截
比如说需求:比如iOS6 升级 iOS7 后需要版本适配,根据不同系统使用不同样式图片(拟物化和扁平化),如何通过不去手动一个个修改每个UIImage的imageNamed:方法就可以实现为该方法中加入版本判断语句?

步骤:
1、为UIImage建一个分类(UIImage+Category)
2、在分类中实现一个自定义方法,方法中写要在系统方法中加入的语句,比如版本判断
3、分类中重写UIImage的load方法,实现方法的交换(只要能让其执行一次方法交换语句,load再合适不过了)

#import "UIImage+Category.h"
#import 
#import "RunTimeHelper.h"

@implementation UIImage (Category)

+ (void)load {
    // 获取两个类的类方法
    [RunTimeHelper methodSwap:[UIImage class] firstMethod:@selector(imageNamed:) secondMethod: @selector(new_imageNamed:)];
}

+ (UIImage *) new_imageNamed:(NSString *)name {
    double version = [[UIDevice currentDevice].systemVersion doubleValue];
    if (version >= 7.0) {
        // 如果系统版本是7.0以上,使用另外一套文件名结尾是‘_os7’的扁平化图片
        name = [name stringByAppendingString:@"_os7"];
    }
    return [UIImage new_imageNamed:name];
}

7.使用runtime获取所有属性来重写归档解档方法,让我们不需要每次都写这么一长串代码,只要实现一小段代码就可以让一个对象具有归解档的能力

@interface NSObject (category)
- (NSArray *)ignoredNames;
- (void)encode:(NSCoder *)aCoder;
- (void)decode:(NSCoder *)aDecoder;
@end

@implementation NSObject (category)
-(void)decode:(NSCoder *)aDecoder
{
    Class c = self.class;

   // 一层层父类往上查找,对父类的属性执行归解档方法
    while ([self class] &&[self class]!=[NSObject class]) {
        
        unsigned int count=0;
        Ivar *ivars=class_copyIvarList([self class], &count);
        for (int i=0; i

8.属性关联

@interface NSObject (category)
@property (nonatomic ,strong)NSString *dynamicAddProperty;

@end

@implementation NSObject (category)
char dynamicAddPropertyKey;
/********
属性关联
objc_setAssociatedObject
set方法,将值value 跟对象object 关联起来(将值value 存储到对象object 中)
 参数 object:给哪个对象设置属性
 参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节
 参数 value:给属性设置的值
 参数 policy:存储策略 (assign 、copy 、 retain就是strong)
 */
-(void)setDynamicAddProperty:(NSString *)dynamicAddProperty
{
    objc_setAssociatedObject(self, &dynamicAddProperty, dynamicAddProperty, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
/********
 属性关联
 objc_getAssociatedObject
 get方法利用参数key 将对象object中存储的对应值取出来
 */
-(NSString *)dynamicAddProperty
{
    return objc_getAssociatedObject(self, &dynamicAddPropertyKey);
}


@end

三、消息处理与消息转发
了解 Runtime ,要先了解它的核心 - 消息传递 .C语言调用一个方法其实就是跳到内存中的某一点并开始执行一段代码。没有任何动态的特性,因为这在编译时就决定好了。而在 Objective-C 中,[object foo] 语法并不会立即执行 foo 这个方法的代码。它是在运行时给 object 发送一条叫 foo 的消息。这个消息,也许会由 object 来处理,也许会被转发给另一个对象,或者不予理睬假装没收到这个消息。多条不同的消息也可以对应同一个方法实现。这些都是在程序运行的时候决定的。当你调用一个类的方法时,先在本类中的方法缓存列表中进行查询,如果在缓存列表中找到了该方法的实现,就执行,如果找不到就在本类中的方列表中进行查找。在本类方列表中查找到相应的方法实现后就进行调用,如果没找到,就去父类中进行查找。如果在父类中的方法列表中找到了相应方法的实现,那么就执行,否则就执行下方的几步。
1.动态方法解析(Resolve Method)
+(BOOL) resolveInstanceMethod:(SEL) sel
调用时机为当被调用的方法实现部分没有找到,而消息转发机制启动之前的这个中间时刻。该方法如果在类中不被重写的话,默认返回NO。如果返回NO就表明不做任何处理,走下一步。如果返回YES的话,就说明在该方法中对这个找不到实现的方法进行了处理。在该方法中,我们可以为找不到实现的SEL动态的添加一个方法实现,添加完毕后,就会执行我们添加的方法实现。这样,当一个类调用不存在的方法时,就不会崩溃了。具体做法如下所示:

/**
 没有找到SEL的IML实现时会执行下方的方法
 @param sel 当前对象调用并且找不到IML的SEL
 @return 找到其他的执行方法,并返回yes
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel;
例如:
@interface ViewController : UIViewController

-(void)introduceName:(NSString *)name;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self introduceName:@"runtime"];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel==@selector(introduceName:)) {
        Method method = class_getInstanceMethod(self, @selector(hl_introduceName:));
        if (method) {
            class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
        }
    }

    return [super resolveInstanceMethod:sel];
}

- (void)hl_introduceName:(NSString *)name
{
     NSLog(@"I am %@",name);
}
@end

  

2、消息快速转发

如果不对上述消息进行处理的话,也就是+resolveInstanceMethod:返回NO时,会走下一步消息转发,即-forwardingTargetForSelector:。该方法会返回一个类的对象,这个类的对象有SEL对应的实现,当调用这个找不到的方法时,就会被转发到SecondClass中去进行处理。这也就是所谓的消息转发。当该方法返回self或者nil, 说明不对相应的方法进行转发,那么就该走下一步了。

@interface ViewController : UIViewController

-(void)introduceName:(NSString *)name;
@end

@implementation ViewController

- (void)viewDidLoad {
   [super viewDidLoad];
   [self introduceName:@"runtime"];
}

/**
将当前对象不存在的SEL传给其他存在该SEL的对象

@param aSelector 当前类中不存在的SEL
@return 存在该SEL的对象
*/
//快速消息转发
- (id)forwardingTargetForSelector:(SEL)aSelector
{
   //将ViewController中调用的实例方法转移到SomeOne中
   if (aSelector==@selector(introduceName:)) {
       return [SomeOne new];
   }
   return [super forwardingTargetForSelector:aSelector];
}


3.消息常规转发
如果不将消息转发给其他类的对象,那么就只能自己进行处理了。如果上述方法返回self的话,会执行-methodSignatureForSelector:方法来获取方法的参数以及返回数据类型,也就是说该方法获取的是方法的签名并返回。如果上述方法返回nil的话,那么消息转发就结束,程序崩溃,报出找不到相应的方法实现的崩溃信息。

@interface ViewController : UIViewController

-(void)introduceName:(NSString *)name;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self introduceName:@"runtime"];
}

/*****消息转发:
 *消息获得函数的参数和返回值类型,如果返回 nil ,程序就会挂掉了。
 *如果返回了一个函数签名,Runtime 就会创建一个 NSInvocation 对象并发送 -forwardInvocation: 消息给目标对象
 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *methodSignature=[super methodSignatureForSelector:aSelector];
    if (!methodSignature) {
        methodSignature= [SomeOne instanceMethodSignatureForSelector:aSelector];
    }
    return methodSignature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    //这里是将ViewController中调用的实例方法转移到SomeOne中,只是多了一层NSInvocation包装
    SEL sel=anInvocation.selector;
    SomeOne *someOne=[SomeOne new];
    if ([someOne respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:someOne];
    }
    
}

你可能感兴趣的:(Runtime小结)