OC中的runtime是什么 ?runtime可以干什么?

什么是runtime?

  • 从字面上理解为 运行时,简单来说 runtime是一个实现OC语言的C库。
  • 我们编写的OC代码会在程序运行过程中会转换成runtime的c语言代码,当然我们也可以直接运用runtime提供的APi进行魔法操作。
  • OC是运行时动态语言在运行时将对象类型确定、方法调用、代码和资源的装载等。

Runtime 概念 及术语

1. Object(objc_object) 实例
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
  • 这里我们看到 objc_object 结构体 里面只包含一个 class类型 的isa 指针。
    这里也就说明 一个Object (实例)唯一保存的就是他所属Class(类)的地址,当我们对一个 实例进行方法调用时候。
    例如 [object message] ,会通过objc_object结构体的 isa指针 去找到到对应的 objec_class 结构体。
2. Class(objc_class) 类
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa;                                          // objc_class 结构体的实例指针

#if !__OBJC2__
    Class _Nullable super_class;                                 // 指向父类的指针
    const char * _Nonnull name;                                  // 类的名字
    long version;                                                // 类的版本信息,默认为 0
    long info;                                                   // 类的信息,供运行期使用的一些位标识
    long instance_size;                                          // 该类的实例变量大小;
    struct objc_ivar_list * _Nullable ivars;                     // 该类的实例变量列表
    struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定义的列表
    struct objc_cache * _Nonnull cache;                          // 方法缓存
    struct objc_protocol_list * _Nullable protocols;             // 遵守的协议列表
#endif

  • 这里我们看到 objc_object 结构体里面定义了很多变量 通过命名不难发现 结构体里面保存了 指向父类的 指针、类的名字、版本、实例大小、实例变量 list 方法 list 缓存 协议列表 等。一个类包含的信息不就正式这些吗?
    objc_class结构体 的第一个成员变量是 isa指针,isa指针 保存的是所属类的结构体的实例指针也就是对象。
    所以Class(类)的本质就是一个对象, 称之为 类对象 。

  • 类对象就是一个结构体 struct objc_class ,这个结构体存放的数据 称之为 元数据 (metadata)

3. Meta Class(元类)

  • 从上面可以看出,对象(objc_object)结构体的 isa指针 指向的是对应 类对象(objc_class)结构体 ,那么类对象 (objc_class)的isa指向什么?答案指向 元类

  • 元类 是类对象(objc_class) 的类 听起来绕口 下面看它的作用就会豁然开朗

  • 在OC中,每当我们创建一个类。在编译时就会创建一个元类,而这个元类的对象 就是我们创建的这个类。(我们创建的类本质也是一个对象objc_class)

  • 那么为什么要有元类?我们看类对象( objc_class结构体) ivars 用来存放属性变量,objc_method_list 用来存放 实例方法 (-方法),那一些静态变量 和 类(+)方法 哪里去了? 没错,他们就是存放在 元类的methodLists和ivars里面。

4. Method(objc_method)

/// An opaque type that represents a method in a class definition.
/// 代表类定义中一个方法的不透明类型
typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name;                    /// 方法名 
    char * _Nullable method_types;               /// 方法类型
    IMP _Nonnull method_imp;                     ///方法实现
};
  • SEL _method_name (方法名)释义
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

SEL: 是一个指向 objc_selecter 结构体的指针,但是在runtime相关头文件中 并没有找到明确的定义,经过测试打印 得出 猜测 结论 SEL 只是一个保存方法名的字符串。

  • char *_Nullable method_types(方法类型)释义

方法类型 method_types 是个字符串,用来存储方法的参数类型和返回值类型。

  • IMP method_imp(方法实现)释义
/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

IMP 的实质是一个函数指针,所指向的就是方法的实现。IMP用来找到函数地址,然后执行函数。

  • objc_class (类对象)结构体中的 methodLists(方法列表)中存放的元素就是Method(方法)

  • 由此看出Method将 SEL(方法名)和IMP(函数指针)相关联。当对一个对象 发送消息的时候 会通过SEL(方法名)去找到IMP(函数指针)进行执行。

