runtime应用总结

runtime是OC中一个很重要的概念,通过这个机制可以帮我们做很多事情。
不给出demo的文章都是耍流氓
demo截图如下:

首先我们先创建一个Person类。.h和.m文件如下:

#import <Foundation/Foundation.h>

@interface Person : NSObject <NSCoding>

@property (nonatomic, assign) int age; // 属性变量

- (void)func1;
- (void)func2;

- (void)sayHello1:(NSString *)name;

@end


#import "Person.h"
#import <objc/runtime.h>
@interface Person ()

@property (nonatomic, copy) NSString *name;

@end

@implementation Person {
    NSString *instanceName;
}

- (instancetype)init
{
    self = [super init];
    if (self) {
        _name = @"Tom";
        instanceName = @"Jim";
        _age = 12;
    }
    return self;
}

// 需要归档哪些属性! 常规方法
//- (void)encodeWithCoder:(NSCoder *)aCoder
//{
//    [aCoder encodeObject:_name forKey:@"name"];
//    [aCoder encodeInt:_age forKey:@"age"];
//}
// 解档
//- (instancetype)initWithCoder:(NSCoder *)aDecoder
//{
//    self = [super init];
//    if (self) {
//        _name = [aDecoder decodeObjectForKey:@"name"];
//        _age = [aDecoder decodeIntForKey:@"age"];
//    }
//    return self;
//}

// 使用runtime来归档、解档
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        // 拿到每个成员变量
        Ivar ivar = ivars[i];
        // 拿名称
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        
        // 归档 -- 利用KVC
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Person class], &count);
        for (int 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];
            // 利用KVC设置值
            [self setValue:value forKey:key];
        }
    }
    return self;
}
- (void)func1
{
    NSLog(@"执行了func1方法");
}
- (void)func2
{
    NSLog(@"执行了func2方法");
}
// 测试消息转发
- (void)sayHello1:(NSString *)name
{
    NSLog(@"Hello, I am a person");
}
- (NSString *)description
{
    return [NSString stringWithFormat:@"name:%@ age:%d", self.name, self.age];
}
@end

1.获取所有变量

Ivar、class_copyIvarList、ivar_getName、ivar_getTypeEncoding
objc_property_t、class_copyPropertyList、property_getName

- (IBAction)getAllVariable:(id)sender
{
    unsigned int count = 0;
    // 获取类的一个包含所有变量的列表,Ivar是runtime声明的一个宏,是实例变量的意思
    Ivar *allVariables = class_copyIvarList([Person class], &count);
    
    for (int i  = 0; i < count; i++) {
        // 遍历每一个变量,包括名称和类型 (此处没有星号"*"),
        Ivar ivar = allVariables[i];
        const char *variablename = ivar_getName(ivar); // 获取成员变量名称
        const char *variableType = ivar_getTypeEncoding(ivar); // 获取成员变量类型
        NSLog(@"(Name: %s)----(Type:%s)", variablename, variableType);
    }
    /*
         2017-07-05 13:07:00.735 RuntimeDemo[3353:127039] (Name: instanceName)----(Type:@"NSString")
         2017-07-05 13:07:00.735 RuntimeDemo[3353:127039] (Name: _age)----(Type:i)
         2017-07-05 13:07:00.736 RuntimeDemo[3353:127039] (Name: _name)----(Type:@"NSString")
         Ivar,一个指向objc_ivar结构体指针,包含了变量名、变量类型等信息。可以看到私有属性_name instanceName都能够访问到了。在有些项目中,为了对某些私有属性进行隐藏,某些.h文件中没有出现相应的显式创建,而是如上面的person类中,在.m中进行私有创建,但是我们可以通过runtime这个有效的方法,访问到所有包括这些隐藏的私有变量。
    */
    NSLog(@"测试一下class_copyPropertyList的区别");
    
    objc_property_t *allProperties = class_copyPropertyList([Person class], &count);
    for (int i = 0; i < count; i++) {
        objc_property_t property = allProperties[i];
        const char *char_f = property_getName(property);
        NSString *propertyName = [NSString stringWithUTF8String:char_f];
        NSLog(@"property = %@", propertyName);
    }
    /*
     2017-07-05 11:55:16.961 RuntimeDemo[3187:98751] property = name
     2017-07-05 11:55:16.961 RuntimeDemo[3187:98751] property = age
     
     如果单单需要获取属性列表,可以使用函数:class_copyPropertyList(),instanceName作为实例变量是不被获取的,而class_copyIvarList()函数则能够返回实例变量和属性变量的所有成员变量。
     */
    
    free(allVariables);
    free(allProperties);
    
}

