iOS Runtime常用方法整理

关于runtime的学习网上有很多博客,在学习之前也查过很多资料,觉得南峰子老师博客中对 runtime 的讲解挺详细的,博客地址:http://southpeak.github.io/categories/objectivec/ 想要学习的可以去认真的看看.

1.runtime动态创建一个类,添加成员变量,添加方法

// 自定义一个方法
void sayFunction(id self, SEL _cmd, id some) {
    NSLog(@"%@岁的%@说:%@", object_getIvar(self, class_getInstanceVariable([self class], "_age")),[self valueForKey:@"name"],some);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        // 动态创建对象 创建一个Person 继承自 NSObject类
        Class People = objc_allocateClassPair([NSObject class], "Person", 0);
        
        // 为该类添加NSString *_name成员变量
        // class_addIvar(<#__unsafe_unretained Class cls#>, <#const char *name#>, <#size_t size#>, <#uint8_t alignment#>, <#const char *types#>)
        /*注意: 添加成员变量方法只能在objc_allocateClassPair函数与objc_registerClassPair之间调用,另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<<alignment。这取决于ivar的类型和机器的架构。如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))。*/
        class_addIvar(People, "_name", sizeof(NSString*), log2(sizeof(NSString*)), @encode(NSString*));
        // 为该类添加int _age成员变量
        class_addIvar(People, "_age", sizeof(int), sizeof(int), @encode(int));
        
        
        // 注册方法名为say的方法
        SEL s = sel_registerName("say:");
        // 为该类增加名为say的方法
        class_addMethod(People, s, (IMP)sayFunction, "v@:@");

        // 注册该类
        objc_registerClassPair(People);
        
        // 创建一个类的实例
        id peopleInstance = [[People alloc] init];
        
        // KVC 动态改变 对象peopleInstance 中的实例变量
        [peopleInstance setValue:@"苍老师" forKey:@"name"];
        
        // 从类中获取成员变量Ivar
        Ivar ageIvar = class_getInstanceVariable(People, "_age");
        // 为peopleInstance的成员变量赋值
        object_setIvar(peopleInstance, ageIvar, @18);
        
        // 调用 peopleInstance 对象中的 s 方法选择器对于的方法
        // objc_msgSend(peopleInstance, s, @"大家好!"); // 这样写也可以,请看我博客说明
         ((void (*)(id, SEL, id))objc_msgSend)(peopleInstance, s, @"大家好");
        peopleInstance = nil; //当People类或者它的子类的实例还存在,则不能调用objc_disposeClassPair这个方法;因此这里要先销毁实例对象后才能销毁类;
        
        // 销毁类
        objc_disposeClassPair(People);
        
        /*
         * 返回指定对象的一份拷贝 id object_copy ( id obj, size_t size );
         * 释放指定对象占用的内存 id object_dispose ( id obj );
         */

    }
    return 0;
}

2.获取一个类中所有的属性,成员变量, 方法

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        People *cangTeacher = [[People alloc] init];
        cangTeacher.name = @"苍井空";
        cangTeacher.age = 18;
        [cangTeacher setValue:@"老师" forKey:@"occupation"];

        NSDictionary *propertyResultDic = [cangTeacher allProperties];
        for (NSString *propertyName in propertyResultDic.allKeys) {
            NSLog(@"propertyName:%@, propertyValue:%@",propertyName, propertyResultDic[propertyName]);
        }
        
        NSDictionary *ivarResultDic = [cangTeacher allIvars];
        for (NSString *ivarName in ivarResultDic.allKeys) {
            NSLog(@"ivarName:%@, ivarValue:%@",ivarName, ivarResultDic[ivarName]);
        }

        NSDictionary *methodResultDic = [cangTeacher allMethods];
        for (NSString *methodName in methodResultDic.allKeys) {
            NSLog(@"methodName:%@, argumentsCount:%@", methodName, methodResultDic[methodName]);
        }
        
    }
    return 0;
}
//
//  People.h
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface People : NSObject
{
    NSString *_occupation;
    NSString *_nationality;
}

