开篇

学习一门编程语言。不仅仅是用它来做项目,要懂得它的原理,这样做心里踏实。想要更加深入的掌握OC或者做好iOS开发,runtime无疑是打开这个门的钥匙。

OC语言中的runtime机制

  • OC语言是一门动态语言,他将很多其他的静态语言在编译和连接时期做的事放在了运行时来处理。所以说OC不仅需要编译器,他还需要一个运行时系统来处理编译的代码,这个运行时系统就像一个操作系统一样,让所有的工作可以正常顺利的进行。
  • 运行时系统就是我们所说的运行时机制(Objc Runtime),它是一套由C语言和汇编语言开发的开源库,由很多数据结构和函数组成,通过runtime我们可以动态的修改类,对象中的属性或者方法,无论公有私有。

类和对象的基础数据结构

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;  // 指针,实例的isa指向类的对象,类对象的isa指向元类。

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

} OBJC2_UNAVAILABLE;

在上述的runtime封装的类的数据结构中通常我们会对一下几个变量感兴趣:
* isa : 学习过C语言的同学知道指针的作用,指针其实就是我们将数据存放到内存中的地址,同理isa就是我们创建的类或者对象存放在内存中的地址(在这里我们需要注意,在OC中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类)),当我们向一个OC对象发送消息时,运行时库会根据实例对象的isa指针找到这个实例对象所属的类,Runtime库会在类的方法列表及父类的方法列表中寻找与消息对应的selector指向的方法.找到后即运行这个方法.
* cache : 用于缓存我们最近调用的方法,当我们调用一个对象的方法时,会根据isa指针找到这个对象,然后遍历方法链表,但是,一个对象中会存在很多的方法,很多都是很少用的,如果每调用一次对象的方法都去遍历一次方法链表,势必会降低性能,所以这是,我们把常用的方法缓存到cache中,这样每次调用方法时,runtime会优先到cache中查找,如果没有找到再去遍历方法链表。

获取属性,变量,方法,协议链表

  • 这里先创建了一个Person类,下面分别为Person.h,Person.m文件
#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *sex;
@property (nonatomic, copy) NSString *age;

- (void)sayHi;
@end
#import "Person.h"
#import <objc/runtime.h>

@interface Person ()
@property (nonatomic, copy) NSString *privacy;
@end

@implementation Person

- (void)sayHi {
    NSLog(@"my name is coco");
}

 
}

@end
  • 接下来我们在来获取Person中链表
// 获取属性
- (void)runtimeTestForProperName {
    unsigned int count;
    objc_property_t *properList = class_copyPropertyList([Person class], &count);
    for (unsigned i = 0; i < count; i++) {
        const char *properName = property_getName(properList[i]);
        NSLog(@"属性名称 %@", [NSString stringWithUTF8String:properName]);
    }
}
// 获取变量
- (void)runtimeTestForIvar {
    unsigned int count;
    Ivar *ivarList = class_copyIvarList([Person class], &count);
    for (unsigned int i = 0; i < count; i++) {
        const char *ivarName = ivar_getName(ivarList[i]);
        NSLog(@"成员变量 %@", [NSString stringWithUTF8String:ivarName]);
    }
}
// 获取方法
- (void)runtimeForMethod {
    unsigned int count;
    Method *methodList = class_copyMethodList([Person class], &count);
    for (unsigned int i = 0; i < count; i++) {
        Method method = methodList[i];
        SEL methodName = method_getName(method);
        NSLog(@"方法名称 %@", NSStringFromSelector(methodName));
    }
}
// 获取协议
- (void)runtimeForProtocol {
    unsigned int count;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList([Person class], &count);
    for (unsigned int i; i<count; i++) {
        Protocol *myProtocal = protocolList[i];
        const char *protocolName = protocol_getName(myProtocal);
        NSLog(@"协议方法 %@", [NSString stringWithUTF8String:protocolName]);
    }
}

动态添加属性

  • 向person对象中添加school属性
- (void)setupProperty {
 
    /** objc_setAssociatedObject: 方法的参数说明
     object: 添加的对象
     key: 添加的属性名称(key值,获取时会用到)
     value: 添加对应的属性值 (value值)
     policy: 添加的策略(属性对应的修饰)
     **/
    Person *person = [[Person alloc] init];
    objc_setAssociatedObject(person, @"school", @"吉林师范", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    NSString *associatedValue = objc_getAssociatedObject(person, @"school");
    NSLog(@"设置的属性 :%@", associatedValue);
    
}

其中 我们可以点进policy这个参数中,我们可以清除的看到这是关于我们定义属性的时候用到的几种修饰。

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,      
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  
    OBJC_ASSOCIATION_RETAIN = 01401,  
    OBJC_ASSOCIATION_COPY = 01403
};
  • 上述是向现有类中添加属性,如果你查看runtime的API你就会发现,还有一个 class_addIvar:方法可以添加变量,但是这个方法不支持现有类的添加,官方是这样说的
