iOS Runtime

首先来点都有的介绍:
RunTime简称运行时。就是系统在运行的时候的一些机制,其中最主要的是消息机制。对于C语言,函数的调用在编译的时候会决定调用哪个函数。编译完成之后直接顺序执行,无任何二义性。OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用(在运行时修改类的具体实现,包括类中所有的私有属性、方法)。
下面是常用到Runtime的几种功能介绍:变量、属性、方法、关联对象
直接使用代码(里边有详细介绍)
记得首先头文件导入#import <objc/runtime.h>

-(void)clickButn
{
    //1.遍历成员变量和变量类型
    [self varNameAndType];

    //2.获取类的所有方法
    [self getAllMethod];

    //3.改变person的name变量属性
    [self changeVarValue];


    //4.为person类添加一个新方法
    [self addSelAction];
    [self getAllMethod];//打印方法名

    //5.交换person类的2个方法的功能
    [self exchangeMethos];

    //6.为person的category类增加一个新属性:(对象的关联方法)
    [self addVarForCategory];
}

//6.为person的category类增加一个新属性:(对象的关联方法)
-(void)addVarForCategory
{
    per.height = 12;           //给新属性height赋值
    NSLog(@"height:%f",[per height]); //访问新属性值

    /*(下边有实现方法,不使用runtime,访问变量直接编译不通过,因为类别不能添加变量和属性)
     分析:可以看到分类的新属性可以在per对象中对新属性height进行访问赋值。

     获取到person类属性时,依然没有height的存在,但是却有height和setHeight这两个方法;因为在分类中,即使使用@property定义了,也只是生成set+get方法,而不会生成_变量名,分类中是不允许定义变量的。

     使用runtime中objc_setAssociatedObject()和objc_getAssociatedObject()方法,本质上只是为对象per添加了对height的属性关联,但是达到了新属性的作用;

     使用场景:假设imageCategory是UIImage类的分类,在实际开发中,我们使用UIImage下载图片或者操作过程需要增加一个URL保存一段地址,以备后期使用。这时可以尝试在分类中动态添加新属性MyURL进行存储。

     */
}

//5.交换person类的2个方法的功能
-(void)exchangeMethos
{
    Method method1 = class_getInstanceMethod([Person class], @selector(func1));
    Method method2 = class_getInstanceMethod([Person class], @selector(func2));
    //交换方法
    method_exchangeImplementations(method1, method2);

    [per func1];

    /* 打印数据:
     2017-05-22 17:52:01.784 IOS_Dev[7025:213975] 执行func2方法。
     *
     * 交换方法的使用场景:项目中的某个功能,在项目中需要多次被引用,当项目的需求发生改变时,要使用另一种功能代替这个功能,且要求不改变旧的项目(也就是不改变原来方法实现的前提下)。那么,我们可以在分类中,再写一个新的方法(符合新的需求的方法),然后交换两个方法的实现。这样,在不改变项目的代码,而只是增加了新的代码的情况下,就完成了项目的改进,很好地体现了该项目的封装性与利用率。

     * 注:交换两个方法的实现一般写在类的load方法里面,因为load方法会在程序运行前加载一次。
     */
}



//4.为person类添加一个新方法
-(void)addSelAction
{
    /* 动态添加方法:
     第一个参数表示Class cls 类型;
     第二个参数表示待调用的方法名称;
     第三个参数(IMP)myAddingFunction,IMP一个函数指针,这里表示指定具体实现方法myAddingFunction;
     第四个参数表方法的参数,0代表没有参数;
     */

    /*class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)*/
    class_addMethod([Person class], @selector(NewMethod), (IMP)myAddFunction, 0);

    //调用方法 【如果使用[per NewMethod]调用方法在ARC下会报“no visible @interface”错误】
    [per performSelector:@selector(NewMethod)];

    /* 打印数据
     2017-05-22 17:44:09.435 IOS_Dev[6916:210127] 已新增方法:NewMethod
     2017-05-22 17:44:12.538 IOS_Dev[6916:210127] (Method:NewMethod)
     2017-05-22 17:44:12.538 IOS_Dev[6916:210127] (Method:setAge:)
     2017-05-22 17:44:12.538 IOS_Dev[6916:210127] (Method:age)
     2017-05-22 17:44:12.539 IOS_Dev[6916:210127] (Method:func1)
     2017-05-22 17:44:12.539 IOS_Dev[6916:210127] (Method:func2)
     2017-05-22 17:44:12.539 IOS_Dev[6916:210127] (Method:.cxx_destruct)
     2017-05-22 17:44:12.539 IOS_Dev[6916:210127] (Method:description)
     2017-05-22 17:44:12.539 IOS_Dev[6916:210127] (Method:init)
     *
     *
     */
}

int myAddFunction(id self,SEL _cmd)
{
    NSLog(@"已新增方法:NewMethod");
    [self func1];//调用person自己的方法
    return 1;
}


