通过上一篇的一些简单介绍与阐述,相信大家应该对 Runtime 有一个基本的认识了,于是,这一篇我们就来看看 Runtime 的基本应用。

准备工作

首先作为一个库,当然需要导入其头文件:

#include <objc/runtime.h>

基本应用

获取和修改对象的类

Class object_getClass(id obj)// 获取对象的类
Class object_setClass(id obj, Class cls)// 设置对象的类

来看一个实例:

MXRuntimeDemoClass *obj = [MXRuntimeDemoClass new];

Class aClass = object_getClass(obj);
NSLog(@"aClass:%@", NSStringFromClass(aClass));
NSLog(@"objClass1:%@", NSStringFromClass([obj class]));

Class returnClass = object_setClass(obj, [MXSomeDemoClass class]);
NSLog(@"returnClass:%@", NSStringFromClass(returnClass));
NSLog(@"objClass2:%@", NSStringFromClass([obj class]));

打印结果是:

aClass:MXRuntimeDemoClass
objClass1:MXRuntimeDemoClass
returnClass:MXRuntimeDemoClass
objClass2:MXSomeDemoClass

获取对象的类名

const char *object_getClassName(id obj)

来看一个实例:

MXRuntimeDemoClass *obj = [MXRuntimeDemoClass new];
const char *name = object_getClassName(obj);
NSString *className = [NSString stringWithCString:name encoding:NSUTF8StringEncoding];
NSLog(@"className:%@", className);

打印结果:

className:MXRuntimeDemoClass

添加方法

BOOL class_addMethod(Class cls,SEL name,IMP imp, const char *types)

此函数具有四个参数:

  • Class cls: 要添加方法的类

  • SEL name: 要添加的方法的方法名

  • IMP imp: 要添加的方法的实现

  • const char *types: 方法签名,你可以参考官方文档,这里不做赘述

首先,要动态的为类添加一个方法,需要先定义这个方法的实现,例如一个 C 实现:

int newFunction(id self, SEL _cmd, NSString *str, NSNumber *num) {
    NSLog(@"\nstr:%@\nnum:%@\n", str, num);
    return num.intValue;
}

接下来,添加和调用:

MXRuntimeDemoClass *obj = [MXRuntimeDemoClass new];
BOOL res = class_addMethod([MXRuntimeDemoClass class], @selector(newMethod::), (IMP)newFunction, "[email protected]:@@");
if ([obj respondsToSelector:@selector(newMethod::)]) {
  NSLog(@"Success");
  [obj performSelector:@selector(newMethod::) withObject:@"String" withObject:@(2)];
} else {
  NSLog(@"Failed");
}

打印结果是:

Success
str:String
num:2

需要注意的是,如果类中已经存在此实现,将不会进行替换,要替换实现请使用:

IMP method_setImplementation(Method m, IMP imp)

如果不使用 C 实现而是 Objective-C 的话,你需要使用 (IMP)instanceMethodForSelector:(SEL)aSelector; 方法,不做赘述。

关联对象

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
id objc_getAssociatedObject(id object, const void *key)

首先解释一下这两个方法的参数,设置关联对象的方法 objc_setAssociatedObject 有四个参数:

  • id object: 要关联对象的实例。

  • const void *key: 关联对象的 key 值,主要用来在其他地方获取关联对象

  • id value: 关联对象

  • objc_AssociationPolicy: 关联策略

其中最后一个参数 objc_AssociationPolicy (关联策略)包括下列内容:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied.
                                            *   The association is not made atomically. */
    OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                            *   The association is made atomically. */
    OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                            *   The association is made atomically. */
};

获取关联对象的方法 objc_getAssociatedObject 有两个参数:

  • id object: 包含要获取的关联对象的实例

  • const void *key: 关联对象的 key,和前面提到的一致

好了,现在来看实例:

MXRuntimeDemoClass *obj = [[MXRuntimeDemoClass alloc] init];
// 使用 associatedObjectKey 的地址作为 key 值
static char associatedObjectKey;
// 创建关联对象
NSString *assString = @"这是一个关联对象";
// 添加为实例 obj 添加关联对象
objc_setAssociatedObject(obj, &associatedObjectKey, assString, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 通过 key 获取关联对象
NSString *getStr = objc_getAssociatedObject(obj, &associatedObjectKey);
NSLog(@"%@", getStr);

打印结果:

这是一个关联对象

替换方法

IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)

参数与 class_addMethod 类似,不做赘述。

获取类中的所有方法名

SEL method_getName(Method m)
Method *class_copyMethodList(Class cls, unsigned int *outCount)

来看实例:

unsigned int insCount = 0;
Method *insMethods = class_copyMethodList([MXRuntimeDemoClass class], &insCount);
for (NSInteger i = 0; i < insCount; ++i) {
  SEL name = method_getName(insMethods[i]);
  NSString *nameString = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
  NSLog(@"%@", nameString);
}

unsigned int classCount = 0;
Method *classMethods = class_copyMethodList(object_getClass([MXRuntimeDemoClass class]), &classCount);
for (NSInteger i = 0; i < classCount; ++i) {
  SEL name = method_getName(classMethods[i]);
  NSString *nameString = [NSString stringWithCString:sel_getName(name) encoding:NSUTF8StringEncoding];
  NSLog(@"%@", nameString);
}