@property (nonatomic, copy) NSString *name;
@property (nonatomic) NSUInteger age;

- (NSDictionary *)allProperties;
- (NSDictionary *)allIvars;
- (NSDictionary *)allMethods;

@end
//
//  People.m
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import "People.h"

#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif

@implementation People

- (NSDictionary *)allProperties
{
    unsigned int count = 0;
    
    // 获取类的所有属性,如果没有属性count就为0
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    NSMutableDictionary *resultDict = [@{} mutableCopy];
    
    for (NSUInteger i = 0; i < count; i ++) {
        
        // 获取属性的名称和值
        const char *propertyName = property_getName(properties[i]);
        NSString *name = [NSString stringWithUTF8String:propertyName];
        id propertyValue = [self valueForKey:name];
        
        if (propertyValue) {
            resultDict[name] = propertyValue;
        } else {
            resultDict[name] = @"字典的key对应的value不能为nil哦!";
        }
    }
    
    // 这里properties是一个数组指针,我们需要使用free函数来释放内存。
    free(properties);
    
    return resultDict;
}

- (NSDictionary *)allIvars
{
    unsigned int count = 0;
    
    NSMutableDictionary *resultDict = [@{} mutableCopy];
    
    Ivar *ivars = class_copyIvarList([self class], &count);
    
    for (NSUInteger i = 0; i < count; i ++) {
        
        const char *varName = ivar_getName(ivars[i]);
        NSString *name = [NSString stringWithUTF8String:varName];
        id varValue = [self valueForKey:name];
        
        if (varValue) {
            resultDict[name] = varValue;
        } else {
            resultDict[name] = @"字典的key对应的value不能为nil哦!";
        }

    }
    
    free(ivars);
    
    return resultDict;
}

- (NSDictionary *)allMethods
{
    unsigned int count = 0;
    
    NSMutableDictionary *resultDict = [@{} mutableCopy];
    
    // 获取类的所有方法,如果没有方法count就为0
    Method *methods = class_copyMethodList([self class], &count);
    
    for (NSUInteger i = 0; i < count; i ++) {
        
        // 获取方法名称
        SEL methodSEL = method_getName(methods[i]);
        const char *methodName = sel_getName(methodSEL);
        NSString *name = [NSString stringWithUTF8String:methodName];
        
        // 获取方法的参数列表
        int arguments = method_getNumberOfArguments(methods[i]);
        
        resultDict[name] = @(arguments-2);
    }
    
    free(methods);
    
    return resultDict;
}

@end

3.给分类添加属性

 People *cangTeacher = [[People alloc] init];
   cangTeacher.associatedBust = @(90);
        cangTeacher.associatedCallBack = ^(){
            
            NSLog(@"苍老师要写代码了!");
        
        };
        cangTeacher.associatedCallBack();
//
//  People+Associated.h
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import "People.h"

typedef void (^CodingCallBack)();

@interface People (Associated)

@property (nonatomic, strong) NSNumber *associatedBust; // 胸围
@property (nonatomic, copy) CodingCallBack associatedCallBack; // 写代码

@end
//
//  People+Associated.m
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import "People+Associated.h"

#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif

@implementation People (Associated)

