美文网首页
利用runtime为系统类添加属性、成员变量.......

利用runtime为系统类添加属性、成员变量.......

作者: CoderZS | 来源:发表于2017-03-17 22:51 被阅读0次

    1️⃣runtime介绍:

    runtime是一套比较底层的纯C语言API, 包含了很多底层的C语言API。在我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码.

    比如说,下面一个创建对象的方法 :

    1.[[ZSPerson alloc] init]

    2.runtime :objc_msgSend(objc_msgSend(“ZSPerson” , “alloc”), “init”)

    2️⃣runtime 用来干什么呢??用在那些地方呢?怎么用呢?

    runtime是属于OC的底层, 可以进行一些非常底层的操作(用OC是无法现实的, 不好实现)

    在程序运行过程中, 动态创建一个类(比如KVO的底层实现)

    在程序运行过程中, 动态地为某个类添加属性\方法, 修改属性值\方法

    遍历一个类的所有成员变量(属性)\所有方法

    例如:我们需要对一个类的属性进行归档解档的时候属性特别的多,这时候,我们就会写很多对应的代码,但是如果使用了runtime就可以动态设置!

    例如,ZSPerson.h的文件如下所示

    @interfaceZSPerson:NSObject

    @property(nonatomic,assign)intage;

    @property(nonatomic,assign)intheight;

    @property(nonatomic,copy)NSString*name;

    @property(nonatomic,assign)intage2;

    @property(nonatomic,assign)intheight2;

    @property(nonatomic,assign)intage3;

    @property(nonatomic,assign)intheight3;

    @property(nonatomic,assign)intage4;

    @property(nonatomic,assign)intheight4;

    @end

    而ZSPerson.m实现文件的内容如下

    #import"ZSPerson.h"

    @implementationZSPerson

    (void)encodeWithCoder:(NSCoder )encoder 

    {

    unsignedintcount =0;

    Ivar ivars = class_copyIvarList([ZSPerson class], &count);

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

    // 取出i位置对应的成员变量

    Ivar ivar = ivars[i];

    // 查看成员变量

    constchar*name = ivar_getName(ivar);

    // 归档

    NSString*key = [NSStringstringWithUTF8String:name];

    idvalue = [selfvalueForKey:key]; 

      [encoder encodeObject:value forKey:key]; 

      }

    free(ivars);

        } 

      (id)initWithCoder:(NSCoder *)decoder 

      {

    if(self= [super init]) {

    unsignedintcount =0;

     Ivar *ivars = class_copyIvarList([ZSPerson class], &count);

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

    // 取出i位置对应的成员变量

    Ivar ivar = ivars[i];

    // 查看成员变量

    const char*name = ivar_getName(ivar);

    // 归档

    NSString*key = [NSStringstringWithUTF8String:name];

    id value = [decoder decodeObjectForKey:key];

    // 设置到成员变量身上

    [selfsetValue:value forKey:key];

    free(ivars);

      }

    returnself; 

      }

    @end

    这样我们可以看到归档和解档的案例其实是runtime写下的

    学习,runtime机制首先要了解下面几个问题

    1.相关的头文件和函数

    a> 头文件

    利用头文件,我们可以查看到runtime中的各个方法!

    b> 相关应用

    NSCoding(归档和解档, 利用runtime遍历模型对象的所有属性)

    字典 –> 模型 (利用runtime遍历模型对象的所有属性, 根据属性名从字典中取出对应的值, 设置到模型的属性上)

    KVO(利用runtime动态产生一个类)

    用于封装框架(想怎么改就怎么改)

    这就是我们runtime机制的只要运用方向

    c> 相关函数

    objc_msgSend : 给对象发送消息

    class_copyMethodList : 遍历某个类所有的方法

    class_copyIvarList : 遍历某个类所有的成员变量

    class_…..

    这是我们学习runtime必须知道的函数!

    2.必备常识

    a> Ivar : 成员变量

    b> Method : 成员方法

    从上面例子中我们看到我们定义的成员变量,如果要是动态创建方法,可以使用Method。

    3️⃣接下来我们进行项目实战

    首先给UITableViewCell创建一个分类RightDownPlugin

    .h文件中

    #import<UIKit,UIKit.h>

    @interfaceUITableViewCell(RightDownPlugin)

    @property(nonatomic,strong)UIImageView*statusImgV;//状态图@property(nonatomic,strong)UILabel*statusLab;//状态label

    @end

    .m文件

    #import"UITableViewCell+RightDownPlugin.h"

    #include <objc/runtime.h>

    @implementationUITableViewCell(RightDownPlugin)

    //定义常量 必须是C语言字符串 因为runtime是C语言API,

    staticchar*statusImgKey ="statusImgKey";

    staticchar*statusLabKey ="statusLabKey";

    /* 

    OBJC_ASSOCIATION_ASSIGN;            //assign策略

    OBJC_ASSOCIATION_COPY_NONATOMIC;    //copy策略

    OBJC_ASSOCIATION_RETAIN_NONATOMIC;  // retain策略

    OBJC_ASSOCIATION_RETAIN;

    OBJC_ASSOCIATION_COPY;

    *//*

    id object 给哪个对象的属性赋值

    const void *key 属性对应的key

    id value  设置属性值为value

    objc_AssociationPolicy policy  使用的策略,是一个枚举值,和copy,retain,assign是一样的,手机开发一般都选择nonatomic

    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);

    */// 然后就需要自定义set和get方法了,因为category默认是不能添加属性的,

    - (void)setStatusImgV:(UIImageView*)statusImgV{    objc_setAssociatedObject(self,statusImgKey,statusImgV,OBJC_ASSOCIATION_RETAIN);

    }

    - (UIImageView*)statusImgV{

    return objc_getAssociatedObject(self, statusImgKey);

    }

    // Lab

    - (void)setStatusLab:(UILabel*)statusLab{    objc_setAssociatedObject(self,statusLabKey,statusLab,OBJC_ASSOCIATION_RETAIN);

    }

    - (UILabel*)statusLab{

    return objc_getAssociatedObject(self, statusLabKey);

    }

    @end

    runtime常见的用法总结

    1.runtime动态添加属性

    需求:给NSString类添加两个属性:name和count

    NSString+String.h中

    #import<Foundation/Foundation.h>

    /**

    *  @property在分类里添加一个属性 不会有setter getter方法 只声明了一个变量 而且 这样声明 这个属性和这个类没有什么关系 */

    @interface NSString (String)

    @property (copy, nonatomic, nullable) NSString *name;

    @property (copy, nonatomic, nullable) NSString *count;

    @end

    NSString+String.m中

    #import "NSString+String.h"

    #import<objc/message>

    /**

    *  动态添加属性的本质是 指向外部已经存在的一个属性 而不是去在对象中创建一个属性

    */

    @implementation NSString (String)

    static NSString *_name;

    //在分类里声明属性 需要自己写set方法和get方法

    - (void) setName:(NSString *)name

    {

    _name = name;

    }

    - (NSString *) name

    {

    return _name;

    }

    //添加属性 应该是与对象有关

    - (void) setCount:(NSString *)count

    {

    //set方法里设置关联

    //Associated 关联 联系

    //跟某个对象产生关联,添加属性

    /**

    * id obj 给哪个对象添加属性(产生关联)

    * const void *key 属性名 (根据key获取关联的对象) void * 相当于 id 万能指针 传c或者oc的都可以

    * id value 要关联的值

    * objc_AssociationPolicy policy 策略 宏对应assign retain copy (因为weak没有用 外面赋值完马上就会被销毁 所以没有weak)

    */

    objc_setAssociatedObject(self, @"count", count, OBJC_ASSOCIATION_ASSIGN);

    }

    - (NSString *) count

    {

    //get方法里获取关联

    return [NSString stringWithFormat:@"%ld",[objc_getAssociatedObject(self, @"count") length]];

    }

    @end

    2.runtime动态加载方法

    ViewController中

    #import "ViewController.h"

    #import "Person.h"

    @interface ViewController ()

    @end

    @implementation ViewController

    - (void) viewDidLoad{    [super viewDidLoad]; 

      //performSelector:动态添加方法   

    Person *p0 = [[Person alloc] init];  

    [p0 performSelector:@selector(eat)];

    //动态添加方法 但是如果没有实现该方法 还是会崩溃 

    [p0 performSelector:@selector(drink:) withObject:@"juice"];

    //动态添加方法 但是如果没有实现该方法 还是会崩溃

    }

    Person.m中

    #import "Person.h"

    #import<objc/message.h>

    //默认一个方法都有两个参数:self 和_cmd  self是方法的调用者 _cmd就是调用方法的编号(方法名) 这两个参数为隐式参数 但是如果调用的是c的函数 则需要写出来

    @implementation Person

    //定义函数 该函数名是啥都可以

    void eat(id self,SEL _cmd)

    {

    //无返回值 两个参数 void(id,SEL) v@:

    NSLog(@"%@调用了%@方法",[self class],NSStringFromSelector(_cmd));//SEL本身没发打印 只能打印方法名

    }

    void drink(id self,SEL _cmd,id param1)

    {

    //v void    @ 对象    : 方法编号

    NSLog(@"%@调用了%@方法 传递参数:%@",[self class],NSStringFromSelector(_cmd),param1);//SEL本身没发打印 只能打印方法名

    }

    //1.动态添加方法 首先要实现resolveInstanceMethod:方法或resolveClassMethod:方法

    //前者对应实例方法 后者对应类方法

    //这两个方法的作用是要知道哪个方法没有被实现

    //这两个方法是在当该类的某个方法没有实现,但是又被外界调用了的时候调用 (及:外界试用performSelector:调用了该类中某个没有实现的方法)

    //sel参数为没有被实现的这个方法

    + (BOOL) resolveInstanceMethod:(SEL)sel

    {

    //打印该方法名

    //    NSLog(@"%@",NSStringFromSelector(sel));

    //动态添加方法

    if ([NSStringFromSelector(sel) isEqualToString:@"eat"])//sel == @selector(eat)也可以 但是会报警

    {

    /**

    *  Class 给哪个类添加方法

    *  sel 要添加的方法编号(方法名)

    *  IMP 方法的实现 ———— 函数的入口(函数的指针 函数名 是啥都可以 不一定和sel相同)

    *  types 方法的类型 编码格式 (类型c语言的字符串) (函数的类型:返回值类型 参数类型 直接查文档 文档有表格)

    */

    class_addMethod([self class], sel, (IMP)eat, "v@:");

    //处理完了要返回YES

    //        return YES;

    }

    else if ([NSStringFromSelector(sel) isEqualToString:@"drink:"])//要加冒号

    {

    class_addMethod([self class], sel, (IMP)drink, "v@:@");

    //        return YES;

    }

    //由于不知道返回的YES还是NO 所以:

    return [super resolveInstanceMethod:sel];

    }

    + (BOOL) resolveClassMethod:(SEL)sel

    {

    return [super resolveClassMethod:sel];

    }

    @end

    3.runtime交换方法(ios黑魔法)

    ViewController.m中

    #import "ViewController.h"

    #import<objc/message.h>

    #import "Person.h"

    //#import "UIImage+image.h"//交换方法时候不用导入也可以 因为交换写在类+(void)load里

    @interface ViewController ()

    @end

    @implementation ViewController

    - (void) viewDidLoad{    [super viewDidLoad]; 

      /** 

      *  交换方法  

    *///  

    UIImage *image = [UIImage ov_imageNamed:@"123"];  

    UIImage *image1 = [UIImage imageNamed:@"123"];  

    UIImage *image2 = [UIImage imageNamed:@"123"];

      //用imageNamed加载图片,并不知道图片是否加载成功  

    //需求:在以后调用imageNamed的时候,要知道图片是否加载成功  

    //交换方法的实现 (把imageNamed:方法和ov_imageNamed:方法交换 及 调用imageNamed就是调用ov_imageNamed)

    }

    @end

    UIImage+image.m中

    #import"UIImage+image.h"

    #import<objc/message.h>

    @implementation UIImage (image)

    //加载这个分类的时候调用

    + (void) load

    {

    NSLog(@"%s",__func__);

    //方法都定义在类里面 所以 交换对象方法也用class_开头

    /**

    *  class_getMethodImplementation 获取类方法的实现

    *

    *  Class 获取哪个类的方法

    *  SEL 获取哪个方法

    *  class_getMethodImplementation(<#__unsafe_unretained Class cls#>, <#SEL name#>)

    */

    /**

    *  class_getInstanceMethod 获取对象方法

    *

    *  Class 获取哪个类的方法

    *  SEL 获取哪个方法

    *

    *  class_getInstanceMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)

    */

    /**

    *  class_getClassMethod 获取类方法

    *

    *  class_getClassMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>)

    */

    /**

    *  method_exchangeImplementations交换方法

    *

    *  Method m1  要被替换的方法

    *  Method m2  要替换Method m1的方法

    *  method_exchangeImplementations(<#Method m1#>, <#Method m2#>)

    */

    //交换方法的实现

    //1.拿到两个方法

    Method imageNamedMethod = class_getClassMethod([self class], @selector(imageNamed:));

    Method ov_imageNamedMethod = class_getClassMethod([self class], @selector(ov_imageNamed:));

    //2.交换

    method_exchangeImplementations(imageNamedMethod, ov_imageNamedMethod);

    }

    /**

    *  分类没有父类 没有super

    */

    //+ (nullable __kindof UIImage *) imageNamed:(nonnull NSString *)imageName

    //{

    //    return nil;

    //}

    /**

    *  用其他方法做 这个方法不好的原因是 1.导入头文件太蛋疼 2.团队其他人可能不知道

    */

    + (nullable __kindof UIImage *) ov_imageNamed:(nonnull NSString *)imageName

    {

    //1.加载图片功能

    //    UIImage *image = [UIImage imageNamed:imageName];//由于使用了方法交换 所以这里再调用该方法就会造成死循环

    UIImage *image = [UIImage ov_imageNamed:imageName];//此处直接调用方法本身即可

    NSLog(@"%s %d",__func__,__LINE__);

    //2.判断返回是否为空功能

    if (!image)

    {

    //NSException 为抛异常(强制崩溃)

    //        NSException *e = [NSException

    //                          exceptionWithName: @"异常情况"

    //                          reason: @"图片为空"

    //                          userInfo: nil];

    //        @throw e;

    }

    else

    {

    }

    return image;

    }

    @end

    相关文章

      网友评论

          本文标题:利用runtime为系统类添加属性、成员变量.......

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