美文网首页iOS 深入理解
RunTime 之常规操作

RunTime 之常规操作

作者: 進无尽 | 来源:发表于2018-03-06 10:01 被阅读9次

    前言

    有关Runtime的知识总结,我本来想集中写成一篇文章的,但是最后发现实在是太长,而且不利于阅读,最后分成了如下几篇:


    查看objc/runtime.h 里面的API,里面是关于下面这些关键字的各种操作方法(创建、添加、修改、销毁),本文就不一一列表具体API的意思,想了解的朋友可以自行查询。本文主要讲Runtime的一些常规操作。

    - Class
    - Ivar
    - property
    - protocol
    - SEL
    - IMP
    - Method
    

    RunTime 的常规操作?

    • NSString、Class、SEL之间的转化(反射机制)
    • 根据字符串动态生成一个UIViewController并跳转
    • 动态创建一个类、添加属性变量并对属性变量赋值,添加方法并调用新方法
    • 获取一个类的所有方法
    • 获取一个类的所有成员变量
    • 获取一个类的所有属性变量
    • 获取协议列表
    • 动态给一个类新增一个方法
    • 动态增加实例变量
    • 动态改变对象的某个变量值.
    • 动态添加属性(属性关联)
    • 交换一个类的两个方法的实现

    NSString、Class、SEL、Protocol之间的转化(反射机制)

    FOUNDATION_EXPORT NSString *NSStringFromSelector(SEL aSelector);
    FOUNDATION_EXPORT SEL NSSelectorFromString(NSString *aSelectorName);
    
    FOUNDATION_EXPORT NSString *NSStringFromClass(Class aClass);
    FOUNDATION_EXPORT Class _Nullable NSClassFromString(NSString *aClassName);
    
    FOUNDATION_EXPORT NSString *NSStringFromProtocol(Protocol *proto) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    FOUNDATION_EXPORT Protocol * _Nullable NSProtocolFromString(NSString *namestr) API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));
    

    根据字符串动态生成一个UIViewController并跳转

        Class cls=NSClassFromString(classNameArray[buttonIndex]);
        UIViewController  *viewController=[[cls alloc] init];
        viewController.hidesBottomBarWhenPushed = YES;
        [self.navigationController pushViewController:viewController animated:YES];
    

    动态创建一个类、添加属性变量并对属性变量赋值,添加方法并调用新方法

    我们常见的创建新的类都是通过新建类文件的方式,
    我们也可以通过 runtime 的方式动态创建一个类,下面是整个过程:

    • 创建一个集成NSObject的类 类名是MyClass并初始化;
    • 为这个类增加一个实例变量,通过KVC给这个实例变量赋值。
    • 为这个类增加一个方法。在这个方法中打印一些值。
    • 通过这个类的实例调用新增的方法。
    注意:调用的c语言的方法 所以不要使用 @"" 表示字符串 应该使用 ""
    Class MyClass = objc_allocateClassPair([NSObject class], "MyClass", 0);
    
    // 2、增加实例变量
    // 参数一、类名
    // 参数二、属性名称
    // 参数三、开辟字节长度
    // 参数四、对其方式
    // 参数五、参数类型 “@” 官方解释 An object (whether statically typed or typed id) (对象 静态类型或者id类型) 具体类型可参照 https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
    // return: BOOL 是否添加成功
    BOOL isSuccess = class_addIvar(MyClass, "test", sizeof(NSString *), 0, "@");
    
    // 三目运算符
    isSuccess?NSLog(@"添加变量成功"):NSLog(@"添加变量失败");
    
    // 3、增加方法
    
    class_addMethod(MyClass, @selector(addMethodForMyClass:), (IMP)addMethodForMyClass, "V@:");
    
    //注册这个类到runtime系统中就可以使用他了
    objc_registerClassPair(MyClass);
    
    //    id myObjc = [[MyClass alloc] init];
    //    NSLog(@"%@",myObjc);
    
    //    在OC中,我们对方法的调用都会被转换成内部的消息发送执行对objc_msgSend方法的调用,掌握好消息发送,可以让我们在编程中更方便灵活。
    
    // 上面的id myObjc = [[MyClass alloc] init]; 我们可以通过runtime 消息发送objc_msgSend去实现
    
    // 实现[MyClass alloc] 去开辟空间
    id myobjc = objc_msgSend(MyClass, @selector(alloc));
    myobjc = objc_msgSend(myobjc, @selector(init));
    
    NSLog(@"%@",myobjc);
    
    NSString *str = @"我是test";
    
    // 通过KVC的方式给myObj对象的test属性赋值
    [myobjc setValue:str forKey:@"test"];
    
    // 如果不调用- (void)addMethodForMyClass:(NSString *)string 这个方法,就不会调用static void addMethodForMyClass(id self, SEL _cmd, NSString *test) 函数
    [myobjc addMethodForMyClass:@"参数"];
    
    --------------------------------------------------
    
    //self和_cmd是必须的,在之后可以随意添加其他参数
    static void addMethodForMyClass(id self, SEL _cmd, NSString *test) {
        
        // 获取类中指定名称实例成员变量的信息
        Ivar ivar = class_getInstanceVariable([self class], "test");
        
    // 获取整个成员变量列表
    //   Ivar * class_copyIvarList ( Class cls, unsigned intint * outCount );
    // 获取类中指定名称实例成员变量的信息
    //   Ivar class_getInstanceVariable ( Class cls, const charchar *name );
    // 获取类成员变量的信息
    //   Ivar class_getClassVariable ( Class cls, const charchar *name );
        
        
        // 返回名为test的ivar变量的值
        id obj = object_getIvar(self, ivar);
        
        NSLog(@"%@",obj);
        NSLog(@"addMethodForMyClass:参数:%@",test);
        NSLog(@"ClassName:%@",NSStringFromClass([self class]));
        
    }
    
    --------------------------------------------------
    //这个方法实际上没有被调用,但是必须实现否则不会调用下面的方法
    - (void)addMethodForMyClass:(NSString *)string {
    
    }
    

    获取一个类的所有方法

    获取类的实例化方法 - 减号方法,包括getter, setter, 分类中的方法等。
    使用 [Person fetchInstanceMethodList]获取

    #import <objc/runtime.h>
    @implementation NSObject (Runtime)
    
    + (NSArray *)fetchInstanceMethodList
    {
        unsigned int count = 0;
        Method *methodList = class_copyMethodList(self, &count);
        
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (unsigned int i = 0; i < count; i++)
        {
            Method method = methodList[i];
            SEL methodName = method_getName(method);
            [mutableList addObject:NSStringFromSelector(methodName)];
        }
        free(methodList);
        return [NSArray arrayWithArray:mutableList];
    }
    

    获取类的类方法 + 加号方法

    使用[Person fetchClassMethodList]获取

    + (NSArray *)fetchClassMethodList
    {
        unsigned int count = 0;
        Method *methodList = class_copyMethodList(object_getClass(self), &count);
        
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (unsigned int i = 0; i < count; i++)
        {
            Method method = methodList[i];
            SEL methodName = method_getName(method);
            [mutableList addObject:NSStringFromSelector(methodName)];
        }
        free(methodList);
        return [NSArray arrayWithArray:mutableList];
    }
    

    获取一个类的所有成员变量

    class_copyIvarList能够获取一个含有类中所有成员变量的列表,列表中包括属性变量和实例变量。需要注意的是,如果如本例中,ivar_getName()获取的值前面有_ 是属性变量,前面没有下划线的是实例变量(包括隐藏起来的私有变量和.h 中公开的实例变量)。
    (因为@property默认生成了"_age",而@synthesize默认是执行了"@synthesize age = _age;"),

    - (void) getAllVariable {
        unsigned int count = 0;
        //获取类的一个包含所有变量的列表,IVar是runtime声明的一个宏,是实例变量的意思.
        Ivar *allVariables = class_copyIvarList([self class], &count);
        for(int i = 0;i<count;i++)
        {
            //遍历每一个变量,包括名称和类型(此处没有星号"*")
            Ivar ivar = allVariables[i];
            const char *Variablename = ivar_getName(ivar); //获取成员变量名称
            const char *VariableType = ivar_getTypeEncoding(ivar); //获取成员变量类型
            NSLog(@"(Name: %s) ----- (Type:%s)",Variablename,VariableType);
        }
        free(allVariables);
    }
    

    获取一个类的所有属性变量

    里面不光有自己定义的属性变量,还有一些系统自带的属性变量

    - (void) getAllPropertyList {
        unsigned int count = 0;
        objc_property_t *allVariables = class_copyPropertyList([self class], &count);
        for(int i = 0;i<count;i++)
        {
            //遍历每一个变量,包括名称和类型(此处没有星号"*")
            objc_property_t property = allVariables[i];
            const char *Variablename = property_getName(property); //获取属性变量名称
            const char *VariableType = property_getAttributes(property); //获取属性变量的属性
            NSLog(@"(Name: %s) ----- (Type:%s)",Variablename,VariableType);
        }
        free(allVariables);
    }
    

    获取协议列表

    下面是获取类所遵循协议列表的方法:

    + (NSArray *)fetchProtocolList
    {
        unsigned int count = 0;
        __unsafe_unretained Protocol **protocolList = class_copyProtocolList(self, &count);
    
        NSMutableArray *mutableList = [NSMutableArray arrayWithCapacity:count];
        for (unsigned int i = 0; i < count; i++ )
        {
            Protocol *protocol = protocolList[i];
            const char *protocolName = protocol_getName(protocol);
            [mutableList addObject:[NSString stringWithUTF8String:protocolName]];
        }
        return [NSArray arrayWithArray:mutableList];
    }
    

    动态给一个类新增一个方法

    /* 动态添加方法:
     第一个参数表示Class cls 类型;
     第二个参数表示待调用的方法名称;
     第三个参数(IMP)myAddingFunction,IMP一个函数指针,这里表示指定具体实现方法myAddingFunction;
     第四个参数表方法的参数,0代表没有参数;
     */
    class_addMethod([person class], @selector(myAddingFunction), (IMP)myAddingFunction, 0);
    //调用方法 【如果使用[per NewMethod]调用方法,在ARC下会报“no visible @interface"错误】
    [person performSelector:@selector(myAddingFunction)];
    
    //具体的实现(方法的内部都默认包含两个参数Class类和SEL方法,被称为隐式参数。)
    int myAddingFunction(id self, SEL _cmd){
        NSLog(@"已新增方法:NewMethod");
        return 1;
    }
    

    虽然会报警告,但是那只是编译器的警告,不会影响实际的运行结果。

    动态增加实例变量

    参数一、类名
    参数二、属性名称
    参数三、开辟字节长度
    参数四、对其方式
    参数五、参数类型 “@” 官方解释 An object (whether statically typed or typed id) (
                  对象 静态类型或者id类型) 具体类型可参照
    

    官方文档
    return: BOOL 是否添加成功

    BOOL isSuccess = class_addIvar(MyClass, "test", sizeof(NSString *), 0, "@");
    // 三目运算符
    isSuccess?NSLog(@"添加变量成功"):NSLog(@"添加变量失败");
    

    动态改变对象的某个变量值.

    可以是属性变量(注意加 _ ),也可以是私有的全局变量.

    Person *person = [[Person alloc]init];
    person.name = @"asdas";
    NSLog(@"%@",person.name);
    [self editObjectIvar:person :@"_name" :@"新属性"];
    NSLog(@"new :%@",person.name);
    
    - (void)editObjectIvar :(id)object
                           :(NSString *)IvarStr
                           :(NSString *)IvarNewStr
    
    {
        unsigned int count = 0;
        Ivar *allList = class_copyIvarList([object class], &count);
        for(int i = 0;i<count;i++)
        {
            //遍历每一个变量,包括名称和类型(此处没有星号"*")
            Ivar ivar = allList[i];
            const char *Variablename = ivar_getName(ivar); //获取成员变量名称
            if ([[NSString stringWithUTF8String:Variablename] isEqualToString:IvarStr]) {
                object_setIvar(object, ivar, IvarNewStr); //name属性Tom被强制改为Mike。
            }
        }
        free(allList);
    }
    

    动态添加属性(属性关联)

    方式1:
    #import "Person.h"
    @interface Person (newProperty)
    @property (nonatomic, copy) NSString *categroyProperty;
    @end
    
    #import "Person+newProperty.h"
    #import <objc/runtime.h>
    @implementation Person (newProperty)
    
    - (void)setCategroyProperty:(NSString *)categroyProperty
    {
        objc_setAssociatedObject(self, @selector(categroyProperty), categroyProperty, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    - (NSString *)categroyProperty
    {
        return objc_getAssociatedObject(self, @selector(categroyProperty));
    }
    @end
    
    方式2:
    
    static char mykey;
    
    objc_setAssociatedObject(self, &mykey,@"test", OBJC_ASSOCIATION_COPY_NONATOMIC);
    NSLog(@"LL: %@",objc_getAssociatedObject(self, &mykey));
    
    ***********************************  使用
    
     #import "Person+newProperty.h"
    
     Person *person = [[Person alloc]init];
     person.name = @"asdas";
     person.categroyProperty = @"厉害啊";
    
    NSLog(@"%@",person.categroyProperty);
    

    特别说明下:
    方式1中的以类别的形式为一个类增加属性,在调用环境中必须满足两个条件,否则会报错。

    • 在调用的类中只能出现 #import "Person+newProperty.h",不能出现 #import "Person.h",否则不能打. 调用新属性。
    • Person 文件中 不能出现 #import "Person+newProperty.h",否则也会报错。

    方式2:中值得注意的是关联的新属性是任意对象类型,可以是一个 UIView,也可以是一个字符串。

    交换一个类的两个方法的实现

    如果将originMethod与currentMethod的方法实现进行交换的话,
    调用originMethod时就会执行currentMethod的内容。

    交换俩个方法的实现:
    
    Method method1 = class_getInstanceMethod([person class], @selector(func1));
    Method method2 = class_getInstanceMethod([person class], @selector(func2));
    
    //交换方法
    method_exchangeImplementations(method1, method2);
    [person func1]; 
    
    结果在预料之中

    封装后:

    交换两个实例方法( - 方法)
    + (void)swapMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
    {
        Method firstMethod = class_getInstanceMethod(self, originMethod);
        Method secondMethod = class_getInstanceMethod(self, currentMethod);
        method_exchangeImplementations(firstMethod, secondMethod);
    }
    
    交换两个类方法( + 方法)
    + (void)swapClassMethod:(SEL)originMethod currentMethod:(SEL)currentMethod;
    {
        Method firstMethod = class_getClassMethod(self, originMethod);
        Method secondMethod = class_getClassMethod(self, currentMethod);
        method_exchangeImplementations(firstMethod, secondMethod);
    }
    

    交换方法的使用场景: 项目中的某个功能,在项目中需要多次被引用,当项目的需求发生改变时,要使用另一种功能代替这个功能,且要求不改变旧的项目(也就是不改变原来方法实现的前提下)。那么 ,

    • 我们可以在分类中,再写一个新的方法(符合新的需求的方法)
    • 然后在分类中的+load方法里面(因为load方法会在程序运行前加载一次)交换两个方法的实现。
    • 这样,在不改变项目的代码,而只是增加了新的代码的情况下,就完成了项目的改进,很好地体现了该项目的封装性与利用率。

    参考文章:
    iOS开发 -- Runtime 的几个小例子
    Runtime的运用和减少应用崩溃
    ios开发 -- runtime动态创建类、添加方法、添加实例变量
    runtime——消息机制
    【OC刨根问底】Runtime简单粗暴理解
    OC最实用的runtime总结,面试、工作你看我就足够了!
    iOS开发之Runtime常用示例总结

    相关文章

      网友评论

        本文标题:RunTime 之常规操作

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