- (void)setAssociatedBust:(NSNumber *)bust
{
    // 设置关联对象
    objc_setAssociatedObject(self, @selector(associatedBust), bust, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSNumber *)associatedBust
{
    // 得到关联对象
    return objc_getAssociatedObject(self, @selector(associatedBust));
}

- (void)setAssociatedCallBack:(CodingCallBack)callback {
    objc_setAssociatedObject(self, @selector(associatedCallBack), callback, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (CodingCallBack)associatedCallBack {
    return objc_getAssociatedObject(self, @selector(associatedCallBack));
}

@end

4.最常用的使用 runtime 归档

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        People *cangTeacher = [[People alloc] init];
        cangTeacher.name = @"苍井空";
        cangTeacher.age = @18;
        cangTeacher.occupation = @"老师";
        cangTeacher.nationality = @"日本";
        
        NSString *path = NSHomeDirectory();
        path = [NSString stringWithFormat:@"%@/cangTeacher",path];
        // 归档
        [NSKeyedArchiver archiveRootObject:cangTeacher toFile:path];
        // 解归档
        People *teacher = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
        NSLog(@"热烈欢迎,从%@远道而来的%@岁的%@%@",teacher.nationality,teacher.age,teacher.name,teacher.occupation);
    }
    return 0;
}
//
//  People.h
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface People : NSObject <NSCoding>

@property (nonatomic, copy) NSString *name; // 姓名
@property (nonatomic, strong) NSNumber *age; // 年龄
@property (nonatomic, copy) NSString *occupation; // 职业
@property (nonatomic, copy) NSString *nationality; // 国籍

@end
//
//  People.m
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import "People.h"

#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif

@implementation People

- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int count = 0;
    //利用 runtime获取实例变量的列表
    Ivar *ivars = class_copyIvarList([People class], &count);
    for (NSUInteger i = 0; i < count; i ++) {
        //取出 i 位置对应的实例变量
        Ivar ivar = ivars[i];
        //查看实例变量的名字
        const char *name = ivar_getName(ivar);
        //C语言转化为 NSString
        NSString *key = [NSString stringWithUTF8String:name];
        //使用 KVC 取出属性对应的值
        id value = [self valueForKey:key];
        //归档
        [aCoder encodeObject:value forKey:key];
    }
    //释放
    free(ivars);
}

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([People class], &count);
        for (NSUInteger i = 0; i < count; i ++) {
            Ivar ivar = ivars[i];
            const char *name = ivar_getName(ivar);
            NSString *key = [NSString stringWithUTF8String:name];
            id value = [aDecoder decodeObjectForKey:key];
            [self setValue:value forKey:key];
        }
        free(ivars);
    }
    return self;
}

@end

5.使用runtime 字典转模型

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSDictionary *dict = @{
                               @"name" : @"苍井空",
                               @"age"  : @18,
                               @"occupation" : @"老师",
                               @"nationality" : @"日本"
                               };
        
        // 字典转模型
        People *cangTeacher = [[People alloc] initWithDictionary:dict];
        NSLog(@"热烈欢迎,从%@远道而来的%@岁的%@%@",cangTeacher.nationality,cangTeacher.age,cangTeacher.name,cangTeacher.occupation);
        
        // 模型转字典
        NSDictionary *covertedDict = [cangTeacher covertToDictionary];
        NSLog(@"%@",covertedDict);

    }
    return 0;
}
//
//  People.h
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface People : NSObject

@property (nonatomic, copy) NSString *name; // 姓名
@property (nonatomic, strong) NSNumber *age; // 年龄
@property (nonatomic, copy) NSString *occupation; // 职业
@property (nonatomic, copy) NSString *nationality; // 国籍

// 生成model
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;

// 转换成字典
- (NSDictionary *)covertToDictionary;

@end
//
//  People.m
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import "People.h"

#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif

@implementation People

- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
    self = [super init];
    
    if (self) {
        for (NSString *key in dictionary.allKeys) {
            id value = dictionary[key];
            
            SEL setter = [self propertySetterByKey:key];
            if (setter) {
                // 这里还可以使用NSInvocation或者method_invoke,不再继续深究了,有兴趣google。
                ((void (*)(id, SEL, id))objc_msgSend)(self, setter, value);
            }
        }
    }
    return self;
}

