Runtime的原理探索-常用场景

/ 0

常用API

动态创建一个类(参数:父类,类名,额外的内存空间)

Class objc_allocateClassPair(Class superclass, const char * name, size_t extraBytes)

注册一个类(要在类注册之前添加成员变量)

void objc_registerClassPair(Class cls) 

销毁一个类

void objc_disposeClassPair(Class cls)

获取 isa 指向的 Class

Class object_getClass(id obj) 

设置 isa 指向的 Class

Class object_setClass(id obj, Class cls)

判断一个 OC 对象是否为类对象

BOOL object_isClass(id obj)

判断一个 Class 是否为元类对象

BOOL class_isMetaClass(Class cls)

获取父类

Class class_getSuperclass(Class cls)

成员变量

获取一个实例变量信息

Ivar class_getInstanceVariable(Class cls, const char * name)

拷贝实例变量列表(最后需要调用 free 释放)

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

设置和获取成员变量的值

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

动态添加成员变量(已经注册的类是不能动态添加成员变量的)

BOOL class_addIvar(Class cls, const char * name, size_t size, uint8_t alignment, const char * types)

获取成员变量的相关信息

const char * ivar_getName(Ivar v)
const char * ivar_getTypeEncoding(Ivar v)

属性

获取一个属性

objc_property_t class_getProperty(Class cls, const char * name)

拷贝属性列表(最后需要调用 free 释放)

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

动态添加属性

BOOL class_addProperty(Class cls, const char * name, const objc_property_attribute_t * attributes, unsigned int attributeCount)

动态替换属性

void class_replaceProperty(Class cls, const char * name, const objc_property_attribute_t * attributes, unsigned int attributeCount)

获取属性的一些信息

const char * property_getName(objc_property_t property)
const char * property_getAttributes(objc_property_t property) 

方法

获得一个实例方法、类方法

Method class_getInstanceMethod(Class cls, SEL name)
Method class_getClassMethod(Class cls, SEL name)

方法实现相关操作

IMP class_getMethodImplementation(Class cls, SEL name) 
IMP method_setImplementation(Method m, IMP imp)
void method_exchangeImplementations(Method m1, Method m2)

拷贝方法列表(最后需要调用 free 释放)

Method *class_copyMethodList(Class cls, unsigned int *outCount)

动态添加方法

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

动态替换方法

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

获取方法的相关信息(带有 copy 的需要调用 free 去释放)

SEL method_getName(Method m)
IMP method_getImplementation(Method m)
const char *method_getTypeEncoding(Method m)
unsigned int method_getNumberOfArguments(Method m)
char *method_copyReturnType(Method m)
char *method_copyArgumentType(Method m, unsigned int index)

选择器相关

const char *sel_getName(SEL sel)
SEL sel_registerName(const char *str)

block 作为方法实现

IMP imp_implementationWithBlock(id block)
id imp_getBlock(IMP anImp)
BOOL imp_removeBlock(IMP anImp)

动态创建类

void run(id self, SEL _cmd)
{
    NSLog(@"%@ - %@", self, NSStringFromSelector(_cmd));
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建类
        Class newCls = objc_allocateClassPair([Person class], "Student", 0);
        // 添加成员变量
        class_addIvar(newCls, "_no", 4, 1, @encode(int));
        // 添加方法
        class_addMethod(newCls, @selector(run), (IMP)run, "v@:");

        // 注册类
        objc_registerClassPair(newCls);

        id stu = [[newCls alloc] init];
        [stu setValue:@(10) forKey:@"_no"];
        [stu run];

        NSLog(@"%@", [stu valueForKey:@"_no"]);

        // 不需要这个类时,释放
        objc_disposeClassPair(newCls);
    }
    return 0;
}