// 此方法仅可在objc_allocateclasspair方法和objc_registerclasspair之间使用
* This function may only be called after objc_allocateClassPair and before objc_registerClassPair. 
// 不支持在现有类中添加一个实例变量
 * Adding an instance variable to an existing class is not supported.

接下来我们通过动态创建一个类,并向其中添加变量。看代码

- (void)createMyClass
{

    /** objc_allocateClassPair: 参数说明
     superclass: 父类
     name: 类名
     extraButes: 额外分配的字节(写0就可以)
     **/

    /** class_addIvar: 参数说明
     cls: 类
     name: 变量名
     size: 变量的所占内存的字节
     alignment: 对齐方式(写0就可以)
     types: 变量类型
     **/

    // rumtime 动态创建一个类
    Class MyClass = objc_allocateClassPair([NSObject class], "myclass", 0);
    / /  添加一个NSString的变量
    if (class_addIvar(MyClass, "motto", sizeof(NSString *), 0, "@")) {
        NSLog(@"add ivar success");
    }
    //  注册这个类到runtime系统中才可以使用它
    objc_registerClassPair(MyClass);

    //  生成了一个实例化对象
    id myobj = [[MyClass alloc] init];
    NSString *string = @"it's my live";
    //   赋值
    [myobj setValue: string forKey:@"motto"];
    Ivar ivarOFset = class_getInstanceVariable([myobj class], "motto");
    id value = object_getIvar(myobj, ivarOFset);
    NSLog(@"添加的变量的值  = %@", value);
}

动态添加方法

- (void)setupMethod {

    /** class_addMethod: 参数说明
     cls: 类
     name: 方法名
     imp: 方法的地址
     types: 方法类型
     **/    

   // 获取 song 方法的类型
    Method method = class_getInstanceMethod([self class], @selector(song));
    const char *methodType = method_getTypeEncoding(method);
    NSLog(@"参数类型 %@", [NSString stringWithUTF8String:methodType]);

    // 添加 song 方法
    class_addMethod([Person class], @selector(song), class_getMethodImplementation([self class], @selector(song)), methodType);
    // 创建实例对象
    Person *person = [[Person alloc] init];
    // 调用添加的方法
    [person performSelector:@selector(song) withObject:nil];

}

- (void)song {
    NSLog(@"大家好,为大家来一首 - 《那就这样吧》");  
}

通过ruantime交换,替换方法

  • 交换两个方法(方法A,方法B),当两个方法交换成功后,调用方法A后就会执行方法B中的代码。
  • 替换方法,很好理解,就是将方法A替换成方法B。

* 无论是交换还是替换方法,实质rumtime就是将方法的指针做了交换或者替换 *

- (void)exchangeMethod {

    // 获取两个方法的Method
    Method funcA = class_getInstanceMethod([self class], @selector(functionA));
    Method funcB = class_getInstanceMethod([self class], @selector(functionB));
    // 交换两个方法
    method_exchangeImplementations(funcA, funcB);
    [self functionA];
    [self functionB];

    /** 官方文档是这样介绍 method_exchangeImplementations 方法实现的:
    * @note This is an atomic version of the following:
    *  \code 
    *  IMP imp1 = method_getImplementation(m1);
    *  IMP imp2 = method_getImplementation(m2);
    *  method_setImplementation(m1, imp2);
    *  method_setImplementation(m2, imp1);
    *  \endcode
     **/

}

- (void)replaceMethod {

    Method funcB = class_getInstanceMethod([self class], @selector(functionB));
   // 将方法A替换成方法B
    class_replaceMethod([self class], @selector(functionA), method_getImplementation(funcB), method_getTypeEncoding(funcB));
    [self functionA];
    [self functionB];

}

- (void)functionA {
    NSLog(@"我是方法A");
}

- (void)functionB {
    NSLog(@"我是方法B");
}

总结

通过上述的介绍我想大家对runtime有了一个初步的了解,同时对于OC也会又一个更深的认识。上面代码的运行结果我就不给大家一一截图了,我都是验证后才写在博客上的。runtime中还有很多方法,但是都是大同小异,用法都是可以举一反三的。希望这篇博客会对大家有所启发,同时欢迎大家提出建议和不同的看法,见解,分享可以让我们共同进步。