- (NSDictionary *)covertToDictionary
{
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList([self class], &count);
    
    if (count != 0) {
        NSMutableDictionary *resultDict = [@{} mutableCopy];
        
        for (NSUInteger i = 0; i < count; i ++) {
            const void *propertyName = property_getName(properties[i]);
            NSString *name = [NSString stringWithUTF8String:propertyName];
            
            SEL getter = [self propertyGetterByKey:name];
            if (getter) {
                id value = ((id (*)(id, SEL))objc_msgSend)(self, getter);
                if (value) {
                    resultDict[name] = value;
                } else {
                    resultDict[name] = @"字典的key对应的value不能为nil哦!";
                }
                
            }
        }
        
        free(properties);
        
        return resultDict;
    }
    
    free(properties);
    
    return nil;
}

#pragma mark - private methods

// 生成setter方法
- (SEL)propertySetterByKey:(NSString *)key
{
    // 首字母大写,你懂得
    NSString *propertySetterName = [NSString stringWithFormat:@"set%@:", key.capitalizedString];
    
    SEL setter = NSSelectorFromString(propertySetterName);
    if ([self respondsToSelector:setter]) {
        return setter;
    }
    return nil;
}

// 生成getter方法
- (SEL)propertyGetterByKey:(NSString *)key
{
    SEL getter = NSSelectorFromString(key);
    if ([self respondsToSelector:getter]) {
        return getter;
    }
    return nil;
}

@end

6.方法的替换

//
//  UIImage+Image.h
//  runtime
//
//  Created by Apple on 16/12/20.
//  Copyright © 2016年 叶炯. All rights reserved.
//

#import <UIKit/UIKit.h>

@interface UIImage (Image)

+ (__kindof UIImage *)yj_Imagename:(NSString *)name;
@end
//
//  UIImage+Image.m
//  runtime
//
//  Created by Apple on 16/12/20.
//  Copyright © 2016年 叶炯. All rights reserved.
//

#import "UIImage+Image.h"
#import <objc/message.h>
@implementation UIImage (Image)

//在分类里面不能调用分类,因为分类没有父类
//+ (UIImage *)imageNamed:(NSString *)name {
//
//    [super im];
//}

+ (__kindof UIImage *)yj_Imagename:(NSString *)name {

    //1. 加载图片
    //这里不能调用系统的 imageName, 否则会造成死循环
    UIImage *image = [UIImage yj_Imagename:name];
    
    //2. 判断功能
    if (image == nil) {
        NSLog(@"image为空");
    }
    
    return image;
}

//加载这个分类的时候就会调用
+ (void)load {

    NSLog(@"%s",__func__);
    
    //交换方法实现 方法都是定义在类里面
    //交换方法以 Method 开头
    //IMP: 方法的实现
    //获取类方法的实现(返回IMP)
//    class_getMethodImplementation(<#__unsafe_unretained Class cls#>, <#SEL name#>)
    
    //获取类方法
    /* Class: 获取那个类方法
     * SEL : 获取方法的编号,根据 SEL 就能去找对应的类方法了
     */
    Method imageNamedMethod = class_getClassMethod([UIImage class], @selector(imageNamed:));
    
    Method yj_imagenameMethod = class_getClassMethod([UIImage class], @selector(yj_Imagename:));
//    //获取对象方法(返回 Method)
//    Method yj_imagenameMethod = class_getInstanceMethod([UIImage class], @selector(yj_Imagename:));
    //交换方法的实现
    method_exchangeImplementations(imageNamedMethod, yj_imagenameMethod);
    
    
}

@end

 

7.动态添加方法

    Person *p = [[Person alloc] init];

    //performSelector 动态添加方法
//    [p performSelector:@selector(weight)];
    
    //带参数的动态添加方法
    [p performSelector:@selector(weight:) withObject:@111];
//
//  Person.h
//  runtime
//
//  Created by Apple on 16/12/20.
//  Copyright © 2016年 叶炯. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Person : NSObject

