美文网首页将来跳槽用iOS学习笔记iOS开发
笔记 - Runtime(征服面试官)

笔记 - Runtime(征服面试官)

作者: 强子ly | 来源:发表于2019-09-25 19:58 被阅读0次

    什么是runtime? 平时项目中有用过么?

    什么是runtime?
    - oc是一门动态性比较强的编程语言,允许很多操作推迟到运行时再进行
    - OC的动态性就是由runtime支撑和实现的,runtime是一套c语言的api,封装了很多动态性相关的函数
    - 平时编写的OC代码,底层都是转换成了RuntimeAPI进行调用
    
    
    平时项目中有用过么?
    - 利用关联对象(AssociatedObject)给分类添加属性
    - 遍历类所有成员变量(访问私有成员变量、字典转模型、自动归档解档)
    - 交换方法实现(交换系统的方法)
    - 利用消息转发机制解决方法找不到的异常问题
    

    一、利用关联对象(AssociatedObject)给分类添加属性

    为一个已有的类(比如说不想影响其文件结构)、第三方库提供的类增加几个property,已经是十分常见且需要的操作了,有人会单独起草一份category.m文件,也有人直接继承。
    category的好处就是:
    1、能减少类文件的数量提高编译速度;
    2、也是为了代码结构更加清晰。

    例:SDWebImage 使用url作为key进行关联
    
    static char imageURLKey;
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    

    想具体了解的可以看我之前写的一片文章 浅谈runtime关联


    二、遍历类所有成员变量(访问私有成员变量、字典转模型、自动归档解档)
    • 2.1、获取UITextField的所有成员变量,并修改占位文字颜色
    unsigned int count = 0;
    // 拷贝出成员变量列表
    Ivar *ivars = class_copyIvarList([UITextField class], &count);
    
    for (int i = 0; i<count; i++) {
        // 取出成员变量
        Ivar ivar = *(ivars + i);
        // 打印成员变量名字
        NSLog(@"变量名:%s;  类型:%s", ivar_getName(ivar), ivar_getTypeEncoding(ivar));
    }
    //释放
    free(ivars);
    
    
    ...
    2019-09-24 20:43:09.900829+0800 test111[976:11971] 变量名:_placeholderLabel;  类型:@"UITextFieldLabel"
    2019-09-24 20:43:09.901223+0800 test111[976:11971] 变量名:_suffixLabel;  类型:@"UITextFieldLabel"
    2019-09-24 20:43:09.901560+0800 test111[976:11971] 变量名:_prefixLabel;  类型:@"UITextFieldLabel"
    ...
    
    self.textField.placeholder = @"请输入用户名称";
    [self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];
    

    同理,感兴趣的可以去看看UIAlertView、或UIActionSheet等。

    • 2.2、字典转换模型
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // 字典转换模型
            NSDictionary *json = @{
                                   @"age" : @20,
                                   @"weight" : @60,
                                   @"name"   : @"JACK"
                                   };
            MJPerson *person = [MJPerson mj_objectWithJson:json];
        }
        return 0;
    }
    
    @interface MJPerson : NSObject
    
    @property (nonatomic, assign) int weight;
    @property (nonatomic, assign) int age;
    @property (nonatomic, copy) NSString *name;
    
    @end
    
    @implementation NSObject (Json)
    
    + (instancetype)mj_objectWithJson:(NSDictionary *)json {
        id object = [[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)];
            
            // 设值
            [object setValue:json[name] forKey:name];
        }
        free(ivars);
        return object;
    }
    
    @end
    

    三、交换方法实现 method_exchangeImplementations
    • 3.1、无侵入式埋点(运行时方法替换,hook系统方法)
    #import "UIViewController+BigDataExtend.h"
    #import <objc/runtime.h>
    
    @implementation UIViewController (BigDataExtend)
    
    + (void)load {
        Method method1 = class_getInstanceMethod(self, @selector(viewWillDisappear:));
        Method method2 = class_getInstanceMethod(self, @selector(bigdata_viewWillDisappear:));
        method_exchangeImplementations(method1, method2);
    }
    
    - (void)bigdata_viewWillDisappear:(BOOL)animated {
        // 插入相关代码,
        // 再执行原方法
        [self bigdata_viewWillDisappear:animated];
    }
    
    @end
    
    • 3.2、数组插入空对象崩溃,进行错误判断(感兴趣的同学可以去弄一下字典的判断)
    #import "NSMutableArray+Extend.h"
    #import <objc/runtime.h>
    
    @implementation NSMutableArray (Extend)
    
    + (void)load {
        // 类簇:NSStirng、NSArray、NSDictionary,真实类型是其他类型
        Class cls = NSClassFromString(@"__NSArrayM");
        Method method1 = class_getInstanceMethod(cls, @selector(insertObject:atIndex:));
        Method method2 = class_getInstanceMethod(cls, @selector(wj_insertObject:atIndex:));
        method_exchangeImplementations(method1, method2);
    }
    
    - (void)wj_insertObject:(id)anObject atIndex:(NSUInteger)index {
        if (anObject == nil) return;
    
        // 只有对象存在才会去执行系统方法
        [self wj_insertObject:anObject atIndex:index];
    }
    
    @end
    
    • 3.3、手机字体适配
    #import "UIFont+Extend.h"
    #import <objc/runtime.h>
    
    @implementation UIFont (Extend)
    
    + (void)load {
        Method method1 = class_getInstanceMethod(self, @selector(systemFontOfSize:));
        Method method2 = class_getInstanceMethod(self, @selector(wj_systemFontOfSize:));
        method_exchangeImplementations(method1, method2);
    }
    
    + (UIFont *)wj_systemFontOfSize:(CGFloat)fontSize {
        // 这里可根据手机屏幕尺寸制定系数
        const CGFloat modulus = 1.5;
        [self wj_systemFontOfSize:fontSize * modulus];
    }
    
    @end
    
    • 3.4、拦截所有button点击事件
    #import "UIControl+Extend.h"
    #import <objc/runtime.h>
    
    @implementation UIControl (Extend)
    
    + (void)load {
        Method method1 = class_getInstanceMethod(self, @selector(sendAction:to:forEvent:));
        Method method2 = class_getInstanceMethod(self, @selector(wj_sendAction:to:forEvent:));
        method_exchangeImplementations(method1, method2);
    }
    
    
    - (void)wj_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event {
    //    if ([self isKindOfClass:[UIButton class]]) {
    //        // 拦截了所有按钮的事件
    //    }
        
        // 重新实现系统自带方法
        [self wj_sendAction:action to:target forEvent:event];
    }
    
    @end
    

    这里主要考察了UIButton继承自UIControl,替换UIControl系统方法


    • 动态创建一个类,并向其中动态添加方法成员变量
    - (void)dynamicAddObject {
        // 动态创建一个Person类
        NSString *ocObjectName = @"Person";
        const char *cObjectName = [ocObjectName cStringUsingEncoding:NSUTF8StringEncoding];
    
        Class newClass = objc_allocateClassPair([NSObject class], cObjectName, 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@:");
    
        /*
         *  class_addIvar 只能在objc_registerClassPair之前调用
         *  不支持将实例变量添加到已有的类
         */
        objc_registerClassPair(newClass);
    
        id person = [[newClass alloc] init];
        [person setValue:@10 forKey:@"_age"];
        [person setValue:@20 forKey:@"_weight"];
    
        [person run];
    
        NSLog(@"%@ -- %@", [person valueForKey:@"_age"], [person valueForKey:@"_weight"]);
        
        // 在不需要这个类时释放
        objc_disposeClassPair(newClass);
    }
    
    void run(id self, SEL _cmd){
        NSLog(@"动态方法调用");
    }
    

    相关文章

      网友评论

        本文标题:笔记 - Runtime(征服面试官)

        本文链接:https://www.haomeiwen.com/subject/esvvuctx.html