2. 获取Person所有方法

Method、class_copyMethodList、SEL、method_getName、sel_getName
这里获取方法列表,所有.m文件显式实现的方法都会被找到,当然也包括setter+getter方法了。Method是runtime声明的一个宏,表示对一个方法的描述。method_getName是获取SEL,即获取方法选择器@selector()。还可以通过sel_getName以字符串获取sel的name,也即@selector()中的方法名称。

- (IBAction)getAllMethod:(id)sender
{
    unsigned int count;
    // 获取方法列表,所有在.m文件显式实现的方法都会被找到,包括setter+getter方法;
    Method *allMethods = class_copyMethodList([Person class], &count);
    for (int i = 0; i < count; i++) {
        // Method,为runtime声明的一个宏,表示对一个方法的描述
        Method md = allMethods[i];
        // 获取SEL:SEL类型,即获取方法选择器@selector()
        SEL sel = method_getName(md);
        // 得到sel的方法名:以字符串格式获取sel的name,也即@selector()中的方法名称
        const char *methodname = sel_getName(sel);
        NSLog(@"(Method:%s)", methodname);
    }
}

/*
 控制台输出:
 2017-07-05 13:17:13.380 RuntimeDemo[3392:134673] (Method:age)
 2017-07-05 13:17:13.381 RuntimeDemo[3392:134673] (Method:func1)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:func2)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:setAge:)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:.cxx_destruct)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:description)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:name)
 2017-07-05 13:17:13.386 RuntimeDemo[3392:134673] (Method:setName:)
 2017-07-05 13:17:13.387 RuntimeDemo[3392:134673] (Method:init)

 控制台输出了包括set和get等方法名。
 分析:Method是一个指向objc_method结构体指针,表示对类中的某个方法的描述。
 在api中的定义typedef struct objc_method *Method;
 而objc_method结构体如下:
 struct objc_method {
 SEL method_name                                          OBJC2_UNAVAILABLE;
 char *method_types                                       OBJC2_UNAVAILABLE;
 IMP method_imp                                           OBJC2_UNAVAILABLE;
 }
 method_name:方法选择器@selector(),类型为SEL。相同名字的方法下,即使在不同类中定义,它们的方法选择器也相同。
 method_types:方法类型,是个char指针,存储着方法的参数类型和返回值类型。
 method_imp: 指向方法的具体实现的指针,数据类型为IMP,本质上是一个函数指针。
 
 SEL:数据类型,表示方法选择器,可以理解为对方法的一种包装。在每个方法都有一个与之对应的SEL类型的数据,根据一个SEL数据"@selector(方法名)"就可以找到对应的方法地址,进而调用方法。
 因此可以通过:获取Method结构体->得到SEL选择器的名称->得到对应的方法名,这样的方式认识OC中关于方法的定义。
 */

3.改变person的_name变量属性

Ivar、class_copyIvarList、object_setIvar

- (IBAction)changeVariable:(id)sender {
    NSLog(@"改变前的person:%@", self.person);
    unsigned int count = 0;
    Ivar *allList = class_copyIvarList([Person class], &count);
    Ivar ivv = allList[2];
    object_setIvar(self.person, ivv, @"Mike"); // name属性Tom被强制改为Mike。
    NSLog(@"改变之后的person: %@", self.person);
}

4.添加新属性

objc_setAssociatedObject、objc_getAssociatedObject
id objc_getAssociatedObject(id object, const void *key)
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
这里第一个参数是需要添加属性的对象,第二个参数是属性的key,必须是C语言字符串,第三个参数是属性的值,类型为id,所以此处先将height转为NSNumber类型,在分类中,即使使用@property定义了,也只是生产set+get方法,而不会生成_变量名,分类中是不允许定义变量的。

#import "Person.h"
@interface Person (PersonCategory)
@property (nonatomic, assign) float height; // 新属性
@end

#import "Person+PersonCategory.h"
#import <objc/runtime.h>