//3.改变person的name变量属性
-(void)changeVarValue
{
    NSLog(@"改变之前person:%@",per);

    unsigned int count = 0;
    Ivar *allList = class_copyIvarList([Person class], &count);
    Ivar ivar = allList[0];
    /*object_setIvar(<#id obj#>, <#Ivar ivar#>, <#id value#>)*/
    object_setIvar(per, ivar, @"Mike");

    NSLog(@"改变之后person:%@",per);
    /*
     *打印数据:
     2017-05-22 17:12:18.787 IOS_Dev[6578:198946] 改变之前person:name:Tom age:12
     2017-05-22 17:12:18.788 IOS_Dev[6578:198946] 改变之后person:name:Mike age:12
     *
     *
     */
}

//2.获取类的所有方法
-(void)getAllMethod
{
    unsigned int count = 0;

    Method *allMethods = class_copyMethodList([Person class], &count);

    for (int i = 0; i < count; i ++) {

        //Method,为runtime声明的一个宏,表示对一个方法的描述
        Method method = allMethods[i];

        //获取SEL:SEL类型,即获取方法选择器@selector()
        SEL sel = method_getName(method);

        //得到sel的方法名:以字符串格式获取sel的name,也即@selector()中的方法名称
        const char *methodName = sel_getName(sel);
        NSLog(@"(Method:%s)",methodName);
    }

    /*
     * 输出结果为:
     2017-05-22 16:58:12.754 IOS_Dev[6395:191855] (Name: name) ----- (Type:@"NSString")
     2017-05-22 16:58:12.754 IOS_Dev[6395:191855] (Name: _age) ----- (Type:i)
     2017-05-22 16:58:12.754 IOS_Dev[6395:191855] (Method:setAge:)
     2017-05-22 16:58:12.754 IOS_Dev[6395:191855] (Method:age)
     2017-05-22 16:58:12.755 IOS_Dev[6395:191855] (Method:func1)
     2017-05-22 16:58:12.755 IOS_Dev[6395:191855] (Method:func2)
     2017-05-22 16:58:12.755 IOS_Dev[6395:191855] (Method:.cxx_destruct)
     2017-05-22 16:58:12.755 IOS_Dev[6395:191855] (Method:description)
     2017-05-22 16:58:12.755 IOS_Dev[6395:191855] (Method:init)
     * 控制台输出了包括set和get等方法名称。【备注:.cxx_destruct方法是关于系统自动内存释放工作的一个隐藏的函数,
     * 当ARC下,且本类拥有实例变量时,才会出现;】
     * 
     * 分析:typedef struct objc_method *Method;
     *
     truct objc_method {
     SEL method_name                                          OBJC2_UNAVAILABLE;
     char *method_types                                       OBJC2_UNAVAILABLE;
     IMP method_imp                                           OBJC2_UNAVAILABLE;
     }
     * method_name :方法选择器@selector(),类型为SEL。 相同名字的方法下,即使在不同类中定义,它们的方法选择器也相同。
     * method_types:方法类型,是个char指针,存储着方法的参数类型和返回值类型。
     * 指向方法的具体实现的指针,数据类型为IMP,本质上是一个函数指针。
     *
     * SEL:数据类型,表示方法选择器,可以理解为对方法的一种包装。在每个方法都有一个与之对应的SEL类型的数据,根据一个SEL数据“@selector(方法名)”就可以找到对应的方法地址,进而调用方法。
     */
}

//1.遍历类中成员变量和变量类型
-(void)varNameAndType
{
    unsigned int count = 0;

    Ivar *allVariables = class_copyIvarList([Person 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);
    }

    /*
     * typedef struct objc_ivar *Ivar;
     * 分析:Ivar,一个指向objc_ivar结构体指针,包含了变量名、变量类型等信息。
     * 1.class_copyIvarList能够获取一个含有类中所有成员变量的列表,列表中包括属性变量和实例变量
     * 2.如果单单需要获取属性列表的话,可以使用函数:class_copyPropertyList();只是返回的属性变量仅仅是“age”,做为实例变量的name是不被获取的。
     *   而class_copyIvarList()函数则能够返回实例变量和属性变量的所有成员变量。
     */

}

//6.runtime在category添加属性实现:
#import "Person.h"
@interface Person (PersonCategory)
@property (nonatomic,assign)float height; //新属性
@end

#import "Person+PersonCategory.h"
#import <objc/runtime.h> //runtime API的使用需要包含此头文件
const char * str = "myKey"; //做为key,字符常量 必须是C语言字符串;
@implementation Person (PersonCategory)
-(void)setHeight:(float)height{
    NSNumber *num = [NSNumber numberWithFloat:height];
    /*(关联对象(将值value与对象object关联起来))
     第一个参数是需要添加属性的对象;
     第二个参数是属性的key;
     第三个参数是属性的值,类型必须为id,所以此处height先转为NSNumber类型;
     第四个参数是使用策略,是一个枚举值,类似@property属性创建时设置的关键字,可从命名看出各枚举的意义;
     objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
     */
    objc_setAssociatedObject(self, str, num, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//提取属性的值:(利用参数key将对象中存储的对应值取出)
-(float)height{
    NSNumber *number = objc_getAssociatedObject(self, str);
    return [number floatValue];
}
@end

猜你喜欢

转载自blog.csdn.net/lining1041204250/article/details/76532354