消息机制的基本原理

根据上面分析,大家脑海中应该会有一个方法调用,也就是消息发送的全过程。下面总结一下

///实例方法调用
Person * p = [[Person alloc]init];
[p eat];

///类方法调用
 [Person sleep];

#import "Person.h"

@implementation Person
-(void)eat;
{
    NSLog(@"吃饭");
}

+(void)sleep;
{
    NSLog(@"睡觉");
}
@end

对象方法调用过程
  1. 通过 实例(p) 的isa指针 找到 实例( p)的 Class(objc_class类对象);

  2. 在Class(objc_class类对象)的结构体中找到 cache(方法缓存)散列表 中根据method_name 看里面有没有对应的IMP(方法实现);

  3. 如果没有找到 就继续在 Class(objc_class类对象)的methodLists(方法列表中)寻找对应的selector ,如果找到,填充到cache(方法缓存)中,并返回 selector;

  4. 如果Class(类)中没有找到这个selector ,就继续在他的superClass中寻找;

  5. 一旦找到对应的selector,就直接执行对应selector方法实现的IMP(方法实现);

  6. 若找不到对应的selector,即将进入 runtime的 消息转发机制。消息转发不做处理 程序发生崩溃。

类方法的调用
  1. 上面我们有说过 我们创建的类 在编译时会创建一个元类,而这个元类的对象 就是我们创建的这个类

  2. 通过Class(objc_class类对象) 的isa指针找到所属元类;

  3. 在进行上述后续操作。

runtime可以干什么?

  • 获取类中所有的实例变量
class_copyIvarList()返回一个指向类的成员变量和属性数组的指针
class_copyPropertyList()返回一个指向类的属性数组的指针
///我是.m 
#import "Person.h"

@interface Person ()

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

@implementation Person
{
    NSNumber *  phone;
}

@end
#import 
#import "Person.h"
#import // 包含对类、成员变量、属性、方法的操作
#import // 包含消息机制
int main(int argc, const char * argv[]) {
   @autoreleasepool {
       // insert code here...
       NSLog(@"Hello, World!");
       
       //    返回所有的属性和实例变量 class_copyIvarList
       unsigned int methodCount = 0;
       Ivar * ivars = class_copyIvarList([Person class], &methodCount);
       
       for (unsigned int i = 0; i< methodCount; i++) {
           Ivar ivar = ivars[i];
           const char * name = ivar_getName(ivar);
           const char * type = ivar_getTypeEncoding(ivar);
           NSLog(@" Persons成员变量属性为%s==类型%s",name,type);
       }
       free(ivars);
       
       //        只返回 属性方法 class_copyPropertyList
       unsigned int method_Count = 0;
       objc_property_t * properties = class_copyPropertyList([Person class], &method_Count);
       
       for (unsigned int i = 0; i< method_Count; i++) {
           objc_property_t property = properties[i];
           const char * properName = property_getName(property);
           NSLog(@" Person 属性为%s",properName);
        }
       free(properties);

       
   }
   return 0;
}
2019-10-29 14:18:17.595647+0800 runtime[7816:359804] Hello, World!
2019-10-29 14:18:17.596917+0800 runtime[7816:359804]  Persons成员变量属性为phone==类型@"NSNumber"
2019-10-29 14:18:17.597031+0800 runtime[7816:359804]  Persons成员变量属性为_name==类型@"NSString"
2019-10-29 14:18:17.597093+0800 runtime[7816:359804]  Persons成员变量属性为_age==类型@"NSString"
2019-10-29 14:18:17.597145+0800 runtime[7816:359804]  Persons成员变量属性为_sex==类型@"NSString"
2019-10-29 14:18:17.597213+0800 runtime[7816:359804]  Person 属性为name
2019-10-29 14:18:17.597608+0800 runtime[7816:359804]  Person 属性为age
2019-10-29 14:18:17.597708+0800 runtime[7816:359804]  Person 属性为sex
Program ended with exit code: 0
  • 使用runtime动态添加一个类
 objc_allocateClassPair:注册一个新类或者元类  如想让这个类成为基类那么参数superclass指针定为nil.参数extraByte是分配给类和元类对象尾部的索引ivars的字节数,通常指为0
  objc_registerClassPair:当创建完新类后,需要调用这个方法注册这个类 之后这个类才可以在程序中使用
  objc_disposeClassPair:用于销毁一个类及其元类,需要注意的是,如果程序运行中还存在类或者其子类的实例,那么就不能调用此方法
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        
        //创建一个新类
             Class myClass = objc_allocateClassPair([NSObject class], "myClass", 0);
             //添加ivar
             //@encode(aType):返回该类型的字符串
             class_addIvar(myClass, "_address", sizeof(NSString*), log2(sizeof(NSString*)),@encode(NSString*));
             class_addIvar(myClass, "_age", sizeof(NSUInteger), log2(sizeof(NSUInteger)), @encode(NSUInteger));
             //注册类
             objc_registerClassPair(myClass);
             //创建实例
             id object = [[myClass alloc] init];
             //为ivar赋值
             [object setValue:@"china" forKey:@"address"];
             [object setValue:@20 forKey:@"age"];
             NSLog(@"address=%@,age=%@",[object valueForKey:@"address"],[object valueForKey:@"age"]);
             //当类或者它的子类的实例还存在 则不能调用  objc_disposeClassPair
             object = nil;
             //销毁
             objc_disposeClassPair(myClass);
   
        
    }
    return 0;
    
}
2019-10-29 15:06:02.931562+0800 runtime[8254:384872] Hello, World!
2019-10-29 15:06:02.936912+0800 runtime[8254:384872] address=china,age=20
Program ended with exit code: 0
  • 在category中增加属性(众所周知正常来说不可以添加但是用runtime完全可以实现)
    给Person类添加个Category