const char * str = "myKey"; // 作为key,字符常量 必须是C语言字符串

@implementation Person (PersonCategory)

- (void)setHeight:(float)height
{
    NSNumber *num = [NSNumber numberWithFloat:height];
    
    /*
     第一个参数是需要添加属性的对象;
     第二个参数是属性的key;
     第三个参数是属性的值,类型必须为id,所以此处height先转为NSNumber类型;
     第四个参数是使用策略,是一个枚举值,类似@property属性创建时设置的关键字,可从命名看出各枚举的意义
     OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
     */
    objc_setAssociatedObject(self, str, num, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

// 提取属性的值
- (float)height
{
    NSNumber *number = objc_getAssociatedObject(self, str);
    return [number floatValue];
}
@end
- (IBAction)addVariable:(id)sender {
    self.person.height = 12;    // 给新属性height赋值
    NSLog(@"添加的新属性height = %f", [self.person height]); // 访问新属性
    
}

/*
 点击按钮四、再点击按钮一、二获取类的属性、方法
 2017-07-05 14:14:23.648 RuntimeDemo[3640:165606] 12.000000
 2017-07-05 14:14:28.026 RuntimeDemo[3640:165606] (Name: instanceName)----(Type:@"NSString")
 2017-07-05 14:14:28.026 RuntimeDemo[3640:165606] (Name: _age)----(Type:i)
 2017-07-05 14:14:28.026 RuntimeDemo[3640:165606] (Name: _name)----(Type:@"NSString")
 2017-07-05 14:14:28.027 RuntimeDemo[3640:165606] 测试一下class_copyPropertyList的区别
 2017-07-05 14:14:28.027 RuntimeDemo[3640:165606] property = height
 2017-07-05 14:14:28.027 RuntimeDemo[3640:165606] property = name
 2017-07-05 14:14:28.027 RuntimeDemo[3640:165606] property = age
 2017-07-05 14:14:28.886 RuntimeDemo[3640:165606] (Method:age)
 2017-07-05 14:14:28.886 RuntimeDemo[3640:165606] (Method:func1)
 2017-07-05 14:14:28.886 RuntimeDemo[3640:165606] (Method:func2)
 2017-07-05 14:14:28.887 RuntimeDemo[3640:165606] (Method:setAge:)
 2017-07-05 14:14:28.887 RuntimeDemo[3640:165606] (Method:.cxx_destruct)
 2017-07-05 14:14:28.887 RuntimeDemo[3640:165606] (Method:description)
 2017-07-05 14:14:28.887 RuntimeDemo[3640:165606] (Method:name)
 2017-07-05 14:14:28.887 RuntimeDemo[3640:165606] (Method:setName:)
 2017-07-05 14:14:28.888 RuntimeDemo[3640:165606] (Method:init)
 2017-07-05 14:14:28.888 RuntimeDemo[3640:165606] (Method:height)
 2017-07-05 14:14:28.888 RuntimeDemo[3640:165606] (Method:setHeight:)
 可以看到分类的新属性可以在person对象中对新属性height进行访问赋值。
 获取person类属性时,依然没有height的存在,但是却有height和setHeight这两个方法;因为在分类中,即使使用@property定义了,也只是生产set+get方法,而不会生成_变量名,分类中是不允许定义变量的。
 使用runtime中objc_setAssociatedObject()和objc_getAssociatedObject()方法,本质上只是为对象person添加了对height的属性关联,但是达到了新属性的作用;
 使用场景:假设imageCategory是UIImage类的分类,在实际开发中,我们使用UIImage下载图片或者操作过程需要增加一个URL保存一段地址,以备后期使用。这时可以尝试在分类中动态添加新属性MyURL进行存储。
 */

5.添加新的方法(这种方法等价于对person类添加Category对方法进行扩展)

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
这里第一个参数表示Class cls类型,第二个参数表示待调用的方法名称,第三个参数IMP表示一个函数指针,这里表示指定具体的实现。

- (IBAction)addMethod:(id)sender {
    /* 动态添加方法:
     第一个参数表示 Class cls 类型;
     第二个参数表示待调用的方法名称;
     第三个参数 (IMP)myAddingFunction,IMP一个函数指针,这里表示指定具体实现方法myAddingFunction;
     第四个参数表示方法的参数,0代表没有参数;
     
     */
    class_addMethod([Person class], @selector(NewMethod), (IMP)myAddingFunction, 0);
    [self.person performSelector:@selector(NewMethod)];
}

// 具体的实现(方法的内部都默认包含两个参数Class和SEL方法,被称为隐式参数。)
int myAddingFunction(id self, SEL _cmd) {
    NSLog(@"已新增方法:NewMethod");
    return 1;
}

6.交换两种方法之后(功能对调)

Method、class_getInstanceMethod、method_exchangeImplementation
要使用dispatch_once执行方法交换,方法交换要求线程安全,而且保证在任何情况下只能交换一次。
回顾下NSObject类的+load和+initialize这两个方法吧。在程序启动时,Runtime会去加载所有的类。在这一时期,如果类或者类的分类实现了+load方法,则会去调用这个方法。而+initialize方法是在类或子类第一次接收消息之前会被调用,这包括类的实例对象或者类对象。如果类一直没有被用到,则这个方法不会被调用。基于这两个方法的特殊性,我们可以将类使用时所需要的一些前置条件在这两个方法中处理。不过,如果可能,应该尽量放在+initialize中。因为+load方法是在程序启动时调用,势必会影响到程序的启动时间。而+initialize方法可以说是懒加载调用,只有用到才会去执行。

- (IBAction)replaceMethod:(id)sender {
    Method method1 = class_getInstanceMethod([self.person class], @selector(func1));
    Method method2 = class_getInstanceMethod([self.person class], @selector(func2));
    
    // 交换方法
    method_exchangeImplementations(method1, method2);
    [self.person func1];
}
- (void)func1
{
    NSLog(@"执行了func1方法");
}
- (void)func2
{
    NSLog(@"执行了func2方法");
}
/*
 输出
 2017-07-06 17:22:39.399 RuntimeDemo[10188:825945] 执行了func2方法
 交换方法的使用场景:项目中的某个功能,在项目中需要多次被引用,当项目的需求发生改变时,要使用另一种功能代替这个功能,且要求不改变旧的项目,也就是不改变原来方法实现的前提下。那么,我们可以在分类中,再写一个新的方法,符合新的需求的方法,然后交换两个方法的实现。这样,在不改变项目的代码,而只是增加了新的代码的情况下,就完成了项目的改进,很好地体现了该项目的封装性与利用率。
 注:交换两个方法的实现一般写在类的load方法里面,因为load方法会在程序运行前加载一次。
 
 */

7.获取协议列表

Protocol、class_copyProtocolList、protocol_getName

- (IBAction)fetchProtocolList:(id)sender {
    unsigned int count = 0;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([self.person class], &count);
    NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
    for (unsigned i = 0; i < count; i++) {
        Protocol *protocol = protocolList[i];
        const char *protocolName = protocol_getName(protocol);
        [mutableList addObject:[NSString stringWithUTF8String:protocolName]];
    }
    NSLog(@"获取到的协议列表为:%@", mutableList);
}
/*
 2017-07-06 17:23:36.327 RuntimeDemo[10188:825945] 获取到的协议列表为:(
 NSCoding
 )

 */

8. 序列化相关,归档&解档

这里实际上还是获取到所有属性值,然后使用KVC来设置值。注意是KVC。

// 需要归档哪些属性! 常规方法
//- (void)encodeWithCoder:(NSCoder *)aCoder
//{
//    [aCoder encodeObject:_name forKey:@"name"];
//    [aCoder encodeInt:_age forKey:@"age"];
//}
// 解档
//- (instancetype)initWithCoder:(NSCoder *)aDecoder
//{
//    self = [super init];
//    if (self) {
//        _name = [aDecoder decodeObjectForKey:@"name"];
//        _age = [aDecoder decodeIntForKey:@"age"];
//    }
//    return self;
//}

// 使用runtime来归档、解档
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList([Person class], &count);
    for (int i = 0; i < count; i++) {
        // 拿到每个成员变量
        Ivar ivar = ivars[i];
        // 拿名称
        const char *name = ivar_getName(ivar);
        NSString *key = [NSString stringWithUTF8String:name];
        
        // 归档 -- 利用KVC
        id value = [self valueForKey:key];
        [aCoder encodeObject:value forKey:key];
    }
}


- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        unsigned int count = 0;
        Ivar *ivars = class_copyIvarList([Person class], &count);
        for (int 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];
            // 利用KVC设置值
            [self setValue:value forKey:key];
        }
    }
    return self;
}

