您的当前位置:首页正文

Runtime常用总结

来源:华拓网

什么是Runtime

因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时。也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的代码。这就是 Objective-C Runtime 系统存在的意义,它是整个Objc运行框架的一块基石。
Runtime基本是用C和汇编写的API函数。Runtime 系统是一个由一系列函数和数据结构组成,具有公共接口的动态共享库。头文件存放于/usr/include/objc目录下。


Runtime常见作用

1. 获取类名、类成员变量、类的属性列表,(包括私有和公有属性,即定义在延展中的属性)
2. 获取对象方法列表:getter, setter, 对象方法等。但不能获取类方法
3. 获取协议列表
4. 交换类、实例方法
5. 动态添加方法

Runtime术语

下面将会逐渐展开介绍一些术语,其实它们都对应着数据结构。

SEL

id objc_msgSend ( id self, SEL op, ... );

objc_msgSend函数第二个参数类型为SEL,它是selector在Objc中的表示类(Swift中是Selector类)。selector是方法选择器,可以理解为区分方法的 ID,而这个 ID 的数据结构是SEL:

typedef struct objc_selector *SEL;

其实它就是个映射到方法的C字符串,你可以用 Objc 编译器命令@selector()或者 Runtime 系统的sel_registerName函数来获得一个SEL类型的方法选择器。不同类中相同名字的方法所对应的方法选择器是相同的,即使方法名字相同而变量类型不同也会导致它们具有相同的方法选择器,于是 Objc 中方法命名有时会带上参数类型(NSNumber一堆抽象工厂方法拿走不谢),Cocoa 中有好多长长的方法哦。

id

objc_msgSend第一个参数类型为id,大家对它都不陌生,它是一个指向类实例的指针:

typedef struct objc_object *id;

那objc_object又是啥呢:

struct objc_object { Class isa; };

Class

之所以说isa是指针是因为Class其实是一个指向objc_class结构体的指针:

typedef struct objc_class *Class;

而objc_class就是我们摸到的那个瓜,里面的东西多着呢:

struct objc_class { 
   Class isa OBJC_ISA_AVAILABILITY; // //也有一个 isa 指针,指向其所属的元类(meta)
   #if !__OBJC2__ 
       Class super_class                      OBJC2_UNAVAILABLE; //指向其超类
       const char *name                       OBJC2_UNAVAILABLE; //类名
       long version                           OBJC2_UNAVAILABLE; //类的版本信息
       long info                              OBJC2_UNAVAILABLE; //类的详情
       long instance_size                     OBJC2_UNAVAILABLE; //该类的实例对象的大小
       struct objc_ivar_list *ivars           OBJC2_UNAVAILABLE; //指向该类的成员变量列表,它将方法选择器和方法实现地址联系起来。methodLists 是指向 ·objc_method_list 指针的指针,也就是说可以动态修改 *methodLists 的值来添加成员方法,这也是 Category 实现的原理,同样解释了 Category 不能添加属性的原因
       struct objc_method_list **methodLists         OBJC2_UNAVAILABLE; //指向该类的实例方法列表
       struct objc_cache *cache               OBJC2_UNAVAILABLE; //Runtime 系统会把被调用的方法存到 cache 中(理论上讲一个方法如果被调用,那么它有可能今后还会被调用),下次查找的时候效率更高
       struct objc_protocol_list *protocols   OBJC2_UNAVAILABLE; //指向该类的协议列表
#endif
} OBJC2_ UNAVAILABLE;

可以看到运行时一个类还关联了它的超类指针,类名,成员变量,方法,缓存,还有附属的协议。
PS:OBJC2_UNAVAILABLE
之类的宏定义是苹果在 Objc 中对系统运行版本进行约束的黑魔法,为的是兼容非Objective-C 2.0的遗留逻辑,但我们仍能从中获得一些有价值的信息,有兴趣的可以查看源代码。

struct objc_ivar_list { 
    int ivar_count OBJC2_ UNAVAILABLE;
#ifdef __LP64__ 
    int space OBJC2_ UNAVAILABLE;
#endif 
/* variable length structure */ 
    struct objc_ivar ivar_list[1]  OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
struct objc_method_list { 
    struct objc_method_list *obsolete OBJC2_UNAVAILABLE; 
    int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__ 
    int space OBJC2_UNAVAILABLE;
#endif 
/* variable length structure */
    struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}

如果你C语言不是特别好,可以直接理解为objc_ivar_list结构存储着objc_ivar数组列表,而objc_ivar结构体存储了类的单个成员变量的信息;同理objc_method_list结构体存储着objc_method
数组列表,而objc_method结构体存储了类的某个方法的信息。
最后要提到的还有一个objc_cache,顾名思义它是缓存,它在objc_class的作用很重要,在后面会讲到。


上图实线是super_class指针,虚线是isa指针。 有趣的是根元类的超类是NSObject,而isa指向了自己,而NSObject的超类为nil,也就是它没有超类。

Method

Method是一种代表类中的某个方法的类型。

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;} OBJC2_UNAVAILABLE;

Ivar