#import 
#import"Person.h"
 
@interface Person (Category)
//不会生成添加属性的getter和setter方法,必须我们手动生成
@property (nonatomic, copy) NSString *phone;
@end
#import "Person+Category.h"
#import 
#import 

@implementation Person (Category)
// 定义关联的key
static const char *key = "phone";

/**
phone的getter方法
*/
-(NSString *)phone
{
      // 根据关联的key,获取关联的值。
   return objc_getAssociatedObject(self, key);
}
/**
 phone的setter方法
 */

-(void)setPhone:(NSString *)phone
 {
    // 第一个参数:给哪个对象添加关联
    // 第二个参数:关联的key,通过这个key获取
    // 第三个参数:关联的value
    // 第四个参数:关联的策略
    objc_setAssociatedObject(self, key, phone, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

@end
#import 
#import "Person.h"
#import "Person+Category.h"
#import // 包含对类、成员变量、属性、方法的操作
#import // 包含消息机制
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        
        Person * per = [[Person alloc]init];
        per.phone = @"111000001011";
        NSLog(@"Category不能添加属性?可以的我用Category为Person添加了一个phone属性,phone=%@",per.phone);

        
    }
    return 0;
    
}
2019-10-29 15:26:13.400545+0800 runtime[8485:392320] Category不能添加属性?可以的我用Category为Person添加了一个phone属性,phone=111000001011
Program ended with exit code: 0
  • runtime进行消息传递
#import "Person.h"

@interface Person ()

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

@implementation Person

{
    NSNumber *  phone;
}

-(void)callPhone;
{
    NSLog(@"电话正在拨打中。。。");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        Person * person = [[Person alloc]init];
//      OC中,当调用某个对象的方法时,其实质上就是向该对象发送了一条消息,比如:
//
        //  [person callPhone]; 本质为以下代码
        objc_msgSend(person, @selector(callPhone));

        
    }
    return 0;
2019-10-29 15:53:47.954497+0800 runtime[8667:403765] Hello, World!
2019-10-29 15:53:47.955521+0800 runtime[8667:403765] 电话正在拨打中。。。
Program ended with exit code: 0
  • 使用runtime动态添加方法[实现消息转发] (https://www.jianshu.com/p/7f868b0e2575)
 
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
      
      //Person类中并不存在 sendMassage方法
       Person * person = [[Person alloc]init];
        SEL aSel = NSSelectorFromString(@"sendMassage");
        [person performSelector:aSel];
 
    }
    return 0;
    
}

