Runtime(七)、runtime应用

915 阅读1分钟

成员变量

// 获取成员变量信息
Ivar ageIvar = class_getInstanceVariable([MJPerson class], "_age");
NSLog(@"%s %s", ivar_getName(ageIvar), ivar_getTypeEncoding(ageIvar));

// 设置和获取成员变量的值
Ivar nameIvar = class_getInstanceVariable([MJPerson class], "_name");

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

// 成员变量的数量
unsigned int count;
Ivar *ivars = class_copyIvarList([MJPerson class], &count);
for (int i = 0; i < count; i++) {
    // 取出i位置的成员变量
    Ivar ivar = ivars[i];
    NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
}
free(ivars);

  • object_setIvar(person, ageIvar, (__bridge id)(void *)10); 这个方法传的值,必须是OC类型。当成员变量是基本类型时,比如这里是int类型,不能是NSNumber类型@(10),必须用 void *并桥接成OC类型。不能用int *桥接
  • class_copyIvarList创建出来的必须释放free(ivars);runtime里如果调用了copy或create创建出来的必须释放。

MJPerson *person = [[MJPerson alloc] init];
    [person run];
    
    object_setClass(person, [MJCar class]);
    [person run];
    
    NSLog(@"%d %d %d",
          object_isClass(person),
          object_isClass([MJPerson class]),
          object_isClass(object_getClass([MJPerson class]))
          );
    
    //        NSLog(@"%p %p", object_getClass([MJPerson class]), [MJPerson class]);

        // 创建类
        Class newClass = objc_allocateClassPair([NSObject class], "MJDog", 0);
        class_addIvar(newClass, "_age", 4, 1, @encode(int));
        class_addIvar(newClass, "_weight", 4, 1, @encode(int));
        class_addMethod(newClass, @selector(run), (IMP)run, "v@:");
        // 注册类
        objc_registerClassPair(newClass);
        
        MJPerson *person = [[MJPerson alloc] init];
        object_setClass(person, newClass);
        [person run];

        id dog = [[newClass alloc] init];
        [dog setValue:@10 forKey:@"_age"];
        [dog setValue:@20 forKey:@"_weight"];
        [dog run];

        NSLog(@"%@ %@", [dog valueForKey:@"_age"], [dog valueForKey:@"_weight"]);
        
        // 在不需要这个类时释放
        objc_disposeClassPair(newClass);
  • 添加成员变量和方法,必须在注册类之前。一旦注册就无法再添加成员变量。所以无法动态创建已经存在的类。
  • 方法的添加可以放在注册后,因为方法放在class_rw_t里。成员变量放在class_ro_t里。
  • 不需要这个类的时要释放。
  • objc_allocateClassPair从名字可以看出(Pair),创建的是两个对象,类对象和元类对象。

字典转模型

#import "NSObject+Json.h"
#import <objc/runtime.h>

@implementation NSObject (Json)

+ (instancetype)mj_objectWithJson:(NSDictionary *)json
{
    id obj = [[self alloc] init];
    
    unsigned int count;
    Ivar *ivars = class_copyIvarList(self, &count);
    for (int i = 0; i < count; i++) {
        // 取出i位置的成员变量
        Ivar ivar = ivars[i];
        NSMutableString *name = [NSMutableString stringWithUTF8String:ivar_getName(ivar)];
        [name deleteCharactersInRange:NSMakeRange(0, 1)];
        
        // 设值
        id value = json[name];
        if ([name isEqualToString:@"ID"]) {
            value = json[@"id"];
        }
        [obj setValue:value forKey:name];
    }
    free(ivars);
    
    return obj;
}

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        
         //字典转模型
        NSDictionary *json = @{
                               @"id" : @20,
                               @"age" : @20,
                               @"weight" : @60,
                               @"name" : @"Jack"
//                               @"no" : @30
                               };

        MJPerson *person = [MJPerson mj_objectWithJson:json];

        [MJCar mj_objectWithJson:json];

        MJStudent *student = [MJStudent mj_objectWithJson:json];

        NSLog(@"123");
    }
    return 0;
}

属性

方法

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *person = [[MJPerson alloc] init];
        
        Method runMethod = class_getInstanceMethod([MJPerson class], @selector(run));
        Method testMethod = class_getInstanceMethod([MJPerson class], @selector(test));
        method_exchangeImplementations(runMethod, testMethod);

        [person run];
    }
    return 0;
}

void myrun()
{
    NSLog(@"---myrun");
}

void test()
{
    MJPerson *person = [[MJPerson alloc] init];
    
    //        class_replaceMethod([MJPerson class], @selector(run), (IMP)myrun, "v");
    
    
    class_replaceMethod([MJPerson class], @selector(run), imp_implementationWithBlock(^{
        NSLog(@"123123");
    }), "v");
    
    [person run];
}

  • method_exchangeImplementtations交换方法本质是,交换imp.这个操作会清空缓存。

具体应用

设置UITextField的placeholder的字体颜色
正常:

    NSMutableDictionary *attrs = [NSMutableDictionary dictionary];
    attrs[NSForegroundColorAttributeName] = [UIColor redColor];
    self.textField.attributedPlaceholder = [[NSMutableAttributedString alloc] initWithString:@"请输入用户名" attributes:attrs];