9. 实现字典和模型的自动转换

优秀的JSON转模型第三方库JSONModel、YYModel等都利用runtime对属性进行获取,赋值操作,要比KVC进行模型转换更强大,更有效率。在YYModel的源码可以看出,YY对NSObject的内容进行了又一次封装,添加了许多描述内容。其中YYClassInfo是对Class进行了再次封装,而YYClassIvarInfo、YYClassMethodInfo、YYCIPropertyInfo分别是对Class的Ivar、Method和property进行了封装和描述。在提取Class的相关信息时都运用了Runtime。

unsigned int ivarCount = 0;
    Ivar *ivars = class_copyIvarList(cls, &ivarCount);
    if (ivars) {
        NSMutableDictionary *ivarInfos = [NSMutableDictionary new];
        _ivarInfos = ivarInfos;
        for (unsigned int i = 0; i < ivarCount; i++) {
            YYClassIvarInfo *info = [[YYClassIvarInfo alloc] initWithIvar:ivars[i]];
            if (info.name) ivarInfos[info.name] = info;
        }
        free(ivars);
    }

以下是一个简陋版的字典转模型方案

@interface NSObject (ZCModel)
+(instancetype) setModelWithDict:(NSDictionary*)dict;
@end

@implementation NSObject (ZCModel)
+(instancetype) setModelWithDict:(NSDictionary*)dict{

    Class cls = [self class];
    id Model = [[self alloc]init];

    unsigned int count;
    //提取Class的property列表
    objc_property_t *property_t = class_copyPropertyList(cls, &count);
    //遍历列表,对每个property分别处理
    for (int i =0; i< count; i++) {
        objc_property_t property = property_t[i];
        NSString *key = [NSString stringWithUTF8String:property_getName(property)];

        id value = dict[key];
        if (!value) continue;
        //提取property的attribute信息
        NSString *attribure = [NSString stringWithUTF8String:property_getAttributes(property)];
        //从attribute信息中提取其class的信息
        if ([attribure hasPrefix:@"T@"]) {
            NSRange range =  [attribure rangeOfString:@"\","];
            NSString *typeString = [attribure substringWithRange:NSMakeRange(3, range.location  - 3)];

            NSLog(@"the property class is %@",typeString);
            //对非NS开头的class处理为嵌套的model,对model进行递归,转为模型
            if (![typeString hasPrefix:@"NS"]) {

                Class modelClass = NSClassFromString(typeString);
                value = [modelClass setModelWithDict:value];
            }
        }
        //将字典中的值设置给模型
        [Model setValue:value forKeyPath:key];
    }

    free(property_t);
    return Model;
}
@end