获取和设置实例变量

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Ivar ageIvar = class_getInstanceVariable([Person class], "_age");
        NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
        // _age i

        Ivar nameIvar = class_getInstanceVariable([Person class], "_name");
        NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));
        // _name @"NSString"

        Person *person = [[Person alloc] init];
        object_setIvar(person, ageIvar, (__bridge id)(void *)10);
        NSLog(@"%d", person.age);
        // 10

        object_setIvar(person, nameIvar, @"jack");
        NSLog(@"%@, %@", person.name, object_getIvar(person, nameIvar));
        // jack, jack
    }
    return 0;
}

遍历指定类的实例变量

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        unsigned int count;
        Ivar *ivars = class_copyIvarList([Person class], &count);
        for (int i = 0; i < count; i++) {
            Ivar ivar = ivars[i];
            NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
            // _age i
            // _name @"NSString"
        }
        free(ivars);
    }
    return 0;
}

简单字典转模型

runtime 结合 KVC 实现简单的字典转模型。只是简单演示,不考虑特殊字段和继承体系:

@interface Person : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, assign) float height;
@property (nonatomic, copy) NSString *name;
@end

@interface NSObject (Json)
+ (instancetype)jf_objectWithJson:(NSDictionary *)json;
@end

@implementation NSObject (Json)
+ (instancetype)jf_objectWithJson:(NSDictionary *)json
{
    id obj = [[self alloc] init];

    unsigned int count;
    Ivar *ivars = class_copyIvarList([self class], &count);
    for (int i = 0; i < count; i++) {
        Ivar ivar = ivars[i];
        NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
        if ([name hasPrefix:@"_"]) {
            [name deleteCharactersInRange:NSMakeRange(0, 1)]; // 删除成员变量的_前缀
        }
        // kvc给成员变量设置值
        [obj setValue:json[name] forKey:name];
    }
    free(ivars);

    return obj;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSDictionary *json = @{
            @"age": @10,
            @"height": @1.8,
            @"name": @"jack"
        };
        Person *person = [Person jf_objectWithJson:json];
        NSLog(@"%d, %.2f, %@", person.age, person.height, person.name);
        // 10, 1.80, jack
    }
    return 0;
}

替换方法

@interface Person : NSObject
- (void)test;
@end

@implementation Person
- (void)test
{
    NSLog(@"test");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        class_replaceMethod([Person class], @selector(test), imp_implementationWithBlock(^{
            NSLog(@"block");
        }), "v@:");
        [[[Person alloc] init] test]; // block
    }
    return 0;
}

交换方法实现

@interface Person : NSObject
- (void)test1;
- (void)test2;
@end

@implementation Person
- (void)test1
{
    NSLog(@"%s", __func__);
}
- (void)test2
{
    NSLog(@"%s", __func__);
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Method test1Method = class_getInstanceMethod([Person class], @selector(test1));
        Method test2Method = class_getInstanceMethod([Person class], @selector(test2));
        // 相当于替换两个Method里的IMP
        method_exchangeImplementations(test1Method, test2Method);
        [[[Person alloc] init] test1]; // -[Person test2]
    }
    return 0;
}

给分类添加"实例变量"

@interface Person (Test)
@property (nonatomic, assign) int age;
@end

@implementation Person (Test)
- (void)setAge:(int)age
{
    objc_setAssociatedObject(self, @"age", @(age), OBJC_ASSOCIATION_ASSIGN);
}
- (int)age
{
    return [objc_getAssociatedObject(self, @"age") intValue];
}
@end

拦截系统方法

下面例子只是拦截了所有按钮点击事件,这样可以监控到所有按钮的点击事件,也可以处理某个点击事件不响应:

@implementation UIControl (Extension)
+ (void)load
{
    // 防止手动调用+load方法,或者其他原因导致load被再次调用
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method methodOld = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        Method methodNew = class_getInstanceMethod(self, @selector(jf_sendAction:to:forEvent:));
        method_exchangeImplementations(methodOld, methodNew);
    });
}
- (void)jf_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
    NSLog(@"%@, %@, %@", self, target, NSStringFromSelector(action));

    // 调用原先的方法
    [self jf_sendAction:action to:target forEvent:event];
}
@end

想实现诸如防止字典添加 nil 元素、数组越界等等错误,都可以使用这种方式来实现拦截。