@end
//
//  Person.m
//  runtime
//
//  Created by Apple on 16/12/20.
//  Copyright © 2016年 叶炯. All rights reserved.
//  动态添加方法

#import "Person.h"
#import <objc/message.h>
@implementation Person

//对象在接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或 者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法”“。不过使用该方法的前提是我们已经 实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。


///---------不带参数动态添加方法-----------------------------------------------------------------*/

////函数
//void weight(id self, SEL _cmd) {
//
//    NSLog(@"---动态添加方法了%@ %@ %@",self, NSStringFromSelector(_cmd));
//}
/////resolveInstanceMethod 动态添加方法,
/////首先实现这个resolveInstanceMethod调用:当一个方法没有实现,但是又调用了这个,就会调用resolveInstanceMethod
////resolveInstanceMethod作用:
////SEL: 没有实现的方法
//+ (BOOL)resolveInstanceMethod:(SEL)sel {
//
////    NSLog(@"%@",NSStringFromSelector(sel));
//    //动态添加方法
//    if (sel == @selector(weight)) {
//       
//        /*
//         * class 给那个类添加方法
//         * SEL: 添加方法的编号
//         * IMP: 方法的实现,函数入口.函数名
//         * types: 方法类型
//         */
//        class_addMethod(self, sel, (IMP)weight, "v@:");
//    
//        return YES;
//    }
//    return [super resolveInstanceMethod:sel];
//}
//
///---------不=带参数动态添加方法-----------------------------------------------------------------*/

//函数
void weight(id self, SEL _cmd ,id param) {
    
    NSLog(@"---动态添加方法了%@ %@ %@",self, NSStringFromSelector(_cmd), param);
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    //    NSLog(@"%@",NSStringFromSelector(sel));
    //动态添加方法
    if (sel == @selector(weight:)) {
        
        /*
         * class 给那个类添加方法
         * SEL: 添加方法的编号
         * IMP: 方法的实现,函数入口.函数名
         * types: 方法类型
         */
        class_addMethod(self, sel, (IMP)weight, "v@:@");
        
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

//如果上一步无法处理消息,则 runtime 会继续调用一下方法
//- (id)forwardingTargetForSelector:(SEL)aSelector {
//
//    return [super forwardingTargetForSelector:aSelector];
//}
//如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。



@end

 

8.runtime 消息转发机制 这里南峰子老师博客中写的很好

消息转发机制基本上分为三个步骤:

  1. 动态方法解析
  2. 备用接收者
  3. 完整转发

动态方法解析

对象在接收到未知的消息时,首先会调用所属类的类方法+resolveInstanceMethod:(实例方法)或者+resolveClassMethod:(类方法)。在这个方法中,我们有机会为该未知消息新增一个”处理方法””。不过使用该方法的前提是我们已经实现了该”处理方法”,只需要在运行时通过class_addMethod函数动态添加到类里面就可以了。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        People *cangTeacher = [[People alloc] init];
        cangTeacher.name = @"苍老师";
        [cangTeacher sing];

    }
    return 0;
}
//
//  People.h
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface People : NSObject

@property (nonatomic, copy) NSString *name;

- (void)sing;

@end
//
//  People.m
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import "People.h"

#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif

@implementation People

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    // 我们没有给People类声明sing方法,我们这里动态添加方法
    if ([NSStringFromSelector(sel) isEqualToString:@"sing"]) {
        class_addMethod(self, sel, (IMP)otherSing, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void otherSing(id self, SEL cmd)
{
    NSLog(@"%@ 唱歌啦!",((People *)self).name);
}

@end

不过这种方案更多的是为了实现@dynamic属性。

备用接收者

如果在上一步无法处理消息,则Runtime会继续调以下方法:

- (id)forwardingTargetForSelector:(SEL)aSelector

 

如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。

使用这个方法通常是在对象内部,可能还有一系列其它对象能处理该消息,我们便可借这些对象来处理消息并返回,这样在对象外部看来,还是由该对象亲自处理了这一消息。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Bird *bird = [[Bird alloc] init];
        bird.name = @"小小鸟";
        
        ((void (*)(id, SEL))objc_msgSend)((id)bird, @selector(sing));
    }
    return 0;
}
//
//  Bird.h
//  runtimeTest
//
//  Created by ian on 16/1/26.
//  Copyright © 2016年 ian. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Bird : NSObject

