美文网首页
iOS底层 -- Runtime之Runtime的API及相关应

iOS底层 -- Runtime之Runtime的API及相关应

作者: happy神悦 | 来源:发表于2020-09-14 09:35 被阅读0次

    一、类

    与类相关的API如下:

        //动态创建一个类(参数:父类,类名,额外的内存空间)
        Class objc_allocateClassPair(Class  _Nullable __unsafe_unretained superclass, const char * _Nonnull name, size_t extraBytes);
        
        //注册一个类(要在类注册之前添加成员变量)
        void objc_registerClassPair(Class  _Nonnull __unsafe_unretained cls);
        
        //销毁一个类
        void objc_disposeClassPair(Class  _Nonnull __unsafe_unretained cls);
        
        //获取isa指向的Class
        Class object_getClass(id  _Nullable obj);
        
        //设置isa指向的Class
        Class object_setClass(id  _Nullable obj, Class  _Nonnull __unsafe_unretained cls);
        
        //判断一个OC对象是否为Class
        BOOL object_isClass(id  _Nullable obj);
        
        //判断一个Class是否为元类
        BOOL class_isMetaClass(Class  _Nullable __unsafe_unretained cls);
        
        //获取父类
        CLass class_getSuperclass(Class  _Nullable __unsafe_unretained cls);
    

    二、成员变量

    与成员变量相关的API如下:

        //获取一个成员变量
        Ivar class_getInstanceVariable(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);
        
        //拷贝成员变量列表
        Ivar *class_copyIvarList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
        
        //设置和获取成员变量的值
        void object_setIvar(id  _Nullable obj, Ivar  _Nonnull ivar, id  _Nullable value);
        id object_getIvar(id  _Nullable obj, Ivar  _Nonnull ivar);
        
        //动态添加成员变量(已经注册的类不能动态添加成员变量)
        BOOL class_addIvar(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, size_t size, uint8_t alignment, const char * _Nullable types);
        
        //获取成员变量的相关信息
        const char *ivar_getName(Ivar  _Nonnull v);
        const char *ivar_getTypeEncoding(Ivar  _Nonnull v);
    

    三、属性

    与属性相关的runtime的API如下:

        //获取一个属性
        objc_property_t class_getProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name);
        
        //拷贝属性列表(最后需要调用free释放)
        objc_property_t *class_copyPropertyList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
        
        //动态添加属性
        BOOL class_addProperty(Class  _Nullable __unsafe_unretained cls, const char * _Nonnull name, const objc_property_attribute_t * _Nullable attributes, unsigned int attributeCount);
        
        //动态替换属性
        void class_replaceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);
        
        //获取属性的一些信息
        const char *property_getName(objc_property_t  _Nonnull property);
        const char *property_getAttributes(objc_property_t  _Nonnull property);
    

    四、方法

    与方法相关的runtime的API:

        //获得一个实例方法,类方法
        Method class_getInstanceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
        Method class_getClassMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
        
        //方法实现相关操作
        IMP class_getMethodImplementation(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name);
        IMP method_setImplementation(Method  _Nonnull m, IMP  _Nonnull imp);
        void method_exchangeImplementations(Method  _Nonnull m1, Method  _Nonnull m2);
        
        //拷贝方法列表(最后需要调用free释放)
        Method *class_copyMethodList(Class  _Nullable __unsafe_unretained cls, unsigned int * _Nullable outCount);
        
        //动态添加方法
        BOOL class_addMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);
        
        //动态替换方法
        IMP class_replaceMethod(Class  _Nullable __unsafe_unretained cls, SEL  _Nonnull name, IMP  _Nonnull imp, const char * _Nullable types);
        
        //获取方法的相关信息
        SEL method_getName(Method  _Nonnull m);
        IMP method_getImplementation(Method  _Nonnull m);
        const char *method_getTypeEncoding(Method  _Nonnull m);
        unsigned int method_getNumberOfArguments(Method  _Nonnull m);
        char *method_copyReturnType(Method  _Nonnull m);
        char *method_copyArgumentType(Method  _Nonnull m, unsigned int index);
        
        //选择器相关
        const char *sel_getName(SEL  _Nonnull sel);
        SEL sel_registerName(const char * _Nonnull str);
    

    相关应用

    • 使用关联对象,实现给分类增加属性
    #import <UIKit/UIKit.h>
    
    @interface UIImage (downLoadURL)
    @property (nonatomic, strong) NSString *downLoadURL;
    @end
    
    #import "UIImage+downLoadURL.h"
    #import <objc/runtime.h>
    @implementation UIImage (downLoadURL)
    
    -(void)setDownLoadURL:(NSString *)downLoadURL{
        objc_setAssociatedObject(self, @selector(downLoadURL), downLoadURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    -(NSString *)downLoadURL{
        return objc_getAssociatedObject(self, @selector(downLoadURL));
    }
    @end
    
    • 修改UITextfield的占位文字颜色
     unsigned int count;
        Ivar *ivarList = class_copyIvarList([self.textField class], &count);
        for (int i = 0; i < count; i++) {
            Ivar ivar = ivarList[i];
           const char *ivarName = ivar_getName(ivar);
            NSLog(@"%s", ivarName);
        }
        free(ivarList);
    

    [self.textField setValue:[UIColor redColor] forKeyPath:@"_placeholderLabel.textColor"];

    • 字典转模型
    +(instancetype) setModelWithDict:(NSDictionary*)dict{
    
        Class cls = [self class];
        id Model = [[self alloc]init];
    
        unsigned int count;
        //提取Class的property列表
        objc_property_t *property_t = class_copyPropertyList(cls, &count);
        //遍历列表,对每个property分别处理
        for (int i =0; i< count; i++) {
            objc_property_t property = property_t[i];
            NSString *key = [NSString stringWithUTF8String:property_getName(property)];
    
            id value = dict[key];
            if (!value) continue;
    
            //将字典中的值设置给模型
            [Model setValue:value forKeyPath:key];
        }
    
        free(property_t);
        return Model;
    }
    
    • 自动归档解档
    //获取所有属性
    - (NSArray *)propertyOfSelf {
        unsigned int count = 0;
        Ivar *ivarList = class_copyIvarList([self class], &count);
        NSMutableArray *propertyArr = [NSMutableArray array];
        for (int i = 0; i<count; i++) {
            //获取成员属性
            Ivar ivar = ivarList[i];
            //属性名
            NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];
             //去掉属性名前面的_
            NSString *key = [name substringFromIndex:1];
            [propertyArr addObject:key];
        }
        return [propertyArr copy];
    }
    
    //归档
    - (void)encodeWithCoder:(NSCoder *)aCoder {
        NSArray *propertyNames = [self propertyOfSelf];
        for (NSString *properName in propertyNames) {
            id value = [self valueForKey:properName];//KVC
            [aCoder encodeObject:value forKey:properName];
        }
    }
    
    //解档
    - (instancetype)initWithCoder:(NSCoder *)coder {
        self = [super init];
        if (self) {
            NSArray *propertyNames = [self propertyOfSelf];
            for (NSString *properName in propertyNames) {
                id value = [coder decodeObjectForKey:properName];
                [self setValue:value forKey:properName];//KVC
            }
        }
        return self;
    }
    
    • 利用消息转发机制解决方法找不到的异常问题
    // TODO: -----------------  Person类  -----------------
    @interface Person : NSObject
    - (void)run;
    - (void)test;
    - (void)other;
    @end
    
    @implementation Person
    - (void)run {
        NSLog(@"%s", __func__);
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
        // 可以调用的方法
        if ([self respondsToSelector:aSelector]) {
            return [super methodSignatureForSelector:aSelector];
        }
        // 无法找到的方法
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    - (void)forwardInvocation:(NSInvocation *)anInvocation {
        NSLog(@"找不到%@方法", NSStringFromSelector(anInvocation.selector));
    }
    
    @end
    
    // TODO: -----------------  main  -----------------
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
    
            Person *person = [[Person alloc] init];
            [person run];
            [person test];
            [person other];
        }
        return 0;
    }
    
    // 打印结果
    Demo[1234:567890] -[Person run]
    Demo[1234:567890] 找不到test方法
    Demo[1234:567890] 找不到other方法
    
    • 安全的JSON
    /*
    后台有时对空字段的默认处理就是返回一个null, OC解析出来的也就是NSNull, 当对NSNull发送消息时, 就会crash.
    因为JSON中只有数字, 字符串, 数组和字典四种类型, 所以只需要在触发消息转发时返回这四种类型中的某一种就可以解决了.
    */
    #define NSNullObjects @[@"",@0,@{},@[]]
    @implementation NSNull (SafeJson)
    
    - (id)forwardingTargetForSelector:(SEL)aSelector {
        
        for (id null in NSNullObjects) {
            if ([null respondsToSelector:aSelector]) {
                return null;
            }
        }
        return nil;
    }
    
    • 手动实现多继承
    @protocol FruitsProviderProtocol
    
    - (void)buyFruits:(NSString *)fruits;
    
    @end
    
    @interface FruitsProvider() <FruitsProviderProtocol>
    
    @end
    
    @implementation FruitsProvider
    
    - (void)buyFruits:(NSString *)fruits
    {
        NSLog(@"fruits: %@",fruits);
    }
    
    @end
    
    ------------------------------------------------------------------------
    
    @protocol RestaurantProviderProtocol
    
    - (void)order:(NSString *)orderName;
    
    @end
    
    @interface RestaurantProvider() <RestaurantProviderProtocol>
    
    @end
    
    @implementation RestaurantProvider
    
    - (void)order:(NSString *)orderName
    {
        NSLog(@"order: %@",orderName);
    }
    
    @end
    
    ------------------------------------------------------------------------
    
    // NSProxy
    //抽象的类,虚类,NSProxy没有构造方法,自己去提供构造方法,并且复写方法签名和消息转发两个方法。
    @interface TakeOutProxy : NSProxy <FruitsProviderProtocol,RestaurantProviderProtocol>
    
    + (instancetype)shareProxy;
    
    @end
    
    @interface TakeOutProxy()
    
    {
        NSMutableDictionary *_methodDict;
    }
    
    @end
    
    @implementation TakeOutProxy
    
    //多继承 继承两个类的方法和实现
    //解决方法1:通过把这两个类的对象传进来,通过这两个类分别调用两个方法实现。
    //解决方法2:通过协议的方式实现。
    //这两个方法本质上都是原来的类去实现的这个方法。
    //复写方法签名和消息转发两个方法
    
    //通过协议的方式
    
    + (instancetype)shareProxy
    {
        return [[TakeOutProxy alloc]init];
    }
    
    - (instancetype)init
    {
        _methodDict = [NSMutableDictionary dictionary];
        
        [self registerMethodTarget:[FruitsProvider new]];
        
        [self registerMethodTarget:[RestaurantProvider new]];
        
        return self;
    }
    
    //将两个类的方法遍历,存放在一个字典中
    - (void)registerMethodTarget:(id)target {
        
        unsigned int count = 0;
        
        //runtime 获取方法列表
        Method *method_list = class_copyMethodList([target class], &count);
        
        for (int i = 0; i<count; i++) {
            Method *method = method_list[i];
            SEL *sel = method_getName(method);
            [_methodDict setObject:target forKey:NSStringFromSelector(sel)];
        }
        
        
        //记得释放
        free(method_list);
        
    }
    
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
    {
        NSString *methodName = NSStringFromSelector(sel);
        
        id target = _methodDict[methodName];
        
        if (target && [target respondsToSelector:sel]) {
            return [target methodSignatureForSelector:sel];
        }else {
            return [super methodSignatureForSelector:sel];
        }
        
    }
    
    - (void)forwardInvocation:(NSInvocation *)invocation
    {
        SEL sel = [invocation selector];
        NSString *methodName = NSStringFromSelector(sel);
        id target = _methodDict[methodName];
        if (target && [target respondsToSelector:sel]) {
            [invocation invokeWithTarget:target];
        }else {
            [super forwardInvocation:invocation];
        }
    }
    
    @end
    
    • 交换控制器ViewDidAppear方法,跟踪每个控制器的展示次数
    #import <UIKit/UIKit.h>
    @interface UIViewController (Swizzling)
    @end
    
    #import "UIViewController+Swizzling.h"
    #import "NSObject+Swizzling.h"
    
    @implementation UIViewController (Swizzling)
    +(void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [UIViewController methodSwizzlingWithOriginalSelector:@selector(viewDidAppear:)
                                               bySwizzledSelector:@selector(my_ViewDidAppear:)];
    
        });
        }
    
    -(void) my_ViewDidAppear:(BOOL)animated{
        [self my_ViewDidAppear:animated];
        NSLog(@"===== %@ viewDidAppear=====",[self class]);
    }
    @end
    
    • 交换通过下标获取对象方法, 实现安全的数组
    @implementation NSArray (SafeArray)
    + (void)load {
        
        Method originMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
        Method swizzleMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(swizzleObjectAtIndex:));
        method_exchangeImplementations(originMethod, swizzleMethod);
    }
    
    - (id)swizzleObjectAtIndex:(NSUInteger)index {
        return index < self.count ? [self swizzleObjectAtIndex:index] : nil;
    }
    @end
    
    • 通用打点器
    + (NSDictionary<NSString *,id > *)observeItems {
        return @{@"UIControl" : @"sendAction:to:forEvent:",
                 
                 @"Person" : @"personFunc:",
                 
                 @"SecondViewController" : @[@"aFunc",
                                             @"aFunc:",
                                             @"aFunc1:",
                                             @"aFunc2:",
                                             @"aFunc3:",
                                             @"aFunc4:",
                                             @"aFunc:objcet:",
                                             @"aFunc:frame:size:point:object:",
                                             @"dasidsadbisaidsabidsbaibdsai"]};
    }//在这里声明需要打点的类和对应的方法, 多个方法放在一个数组中即可, 对于不响应的方法不会被打点
    
    + (void)object:(id)object willInvokeFunction:(NSString *)function withArguments:(NSArray *)arguments {
        //打点方法执行前会调用 参数分别是方法执行对象 方法名和方法参数
    }
    
    + (void)object:(id)object didInvokeFunction:(NSString *)function withArguments:(NSArray *)arguments {
        //打点方法执行后会调用 参数分别是方法执行对象 方法名和方法参数
    }
    
    /*
    1.根据observeItems中的信息拿到被打点类和对应方法method.
    2.替换method到forwardInvocation:, 同时添加一个newMethod指向method的原实现.
    3.在forwardInvocation:中解析invocation获取需要的信息进行打点.
    4.调用newMethod执行原来的方法实现
    */
    + (void)load {
        
        _nilObject = [NSObject new];
        [[self observeItems] enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull className, id _Nonnull selectors, BOOL * _Nonnull stop) {
            //遍历打点容器获取类名和打点方法进行打点
            Class cls = NSClassFromString(className);
            if ([selectors isKindOfClass:[NSString class]]) {
                [self replaceClass:cls function:selectors];
            } else if ([selectors isKindOfClass:[NSArray class]]) {
                
                for (NSString *selectorName in selectors) {
                    [self replaceClass:cls function:selectorName];
                }
            }
        }];
    }
    
    + (void)replaceClass:(Class)cls function:(NSString *)selectorName {
        
        SEL selector = NSSelectorFromString(selectorName);//被打点的方法名
        SEL forwardSelector = HHOriginSeletor(selectorName);//指向方法原实现的新方法名
        Method method = class_getInstanceMethod(cls, selector);//获取方法实现 下文使用
        if (method != nil) {//如果没有实现, 那就不用打点了
    
            IMP msgForwardIMP = _objc_msgForward;//消息转发IMP
            IMP originIMP = class_replaceMethod(cls, selector , msgForwardIMP, method_getTypeEncoding(method));//替换原方法实现到forwardInvocation:
            class_addMethod(cls, forwardSelector, originIMP, method_getTypeEncoding(method));//添加一个新的方法指向原来的方法实现
            class_replaceMethod(cls, @selector(forwardInvocation:), (IMP)HHForwardInvocation, "v@:@");//替换系统的forwardInvocation:实现指向自己的HHForwardInvocation实现, 在这里进行方法解析, 拿到信息后打点
        }
    }
    
    static void HHForwardInvocation(__unsafe_unretained id target, SEL selector, NSInvocation *invocation) {
        
        NSMutableArray *arguments = [NSMutableArray array];
        NSMethodSignature *methodSignature = [invocation methodSignature];
        for (NSUInteger i = 2; i < methodSignature.numberOfArguments; i++) {
            const char *argumentType = [methodSignature getArgumentTypeAtIndex:i];
            switch(argumentType[0] == 'r' ? argumentType[1] : argumentType[0]) {
                     //...各种参数类型解析 略
                    HH_FWD_ARG_CASE('c', char)
                    HH_FWD_ARG_CASE('C', unsigned char)
                    HH_FWD_ARG_CASE('s', short)
                    //...各种参数类型解析 略
                default: {
                    NSLog(@"error type %s", argumentType);
                }   break;
            }
        }
        NSString *selectorName = NSStringFromSelector(invocation.selector);
        [HHObserver object:target willInvokeFunction:selectorName withArguments:arguments];//拿到方法信息后向外传
        [invocation setSelector:HHOriginSeletor(selectorName)];
        [invocation invoke];//执行方法的原实现
        [HHObserver object:target didInvokeFunction:selectorName withArguments:arguments];//拿到方法信息后向外传
        
    }
    
    • 万能界面跳转
    @interface MyViewController : UIViewController
    // 注:根据下面的两个属性,可以从服务器获取对应的频道列表数据
    /** 频道ID */
    @property (nonatomic, copy) NSString *ID;
    /** 频道type */
    @property (nonatomic, copy) NSString *type;
    
    @end
    
    // 这个规则肯定事先跟服务端沟通好,跳转对应的界面需要对应的参数
    NSDictionary *userInfo = @{
                               @"class": @"MyViewController",
                               @"property": @{
                                            @"ID": @"123",
                                            @"type": @"12"
                                       }
                               };
    
    // 跳转界面
    - (void)push:(NSDictionary *)params
    {
        // 类名
        NSString *class =[NSString stringWithFormat:@"%@", params[@"class"]];
        const char *className = [class cStringUsingEncoding:NSASCIIStringEncoding];
        // 从一个字串返回一个类
        Class newClass = objc_getClass(className);
        if (!newClass)
        {
            // 创建一个类
            Class superClass = [UIViewController class];
            newClass = objc_allocateClassPair(superClass, className, 0);
            // 注册你创建的这个类
            objc_registerClassPair(newClass);
        }
        // 创建对象
        id instance = [[newClass alloc] init];
        // 对该对象赋值属性
        NSDictionary * propertys = params[@"property"];
        [propertys enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
            // 检测这个对象是否存在该属性
            if ([self checkIsExistPropertyWithInstance:instance verifyPropertyName:key]) {
                // 利用kvc赋值
                [instance setValue:obj forKey:key];
            }
        }];
        // 获取导航控制器
        UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
        UINavigationController *pushClassStance = (UINavigationController *)tabVC.viewControllers[tabVC.selectedIndex];
        // 跳转到对应的控制器
        [pushClassStance pushViewController:instance animated:YES];
    }
    
    // 检测对象是否存在该属性
    - (BOOL)checkIsExistPropertyWithInstance:(id)instance verifyPropertyName:(NSString *)verifyPropertyName
    {
        unsigned int outCount, i;
        // 获取对象里的属性列表
        objc_property_t * properties = class_copyPropertyList([instance
                                                               class], &outCount);
        for (i = 0; i < outCount; i++) {
            objc_property_t property =properties[i];
            //  属性名转成字符串
            NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
            // 判断该属性是否存在
            if ([propertyName isEqualToString:verifyPropertyName]) {
                free(properties);
                return YES;
            }
        }
        free(properties);
        return NO;
    }
    

    相关文章

      网友评论

          本文标题:iOS底层 -- Runtime之Runtime的API及相关应

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