10. 实现跳转功能,

objc_getClass、objc_allocateClassPair、objc_registerClassPair

- (IBAction)push:(id)sender {
    NSDictionary *dic = @{@"class" : @"TestViewController",
                          @"property" : @{
                                  @"name" : @"guohongwei",
                                  @"name1" : @"guohongwei",

                                  @"phoneNum" : @"1234567890"
                                  }};
    // leim
    NSString *class = [NSString stringWithFormat:@"%@", dic[@"class"]];
    const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
    // 从一个字符串返回一个类
    Class newClass = objc_getClass(className);
    if (!newClass) {
        // 创建一个类
        Class superClass = [NSObject class];
        newClass = objc_allocateClassPair(superClass, className, 0);
        // 注册你创建的这个类
        objc_registerClassPair(newClass);
    }
    
    // 创建对象
    id instance = [[newClass alloc] init];
    
    // 对该对象赋值属性
    NSDictionary *propertys = dic[@"property"];
    [propertys enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
         //检测这个对象是否存在该属性, 如果没有检查的话,会crash,报错如下:reason: '[<TestViewController 0x7f88e1403f60> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key name1.'
        if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
            // 利用kvc赋值
            [instance setValue:obj forKey:key];
        }
    }];
    // 跳转到对应的控制器
    [self.navigationController pushViewController:instance animated:YES];
    
    
}

//    TestViewController *test = [[TestViewController alloc] init];
//    [self.navigationController pushViewController:test animated:YES];