@property (nonatomic, copy) NSString *name;

@end
//
//  Bird.m
//  runtimeTest
//
//  Created by ian on 16/1/26.
//  Copyright © 2016年 ian. All rights reserved.
//

#import "Bird.h"
#import "People.h"

@implementation Bird

// 第一步:我们不动态添加方法,返回NO,进入第二步;
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    return NO;
}

// 第二部:我们不指定备选对象响应aSelector,进入第三步;
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return nil;
}

// 第三步:返回方法选择器,然后进入第四部;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if ([NSStringFromSelector(aSelector) isEqualToString:@"sing"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

// 第四部:这步我们修改调用对象
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    // 我们改变调用对象为People
    People *cangTeacher = [[People alloc] init];
    cangTeacher.name = @"苍老师";
    [anInvocation invokeWithTarget:cangTeacher];
}

@end

这一步合适于我们只想将消息转发到另一个能处理该消息的对象上。但这一步无法对消息进行处理,如操作消息的参数和返回值。

完整消息转发

如果在上一步还不能处理未知消息,则唯一能做的就是启用完整的消息转发机制了。此时会调用以下方法:

    
- (void)forwardInvocation:(NSInvocation *)anInvocation

运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation方法中选择将消息转发给其它对象。

forwardInvocation:方法的实现有两个任务:

  1. 定位可以响应封装在anInvocation中的消息的对象。这个对象不需要能处理所有未知消息。
  2. 使用anInvocation作为参数,将消息发送到选中的对象。anInvocation将会保留调用结果,运行时系统会提取这一结果并将其发送到消息的原始发送者。

不过,在这个方法中我们可以实现一些更复杂的功能,我们可以对消息的内容进行修改,比如追回一个参数等,然后再去触发消息。另外,若发现某个消息不应由本类处理,则应调用父类的同名方法,以便继承体系中的每个类都有机会处理此调用请求。

 还有一个很重要的问题,我们必须重写以下方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        People *cangTeacher = [[People alloc] init];
        
        ((void(*)(id, SEL)) objc_msgSend)((id)cangTeacher, @selector(sing));

    }
    return 0;
}
//
//  People.h
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface People : NSObject

@end
//
//  People.m
//  runtimeTest
//
//  Created by ian on 16/1/22.
//  Copyright © 2016年 ian. All rights reserved.
//

#import "People.h"

#if TARGET_IPHONE_SIMULATOR
#import <objc/objc-runtime.h>
#else
#import <objc/runtime.h>
#import <objc/message.h>
#endif

@implementation People

// 第一步:我们不动态添加方法,返回NO,进入第二步;
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    return NO;
}

// 第二部:我们不指定备选对象响应aSelector,进入第三步;
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return nil;
}

// 第三步:返回方法选择器,然后进入第四部;
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if ([NSStringFromSelector(aSelector) isEqualToString:@"sing"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

// 第四部:这步我们修改调用方法
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    [anInvocation setSelector:@selector(dance)];
    // 这还要指定是哪个对象的方法
    [anInvocation invokeWithTarget:self];
}

// 若forwardInvocation没有实现,则会调用此方法
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
    NSLog(@"消息无法处理:%@", NSStringFromSelector(aSelector));
}

- (void)dance
{
    NSLog(@"跳舞!!!come on!");
}

@end

 

posted @ 2017-02-06 17:46  _水畔竹汐  阅读(547)  评论(0编辑  收藏  举报