Ivar是一种代表类中实例变量的类型。

typedef struct objc_ivar *Ivar;

而objc_ivar在上面的成员变量列表中也提到过:

struct objc_ivar { 
    char *ivar_name     OBJC2_UNAVAILABLE; 
    char *ivar_type     OBJC2_UNAVAILABLE; 
    int ivar_offset     OBJC2_UNAVAILABLE;
    #ifdef __LP64__ 
        int space OBJC2_UNAVAILABLE;
    #endif
} OBJC2_UNAVAILABLE;

可以根据实例查找其在类中的名字,也就是“反射”:

-(NSString *)nameWithInstance:(id)instance 
{ 
    unsigned int numIvars = 0; 
    NSString *key=nil; 
    Ivar * ivars = class_copyIvarList([self class], &numIvars); 
    for(int i = 0; i < numIvars; i++) { 
        Ivar thisIvar = ivars[i]; 
        const char *type = ivar_getTypeEncoding(thisIvar); 
        NSString *stringType = [NSString stringWithCString:type encoding:NSUTF8StringEncoding]; 
    if (![stringType hasPrefix:@"@"]) { continue; } 
        if ((object_getIvar(self, thisIvar) == instance)) {//此处若 crash 不要慌! 
            key = [NSString strin gWithUTF8String:ivar_getName(thisIvar)]; 
            break; 
        } 
    } 
    free(ivars); 
    return key;
}

IMP

IMP在objc.h中的定义是:

typedef id (*IMP)(id, SEL, ...);

Cache

在runtime.h中Cache的定义如下:

typedef struct objc_cache *Cache

还记得之前objc_class结构体中有一个struct objc_cache *cache
吧,它到底是缓存啥的呢,先看看objc_cache的实现:

struct objc_cache { 
    unsigned int mask /* total = mask + 1 */     OBJC2_UNAVAILABLE; 
    unsigned int occupied                        OBJC2_UNAVAILABLE; 
    Method buckets[1]                            OBJC2_UNAVAILABLE;
};

Property

@property标记了类中的属性,这个不必多说大家都很熟悉,它是一个指向objc_property结构体的指针:

typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//这个更常用

可以通过class_copyPropertyList和protocol_copyPropertyList方法来获取类和协议中的属性:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

返回类型为指向指针的指针,哈哈,因为属性列表是个数组,每个元素内容都是一个objc_property_t指针,而这两个函数返回的值是指向这个数组的指针。举个栗子,先声明一个类:

@interface Lender : NSObject { 
    float alone;
}
@property float alone;
@end

你可以用下面的代码获取属性列表:

id LenderClass = objc_getClass("Lender");
unsigned int outCount;objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);

你可以用property_getName函数来查找属性名称:

const char *property_getName(objc_property_t property)

你可以用class_getProperty和protocol_getProperty通过给出的名称来在类和协议中获取属性的引用:

objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)

你可以用property_getAttributes函数来发掘属性的名称和@encode类型字符串:

const char *property_getAttributes(objc_property_t property)

把上面的代码放一起,你就能从一个类中获取它的属性啦:

id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) { 
    objc_property_t property = properties[i]; 
    fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}

方法调用

让我们看一下方法调用在运行时的过程(参照前文类在runtime中的表示)如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象)操作。
如果调用的是类方法,就会到类对象的isa指针指向的对象(也就是元类对象)中操作。

    1.首先,在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行

    2.如果没找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行

    3.如果没找到,去父类指针所指向的对象中执行1,2.

    4.以此类推,如果一直到根类还没找到,转向拦截调用。

    5.如果没有重写拦截调用的方法,程序报错。

拦截调用

在方法调用中说到了,如果没有找到方法就会转向拦截调用。那么什么是拦截调用呢。拦截调用就是,在找不到调用的方法程序崩溃之前,你有机会通过重写NSObject的四个方法来处理。

+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后两个方法需要转发到其他的类处理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;

第一个方法是当你调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,你可以加上自己的处理然后返回YES。
第二个方法和第一个方法相似,只不过处理的是实例方法。
第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要你返回一个有这个方法的target。
第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法。

多说无用,代码为证

为了方便起见,首先我们必须先导入两个头文件:

#import <objc/runtime.h>
#import <objc/message.h>

获取类名

/**
 获取类名
 
 @param class 相应类
 @return NSString:类名
 */
+ (NSString *)fetchClassName:(Class)class
{
    const char *className = class_getName(class);
    return [NSString stringWithUTF8String:className];
}

获取成员变量

/**
 获取成员变量
 
 @param class Class
 @return NSArray
 */
+ (NSArray *)fetchIvarList:(Class)class
{
    unsigned int outCount = 0;
    /**
     * __unsafe_unretained Class cls 哪个类
     * unsigned int *outCount 放一个接收值的地址,用来存放属性的个数
     */
        // Ivar是一种代表类中实例变量的类型
    Ivar *ivars = class_copyIvarList(class, &outCount);
    NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:outCount];
        // 遍历所有成员变量
    for (unsigned int i = 0; i < outCount; i++) {
            // 取出i位置对应的成员变量
        Ivar ivar = ivars[i];
        const char *ivarName = ivar_getName(ivar);
        const char *ivarType = ivar_getTypeEncoding(ivar);
        
        NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:2];
        dic[@"ivarType"] = [NSString stringWithUTF8String: ivarType];
        dic[@"ivarName"] = [NSString stringWithUTF8String: ivarName];
        [mutableArray addObject:dic];
    }
        // 注意释放内存!
    free(ivars);
    return mutableArray;
}