运行

-[Person sendMassage]: unrecognized selector sent to instance 0x100626da0

运行崩溃此异常为没有找到sendMassage ,消息转发过程以程序崩溃结束 但是可以利用runtime补救 程序消息转发详解请看这里[实现消息转发] (https://www.jianshu.com/p/7f868b0e2575)


//person.m 没有找到sendMassage方法调用callPhone方法吧

-(void)callPhone;
{
    NSLog(@"电话正在拨打中。。。");
}

//动态补加方法
+(BOOL)resolveInstanceMethod:(SEL)sel
{
    NSString *  str = NSStringFromSelector(sel);
    if ([str isEqualToString:@"sendMassage"]) {
     // 得到实例方法
         Method  _Nonnull m =  class_getInstanceMethod(self, @selector(callPhone));
     //  返回方法的调用地址
         IMP imp = method_getImplementation(m);
     //   给类添加一个新的方法和该方法的具体实现
        class_addMethod(self, @selector(sendMassage), imp, "");
    }
    return [super resolveInstanceMethod:sel];
}
  • 利用kvc和runtime暴力访问私有属性和变量
 Person * person = [[Person alloc]init];
        
        //kvo 暴力访问 私有属性和私有变量
        
        [person setValue:@"小明" forKey:@"name"];
        NSString * pname = [person valueForKey:@"name"];
        [person setValue:@"19" forKey:@"age"];
        NSString * page = [person valueForKey:@"age"];
        [person setValue:@"男" forKey:@"sex"];
        NSString * psex = [person valueForKey:@"sex"];
        
        [person setValue:@1111111111 forKey:@"phone"];
        NSString * pphone = [person valueForKey:@"phone"];
        
        NSLog(@"姓名%@,年龄%@,性别%@,电话%@",pname,page,psex,pphone);
        
 //      runtime如何来做?
        unsigned int count = 0;
        
        //获取所有属性变量名字
        Ivar * members= class_copyIvarList([Person class], &count);
        for (unsigned int i = 0; i
  • 使用runtime进行方法交换
    为什么在load里面交换请看 [load方法和initialize方法异同] (https://www.jianshu.com/p/d4ce4530246e)
///我是Person.m
+ (void)load
{
    //获取实例的方法
    Method M1 =  class_getInstanceMethod(self, @selector(callBB机));
    Method M2 = class_getInstanceMethod(self, @selector(callPhone));
    //交换方法
    method_exchangeImplementations(M1, M2);
    
    //获取类方法
    Method cl1 =  class_getClassMethod(self, @selector(callQQ));
    Method cl2 = class_getClassMethod(self, @selector(callWX));
    
    //交换方法
    method_exchangeImplementations(cl1, cl2);
}

-(void)callBB机;
{
    NSLog(@"打bb机中..");
}


-(void)callPhone;
{
    NSLog(@"电话正在拨打中。。。");
}


+(void)callQQ
{
     NSLog(@"打QQ中..");
}


+(void)callWX
{
     NSLog(@"打微信中..");
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
     
        Person * person = [[Person alloc]init];
        NSLog(@"打电话------------");
        [person callPhone];
        NSLog(@"打BB机------------");
        [person callBB机];
        NSLog(@"打QQ------------");
        [Person callQQ];
        NSLog(@"打WX------------");
        [Person callWX];
        
        
        
        
    }
    return 0;

运行

2019-10-29 17:56:41.131014+0800 runtime[9536:457820] 打电话------------
2019-10-29 17:56:41.131757+0800 runtime[9536:457820] 打bb机中..
2019-10-29 17:56:41.131928+0800 runtime[9536:457820] 打BB机------------
2019-10-29 17:56:41.131989+0800 runtime[9536:457820] 电话正在拨打中。。。
2019-10-29 17:56:41.132038+0800 runtime[9536:457820] 打QQ------------
2019-10-29 17:56:41.132083+0800 runtime[9536:457820] 打微信中..
2019-10-29 17:56:41.132127+0800 runtime[9536:457820] 打WX------------
2019-10-29 17:56:41.132173+0800 runtime[9536:457820] 打QQ中..

  • 使用runtime为model赋值

建PersonModel类

///我是.h
#import 

@interface PersonModel : NSObject
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *age;
@property (nonatomic,copy) NSString *sex;
@property (nonatomic,copy) NSString *phone;
 
+(instancetype)modelWithDict:(NSDictionary*)dict;

@end

///我是.m
#import "PersonModel.h"

#import 
@implementation PersonModel

+(instancetype)modelWithDict:(NSDictionary*)dict;
{
    id object = [[self alloc]init];
    unsigned int count = 0;
    Ivar * ivarList = class_copyIvarList(self, &count);
    for (int i = 0; i

main函数赋值

int main(int argc, const char * argv[]) {
    @autoreleasepool {
     
    NSDictionary * data  = @{@"name":@"小花花",
                                 @"age":@"10",
                                 @"sex":@"女",
                                 @"phone":@"1111111111111"};
                                 
    
        PersonModel * model =  [PersonModel modelWithDict:data];
        NSLog(@"name:%@.age:%@.sex:%@.phone:%@",model.name,model.age,model.sex,model.phone);
 
        
    }
    return 0;
    
}

运行 模型赋值成功

2019-10-30 11:21:59.319949+0800 runtime[18571:872011] name:小花.age:10.sex:女.phone:1111111111111
Program ended with exit code: 0
  • 自动归档解档

当我们需要将一个对象进行归档时,都要让该对象的类遵守NSCoding协议,再实现归档和接档方法。以上面的PersonModel为例

/**
*  将对象写入某个文件时需要调用,在该方法中说明哪些属性需要存储
*/
- (void)encodeWithCoder:(NSCoder *)coder;
{
   [coder encodeObject:self.name forKey:@"name"];
   [coder encodeObject:self.age forKey:@"age"];
   [coder encodeObject:self.sex forKey:@"sex"];
   [coder encodeObject:self.phone forKey:@"phone"];
}

 
/**
 *  从文件中解析对象时会调用,在该方法中解析对象的属性
 */
- (nullable instancetype)initWithCoder:(NSCoder *)coder;
{
    if (self = [super init]) {
        // 解析之后要赋值给属性
        _name  = [coder decodeObjectForKey:@"name"];
        _age   = [coder decodeObjectForKey:@"age"];
        _sex   = [coder decodeObjectForKey:@"sex"];
        _phone = [coder decodeObjectForKey:@"phone"];
    }
    return self;
}//

如果这个类属性上百个那一个一个的写会累死 而且还很low
应该使用runtime来进行

- (nullable instancetype)initWithCoder:(NSCoder *)coder
{
   
    if (self = [super init]) {
        Class c = self.class;
        // 截取类和父类的成员变量
        while (c && c != [NSObject class]) {
            unsigned int count = 0;
            Ivar *ivars = class_copyIvarList(c, &count);
            for (int i = 0; i < count; i++) {
               
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivars[i])];
               
                id value = [coder decodeObjectForKey:key];
               
                [self setValue:value forKey:key];

            }
            // 获得c的父类
            c = [c superclass];
            free(ivar);
        }
       
       
    }
    return self;
}

/**
 *  从文件中解析对象时会调用,在该方法中解析对象的属性
 */
- (void)encodeWithCoder:(NSCoder *)coder;
{
   
     Class c = self.class;
    // 截取类和父类的成员变量
    while (c && c != [NSObject class]) {
        unsigned int count = 0;
       
        Ivar *ivars = class_copyIvarList(c, &count);
       
        for (int i = 0; i < count; i++) {
            Ivar ivar = ivars[i];
            NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];

            id value = [self valueForKey:key];
           
            [coder encodeObject:value forKey:key];
        }
        c = [c superclass];
        // 释放内存
        free(ivar);
    }
   
}

参考资料

博文:『Runtime』详解(一)基础知识
博文: iOS Runtime详解

你可能感兴趣的:(OC中的runtime是什么 ?runtime可以干什么?)