但是也可以,找到这个属性,查看它的类型,然后通过runtime设置。

    unsigned int count;
    Ivar *ivars = class_copyIvarList([UITextField class], &count);
    for (int i = 0; i < count; i++) {
        // 取出i位置的成员变量
        Ivar ivar = ivars[i];
        NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
    }
    free(ivars);
Ivar ivar = class_getInstanceVariable([UITextField class],"_placeholderLabel");
    NSLog(@"%s %s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
    
    
    NSLog(@"%@",class_getSuperclass(NSClassFromString(@"UITextFieldLabel")));
self.textField.placeholder = @"请输入用户名";
//    NSLog(@"%@",class_getSuperclass([UITextFieldLabel class]));//报错,这个类是前向声明。我们必须找到它的父类
//
//    //找到UITextFieldLabel的父类
//    NSLog(@"%@",class_getSuperclass(NSClassFromString(@"UITextFieldLabel")));
//
//
//    [self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
    

    UILabel *placeholderLabel = [self.textField valueForKeyPath:@"_placeholderLabel"];
    placeholderLabel.textColor = [UIColor redColor];

13.0后不允许用KVC方式,设置其成员变量了。运行崩溃。 用下面的方法:

    Ivar ivar = class_getInstanceVariable([UITextField class],"_placeholderLabel");
    UILabel *label = object_getIvar(self.textField,ivar);
    label.textColor = [UIColor redColor];

改变按钮点击事件

@implementation UIControl (Extension)

+ (void)load
{
    // hook:钩子函数
    Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
    Method method2 = class_getInstanceMethod(self, @selector(mj_sendAction:to:forEvent:));
    method_exchangeImplementations(method1, method2);
}

- (void)mj_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
    NSLog(@"%@-%@-%@", self, target, NSStringFromSelector(action));
    
    // 调用系统原来的实现
    [self mj_sendAction:action to:target forEvent:event];
    
//    [target performSelector:action];
    
//    if ([self isKindOfClass:[UIButton class]]) {
//        // 拦截了所有按钮的事件
//
//    }
}

  • [btn addTarget:<#(nullable id)#> action:<#(nonnull SEL)#> forControlEvents:<#(UIControlEvents)#>] 本质上是调用了 UIControllersendAction:to:forEvent:

  • 调用系统原来的实现是[self mj_sendAction:action to:target forEvent:event];,因为两个方法已经交换了。而不是sendAction:to:forEvent:

解决数组元素为nil

    NSString *obj = nil;
    
    NSMutableArray *array = [NSMutableArray array];
    [array addObject:@"jack"];
    [array insertObject:obj atIndex:0];
+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 类簇:NSString、NSArray、NSDictionary,真实类型是其他类型
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(mj_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    });
}

- (void)mj_insertObject:(id)anObject atIndex:(NSUInteger)index
{
    if (anObject == nil) return;
    
    [self mj_insertObject:anObject atIndex:index];
}

报错:reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'

  • NSArray,NSMutableArray,NSDictionary,NSMutableDictionary等都是运行在类簇模式下。真实类型是其他类型。

字典key为nil

以下代码并不会crash:

    NSString *obj = nil;
    NSDictionary *dict = @{@"name" : [[NSObject alloc] init],
                           @"age" : @"jack"};
    NSString *value =  dict[obj];

    NSLog(@"%@", [dict class]);

以下代码会崩溃:

 NSString *obj = nil;

    NSMutableDictionary *mulDict = [NSMutableDictionary dictionary];
    mulDict[@"name"] = @"jack";
    mulDict[obj] = @"rose"; // 会崩溃
    mulDict[@"age"] = obj; //不会崩溃
//    [mulDict setObject:nil forKey:@"nickname"]; //会崩溃

    NSLog(@"%@", [mulDict class]);

解决方案:

@implementation NSMutableDictionary (Extension)

+ (void)load
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class cls = NSClassFromString(@"__NSDictionaryM");
        Method method1 = class_getInstanceMethod(cls, @selector(setObject:forKeyedSubscript:));
        Method method2 = class_getInstanceMethod(cls, @selector(mj_setObject:forKeyedSubscript:));
        method_exchangeImplementations(method1, method2);
        
        Class cls2 = NSClassFromString(@"__NSDictionaryI");
        Method method3 = class_getInstanceMethod(cls2, @selector(objectForKeyedSubscript:));
        Method method4 = class_getInstanceMethod(cls2, @selector(mj_objectForKeyedSubscript:));
        method_exchangeImplementations(method3, method4);
    });
}

- (void)mj_setObject:(id)obj forKeyedSubscript:(id<NSCopying>)key
{
    if (!key) return;
    
    [self mj_setObject:obj forKeyedSubscript:key];
}

- (id)mj_objectForKeyedSubscript:(id)key
{
    if (!key) return nil;
    
    return [self mj_objectForKeyedSubscript:key];
}

@end

  • dispatch_once 保证这部分代码只运行一次,这里也可以不要。因为load方法一般只走一次。
  • 这个分类用的时候不需要导入,因为+load方法不导入也会走的。