获取类的属性列表

/**
 获取类的属性列表, 包括私有和公有属性,以及定义在延展中的属性
 
 @param class Class
 @return 属性列表数组
 */
+ (NSArray *)fetchPropertyList:(Class)class {
    unsigned int count = 0;
    objc_property_t *propertyList = class_copyPropertyList(class, &count);
    
    NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++ ) {
        const char *propertyName = property_getName(propertyList[i]);
        [mutableArray addObject:[NSString stringWithUTF8String: propertyName]];
    }
    free(propertyList);
    return mutableArray;
}

获取实例方法列表

/**
 获取实例方法列表:getter, setter, 对象方法等。但不能获取类方法
 
 @param class 类名
 @return 类的实例方法列表数组
 */
+ (NSArray *)fetchMethodList:(Class)class {
    unsigned int count = 0;
    Method *methodList = class_copyMethodList(class, &count);
    
    NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++ ) {
        Method method = methodList[i];
        SEL methodName = method_getName(method);
        [mutableArray addObject:NSStringFromSelector(methodName)];
    }
    free(methodList);
    return mutableArray;
}

获取协议列表

/**
 获取协议列表
 
 @param class 类名
 @return 协议列表数组
 */
+ (NSArray *)fetchProtocolList:(Class)class {
    unsigned int count = 0;
    __unsafe_unretained Protocol **protocolList = class_copyProtocolList(class, &count);
    
    NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:count];
    for (unsigned int i = 0; i < count; i++ ) {
        Protocol *protocol = protocolList[i];
        const char *protocolName = protocol_getName(protocol);
        [mutableArray addObject:[NSString stringWithUTF8String: protocolName]];
    }
    
    return mutableArray;
}

交换实例方法

/**
 交换实例方法,可拦截系统方法
 
 @param class 交换方法所在的类
 @param method1 方法1
 @param method2 方法2
 */
+ (void)swapInstanceMethod:(Class)class firstMethod:(SEL)method1 secondMethod:(SEL)method2 {
    Method firstMethod = class_getInstanceMethod(class, method1);
    Method secondMethod = class_getInstanceMethod(class, method2);
    method_exchangeImplementations(firstMethod, secondMethod);
}

交换类方法

/**
 交换类方法,可拦截系统方法
 
 @param class 交换方法所在的类
 @param method1 方法1
 @param method2 方法2
 */
+ (void)swapClassMethod:(Class)class firstMethod:(SEL)method1 secondMethod:(SEL)method2 {
    Method firstMethod = class_getClassMethod(class, method1);
    Method secondMethod = class_getClassMethod(class, method2);
    method_exchangeImplementations(firstMethod, secondMethod);
}

动态添加方法

/**
 往类上添加新的方法与其实现
 
 @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);
}
@end

关联对象

在你的某个类扩展.m文件中加入:

/** 要动态添加的属性 */
@property (nonatomic, copy) NSString *dynamicAddedProperty;

在其.m文件中加入:

#pragma mark - 动态属性关联
char nameKey;
/**
 setter方法
 
 @param dynamicAddedProperty 设置关联属性的值
 */
- (void)setDynamicAddedProperty:(NSString *)dynamicAddedProperty
{
    /** objc_setAssociatedObject
     * 参数 object:给哪个对象设置属性
     * 参数 key:一个属性对应一个Key,将来可以通过key取出这个存储的值,key 可以是任何类型:double、int 等,建议用char 可以节省字节
     * 参数 value:给属性设置的值
     * 参数policy:存储策略 (assign 、copy 、 retain就是strong)
     */
        // 将某个值跟某个对象关联起来,将某个值存储到某个对象中
    objc_setAssociatedObject(self, &nameKey, dynamicAddedProperty, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}


/**
 getter方法
 
 @return 返回关联属性的值
 */
- (NSString *)dynamicAddedProperty {
    return objc_getAssociatedObject(self, &nameKey);
}

假设你要扩展的类名为 TestClass,在你的 Controller 中加入:

TestClass *instance = [TestClass new];
    instance.dynamicAddedProperty = @"我是动态添加的属性";
NSLog(@"动态关联属性内容:%@", instance.dynamicAddedProperty);

敝人文采有限,讲到这里,文章也该结束了,我只是根据自己的理解把下面参考的资料进行了汇总,相信任何一个人的资料并非十全十美,本文章也仅供参考,如果有什么疑问,可及时在下方回复沟通。

我们不单单是代码的搬运工,我们更有我们自己的思想。天道酬勤,代码之路,你我共行。

道虽迩,不行不至;事虽小,不为不成!


参考来源: