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<count; i++) {
        NSMutableDictionary *mDic=[[NSMutableDictionary alloc]init];
        Ivar ivar=ivars[i];
        const char *name=ivar_getName(ivar);
        const char *type=ivar_getTypeEncoding(ivar);
        mDic[@"name"]=[NSString stringWithUTF8String:name];
        mDic[@"type"]=[NSString stringWithUTF8String:type];
        [mArr addObject:mDic];
    }
    return [NSArray arrayWithArray:mArr];
}

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 <objc/runtime.h>
#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<count; i++) {
            Ivar ivar=ivars[i];
            NSString *key=[NSString stringWithUTF8String:ivar_getName(ivar)];
            if ([self respondsToSelector:@selector(ignoredNames)]) {
                if ([[self ignoredNames] containsObject:key]) continue;
            }
            [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
        }
        free(ivars);
        c = [c superclass];
    }
}

-(void)encode:(NSCoder *)aCoder
{
    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<count; i++) {
            Ivar ivar=ivars[i];
            NSString *key=[NSString stringWithUTF8String:ivar_getName(ivar)];
            if ([self respondsToSelector:@selector(ignoredNames)]) {
                if ([[self ignoredNames] containsObject:key]) continue;
            }
            [aCoder encodeObject:[self valueForKey:key] forKey:key];
        }
        free(ivars);
        c = [c superclass];
    }
}

@end

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];
    }
    
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 226,608评论 6 524
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 97,559评论 3 411
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 174,222评论 0 371
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,103评论 1 306
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 70,943评论 6 405
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,480评论 1 318
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,589评论 3 433
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 41,743评论 0 283
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,253评论 1 329
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,233评论 3 352
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,366评论 1 363
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 37,932评论 5 354
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,624评论 3 342
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,047评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,246评论 1 278
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 50,953评论 3 385
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,351评论 2 369