其中 MXRuntimeDemoClass 类中只有下列方法的定义:

  • - (void)someInstanceMethod1;

  • - (void)someInstanceMethod2;

  • + (void)someClassMethod;

打印结果:

someInstanceMethod1
someInstanceMethod2
someClassMethod

获取类中的所有属性名

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)

来看实例:

u_int count;
objc_property_t *properties = class_copyPropertyList([MXRuntimeDemoClass class], &count);
for (int i = 0; i < count ; i++) {
  const char *propertyName = property_getName(properties[i]);
  NSString *nameString = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding];
  NSLog(@"%@", nameString);
}

打印结果:

someInteger
someString
someArray

获取所有实例变量

Ivar *class_copyIvarList(Class cls, unsigned int *outCount)

来看实例:

MXRuntimeDemoClass *obj = [[MXRuntimeDemoClass alloc] init];
[obj setSomeNumber:@(99)];
[obj setSomeArray:@[@"Hello", @(2)]];
[obj setSomeString:@"String"];

unsigned int count = 0;
Ivar * ivars = class_copyIvarList([MXRuntimeDemoClass class], &count);

for(int i = 0; i < count; i++) {
  Ivar someIvar = ivars[i];

  NSString *ivarName = [NSString stringWithCString:ivar_getName(someIvar) encoding:NSUTF8StringEncoding];

  NSString *ivatType = [NSString stringWithCString:ivar_getTypeEncoding(someIvar) encoding:NSUTF8StringEncoding];

  id var = object_getIvar(obj, someIvar);

  NSLog(@"%@(%@) = %@", ivarName, ivatType, var);
}

打印结果:

_someInteger(q) = (null)
_someNumber(@"NSNumber") = 99
_someString(@"NSString") = String
_someArray(@"NSArray") = (
    Hello,
    2
)

当然你不但可以获取 iVar 的值,也可以做出修改:

id object_getIvar(id obj, Ivar ivar)
void object_setIvar(id obj, Ivar ivar, id value)

根据苹果的说明,这两个方法在实例变量已知的情况下,要比 object_getInstanceVariableobject_setInstanceVariable 方法更快,这里不做赘述。

交换方法实现

void method_exchangeImplementations(Method m1, Method m2)

来看实例:

Method method1 = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method method2 = class_getInstanceMethod([NSString class], @selector(uppercaseString));
method_exchangeImplementations(method1, method2);
NSLog(@"lowercaseString:%@", [@"www.MENINY.cn" lowercaseString]);
NSLog(@"uppercaseString:%@", [@"WWW.meniny.CN" uppercaseString]);

打印结果:

lowercaseString:WWW.MENINY.CN
uppercaseString:www.meniny.cn

获取和设置方法实现

Method class_getInstanceMethod(Class cls, SEL name)
IMP method_setImplementation(Method m, IMP imp)

来看实例,首先在测试类 MXRuntimeDemoClass 中声明和定义两个方法:

- (void)someInstanceMethod1 {
    NSLog(@"%s", __func__);
}

- (void)someInstanceMethod2 {
    NSLog(@"%s", __func__);
}

接下来我们将获取 someInstanceMethod1 的实现,并设置给 someInstanceMethod2:

MXRuntimeDemoClass *obj = [[MXRuntimeDemoClass alloc] init];
[obj someInstanceMethod2];

Method method1 = class_getInstanceMethod([MXRuntimeDemoClass class], @selector(someInstanceMethod1));
IMP methodIMP = method_getImplementation(method1);

Method method2 = class_getInstanceMethod([MXRuntimeDemoClass class], @selector(someInstanceMethod2));
method_setImplementation(method2, methodIMP);

[obj someInstanceMethod2];

打印结果:

[MXRuntimeDemoClass someInstanceMethod2]
[MXRuntimeDemoClass someInstanceMethod1]

OBJC_ARC_UNAVAILABLE

此外,还有一些函数并不支持 ARC 模式,分别有:

// OBJC_ARC_UNAVAILABLE
id object_copy(id obj, size_t size)// 对象拷贝
id object_dispose(id obj)// 对象释放
Ivar object_setInstanceVariable(id obj, const char *name, void *value)// 设置实例变量的值
Ivar object_getInstanceVariable(id obj, const char *name, void **outValue)// 获取实例变量的值
Class objc_getFutureClass(const char *name)
id class_createInstance(Class cls, size_t extraBytes)
id objc_constructInstance(Class cls, void *bytes)
void *objc_destructInstance(id obj)

以及下面几个不支持 iOS 环境的函数:

// OBJC_ARC_UNAVAILABLE
// __IPHONE_NA
id object_copyFromZone(id anObject, size_t nBytes, void *z)
id class_createInstanceFromZone(Class, size_t idxIvars, void *z)

结语

那么,Runtime 的基本使用就是这样,事实上 runtime.h 中还有很多函数,提供了十分详尽的功能和解释,基本的道理是一样的,大家只需要根据需求灵活应用。当然,还是那句话,Runtime 的确是一把锋利的刀,但要小心伤到自己。