- (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
{
    unsigned int outCount, i;
    // 获取对象里的属性列表
    objc_property_t *properties = class_copyPropertyList([instance class], &outCount);
    for (i  = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        // 属性名转为字符串
        NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
        // 判断该属性是否存在
        if ([propertyName isEqualToString:verifyPropertyName]) {
            free(properties);
            return YES;
        }
    }
    free(properties);
    return NO;
}

11. 消息转发(resolveInstanceMethod:)

一个方法的声明必定会有与之对应的实现,如果调用了只有声明没有实现的方法会导致程序crash,而实现并非只有中规中矩的在.m里写上相同的方法名再在内部写实现代码。
当调用[receiver message]时,会触发id objc_msgSend(id self, SEL op, ...)这个函数。
receiver通过isa指针找到当前对象的class,并在class中寻找op,如果找到,调用op,如果没找到,到super_class中继续寻找,如此循环直到NSObject(引自引文)。 如果NSObject中仍然没找到,程序并不会立即crash,而是按照优先级执行下列三个方法(下列方法优先级依次递减,高优先级方法消息转发成功不会再执行低优先级方法):

  • 1 + resolveInstanceMethod:(SEL)sel // 对应实例方法
    • resolveClassMethod:(SEL)sel // 对应类方法
  • 2 - (id)forwardingTargetForSelector:(SEL)aSelector
  • 3 - (void)forwardInvocation:(NSInvocation *)anInvocation
// 注意sayHello方法并没有实现哦,如果直接调用的话是会崩溃的
- (IBAction)testResolveInstanceMethod:(id)sender {
    [self sayHello:@"runtime"];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(sayHello:)) {
        class_addMethod([self class], sel, (IMP)say, "v@:@");
    }
    return [super resolveInstanceMethod:sel];
}

void say(id self, SEL _cmd, NSString *name) {
    NSLog(@"Hello %@", name);
}

12. 消息转发(forwardingTargetForSelector:)

- (IBAction)testForwardingTargetForSelector:(id)sender {
    [self sayHello1:@"runtime"];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(sayHello1:)) {
        return [Person new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

12. 消息转发(forwardInvocation:)

- (IBAction)testForwardInvacation:(id)sender {
    [self sayHello1:@"runtime"];

}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];
    if (!methodSignature) {
        methodSignature = [Person instanceMethodSignatureForSelector:aSelector];
    }
    return methodSignature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    Person *person = [Person new];
    if ([person respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:person];
    }
}

13 热修复

JSPatch是个优秀开源项目,只需要在项目里引入极小的引擎文件就可以使用JavaScript调用任何OC的原生接口,替换任意的OC原生方法。目前主要用于下发JS脚本替换原生OC代码,实时修复线上bug,更多详情可以阅读JSPatch技术文档。JSPatch能做到通过JS调用和改写OC方法最根本的原因是OC是动态语言,OC上所有方法的调用和类的生成都通过OC Runtime在运行时进行,我们可以通过类名/方法名反射得到相应的类和方法。理论上你可以在运行时通过类名/方法名调用任何OC方法,替换任何类的实现以及新增任意类,所以JSPatch的基本原理是:JS传递字符串给OC,OC通过Runtime接口调用和替换OC方法。这是最基础的原理。

在JSPatch实现方法替换上,通过Selector调用方法时,会从methodList链表里找到对应的Method进行调用,这个methodList上的元素是可以动态替换的,可以把某个Selector对应的函数指针IMP替换成新的,也可以拿到已有的某个Selector对应的函数指针IMP,让另一个Selector跟它对应,Runtime提供了相应的方法实现这些。

参考文章

Runtime常用的几个应用场景
iOS开发-- Runtime的几个小例子
iOS开发之Runtime常用示例总结

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,636评论 0 9
  • 一些公用类: @interface CustomClass : NSObject - (void) fun1; @...
    xh_0129阅读 530评论 0 0
  • 一、前言 OC是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。即说明OC需要一个编译器和...
    雨润听潮阅读 658评论 1 0
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 694评论 0 2
  • 回顾一个人的一生, 如果非得让我挑出一个对我的一生来说至关重要的要素,那大概是一种力争上游的激情吧。 沃尔玛的故事...
    Yling525阅读 282评论 0 0