• runtime之玩转成员变量


    前言:

      不铺垫那么多,单刀直入吧:runtime是一个C和汇编写的动态库,就像是一个小小的系统,将OC和C紧密关联在一次,这个系统主要做两件事情。

    1,封装C语言的结构体和函数,让开发者在运行时创建,检查或者修改类,对象和方法等
    2,传递消息,找出方法的最终执行代码

    也就是说我们写的OC代码在运行的时候都会转为运行时代码

    通过runtime的学习能够更好理解OC的这种消息发送机制,并且我也认为对runtime的学习是对深入学习iOS必不可少的坎,比如你有可能通过阅读一些第三方框架来提高自己的编程技巧,在这些第三方框架中就会有大量运行时代码。掌握了runtime我们能够简单做些什么事情呢?

      1,遍历对象的所有属性
      2,动态添加/修改属性,动态添加/修改/替换方法
      3,动态创建类/对象
      4,方法拦截使用(给方法添加一个动态实现,甚至可以讲该方法重定向或者打包给lisi)

     听起来跟黑魔法一样。其实runtime就素有黑魔法之称!我们就从成员变量开始我们对runtime的学习吧。

    正文


     

    成员变量:

      成员变量是我们在定义一个类中其中重要的成分,主要是想描述这个类实例化后具备了什么属性,特点,等等。就像定义了一个Person类,Person类具备了name,age,gender等各种属性来描述这个类。举了这个稍微符合的例子来辅助说明成员变量是干嘛用的,但是却还是不能说明成员变量到底本质是什么?在runtime.h文件中的成员变量是一个指向objc_ivar类型的结构体指针:

    1 /// An opaque type that represents an instance variable.
    2 typedef struct objc_ivar *Ivar;

    在这个objc_ivar这个结构体定义如下:

    struct objc_ivar {
        char *ivar_name                                          OBJC2_UNAVAILABLE;//成员变量的名字
        char *ivar_type                                          OBJC2_UNAVAILABLE;//成员变量的类型
        int ivar_offset                                          OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
    }      

    对成员变量进行操作的主要有以下几种方式:

    Ivar *class_copyIvarList(Class cls, unsigned int *outCount)      //获取所有成员变量
    const char *ivar_getName(Ivar v)            //获取某个成员变量的名字
    const char *ivar_getTypeEncoding(Ivar v)   //获取某个成员变量的类型编码
    Ivar class_getInstanceVariable(Class cls, const char *name)    //获取某个类中指定名称的成员变量
    id object_getIvar(id obj, Ivar ivar)    //获取某个对象中的某个成员变量的值
    void object_setIvar(id obj, Ivar ivar, id value)    //设置某个对象的某个成员变量的值

    下面通过建立一个Person类来理解runtime中提供的这些函数,首先我们定义一个Person类,并且重写它的description方法:

    Person.h中:

    @interface Person : NSObject
    {
        NSString *clan;//族名
    }
    @property(nonatomic,copy)NSString *name;
    @property(nonatomic,copy)NSString *gender;
    @property(nonatomic,strong)NSNumber *age;
    @property(nonatomic,assign)NSInteger height;
    @property(nonatomic,assign)double weight;

    Person.m

    -(NSString *)description
    {
        unsigned int outCount;
        Ivar *IvarArray = class_copyIvarList([Person class], &outCount);//获取到Person中的所有成员变量
        for (unsigned int i = 0; i < outCount; i ++) {
            Ivar *ivar = &IvarArray[i];
            NSLog(@"第%d个成员变量:%s,类型是:%s",i,ivar_getName(*ivar),ivar_getTypeEncoding(*ivar));// 依次获取每个成员变量并且打印成员变量名字和类型
        }
        return nil;
    }

    在程序入口创建Person实例类并且调用description方法可以看到打印台打印:


    ivar_getTypeEncoding函数获取到的是成员变量的类型编码。类型编码是苹果对数据类型对象类型规定的另一个表现形式,比如"@"代表的是对象,":"表示的是SEL指针,"v"表示的是void。具体可以看苹果官方文档对类型编码的具体规定:戳我!!!

    通过runtime来给对象赋值和获取对象的值:

    Person.m中实现了两个分别对实例化person对象赋值和取值方法:

     1 + (Person *)personWithName:(NSString *)name age:(NSNumber *)age gender:(NSString *)gender clan:(NSString *)clan
     2 {
     3     Person *p = [Person new];
     4     unsigned int outCount;
     5     Ivar *IvarArray = class_copyIvarList([Person class], &outCount);
     6     object_setIvar(p, IvarArray[0], clan);
     7     object_setIvar(p, IvarArray[1], name);
     8     object_setIvar(p, IvarArray[2], gender);
     9     object_setIvar(p, IvarArray[3], age);
    10     return p;
    11 }
    12 
    13 - (void)personGetPersonMessage
    14 {
    15     unsigned int outCount;
    16     Ivar *IvarArray = class_copyIvarList([Person class], &outCount);
    17     for (NSInteger i = 0; i < 4; i ++) {
    18         NSLog(@"%s = %@",ivar_getName(IvarArray[i]),object_getIvar(self,IvarArray[i]));
    19     }
    20 }

    在viewDidLoad中:

    1 Person *person = [Person personWithName:@"张三" age:@26 gender:@"man" clan:@""];
    2     [person personGetPersonMessage];

    可以看到打印台打印:

    成功的对Person对象进行设置值和取值操作。


    属性:

    属性在runtime中定义如下:

    1 /// An opaque type that represents an Objective-C declared property.
    2 typedef struct objc_property *objc_property_t;
    3 /// Defines a property attribute
    4 typedef struct {
    5     const char *name;           /**< The name of the attribute */
    6     const char *value;          /**< The value of the attribute (usually empty) */
    7 } objc_property_attribute_t;

    属性的本质是一个指向objc_property的结构体指针。跟成员变量一样,runtime中一样为属性定义了一系列对属性的操作函数:

    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)   //获取所有属性的列表
    const char *property_getName(objc_property_t property) //获取某个属性的名字
    const char *property_getAttributes(objc_property_t property)   //获取属性的特性描述
    objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)   //获取所有属性的特性

    获取person实例对象中所有属性的特性描述:

    Person.m中:

    1 - (void)getAttributeOfproperty
    2 {
    3     unsigned int outCount;
    4     objc_property_t *propertyList = class_copyPropertyList([Person class], &outCount);
    5     for (NSInteger i = 0; i < outCount; i ++) {
    6         NSLog(@"属性:%s,它的特性描述:%s",property_getName(propertyList[i]),property_getAttributes(propertyList[i]));
    7     }
    8 }

    获取属性列表只会获取有property属性声明的变量,所有当调用getAttributeOfproperty的时候打印台打印:

    特性描述主要说明的是该属性的修饰符,具体的代表意义如下:

    1 属性类型  name值:T  value:变化
    2 编码类型  name值:C(copy) &(strong) W(weak) 空(assign) 等 value:无
    3 非/原子性 name值:空(atomic) N(Nonatomic)  value:无

    在运行时runtime下我们可以获取到所有的成员变量,以及类的私有变量。所有runtime的重要应用就是字典转模型,复杂归档

    应用1:复杂对象归档

    复杂对象归档平常我们需要类遵循<NSCoding>协议,重写协议中编码和解码的两个方法,创建NSKeyarchive对象将类中的成员变量进行逐一编码和解码。

    runtime下基本是同样的操作,但是我们可以利用runtime提供的函数获取变量的名字和所对应的成员变量,开启循环进行快速归档(要记得普通情况下我们可以要逐一的写),同样是以Person类为例;

    Person.m中:

    -(instancetype)initWithCoder:(NSCoder *)aDecoder
    {
        self = [super init];
        if (self) {
            unsigned int outCount;
            Ivar *ivarList = class_copyIvarList([Person class], &outCount);
            for (NSInteger i = 0; i < outCount; i ++) {
                Ivar ivar = ivarList[i];
                NSString *ivarName = [NSString
                                      stringWithUTF8String:ivar_getName(ivar)];
                [self setValue:[aDecoder decodeObjectForKey:ivarName] forKey:ivarName];
            }
        }
        return self;
    }
    
    
    -(void)encodeWithCoder:(NSCoder *)aCoder
    {
        unsigned int outCount;
        Ivar *ivarlist = class_copyIvarList([self class], &outCount);
        for (NSInteger i = 0; i < outCount; i ++) {
            Ivar ivar = ivarlist[i];
            NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
            [aCoder encodeObject:[self valueForKey:ivarName] forKey:ivarName];
        }
    }

    应用2字典转模型:

    另一个重要的应用便是字典转模型,将字典中的数据赋值给模型中对应的属性。大概思路是先通过class_copyPropertyList获取到所有的属性,再通过property_getName获取到变量对应的名字作为key值,通过key值查看字典中是否有对应的value,若是有的话则给属性赋值。

    以上的操作都是基于对象具有的属性通过runtime获取属性的一些信息,比如名字,属性的值,属性的特性描述等。通过runtime还可以给对象动态添加变量,也就是添加关联。还记得分类和延展的区别吗?延展可以为类添加属性和方法,而分类只能为类添加方法。有个面试题:不使用继承的方式如何给系统类添加一个公共变量?我们知道在延展里面为类添加的变量是私有变量,外界无法访问的。如果对runtime有了解的人也许就知道这是想考验应聘人对runtime的了解。

    runtime下提供了三个函数给我们能够进行关联对象的操作:

     1 void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)     //为某个类关联某个对象
     2 id objc_getAssociatedObject(id object, const void *key)    //获取到某个类的某个关联对象
     3 void objc_removeAssociatedObjects(id object)          //移除已经关联的对象
     4 参数说明:
     5 /**
     6  *  参数说明:
     7     object:要添加成员变量的对象
     8     key:添加成员变量对应的key值
     9     value:要添加的成员变量
    10     policy:添加的成员变量的修饰符
    11  */

    我们以给NSDictionary添加一个NSString类型的公共变量funnyName为例:

    在NSDictionary分类MyDict.h新增加两个属性其中一个字符串一个block中:

    1 @property(nonatomic,copy)NSString *funnyName;
    2 @property(nonatomic,copy)void(^dictAction)(NSString *str);

    一般情况下如果我们只声明了这些变量在外面使用的时候就会报错,所有需要我们手动实现他们的set和get方法(可别以为是因为我们没有实现它们的set和方法才报错了哦,@property修饰的属性可是会自动生成get和set方法)

    那应该如何实现它们的set和get方法呢:

     1 -(void)setFunnyName:(NSString *)funnyName
     2 {
     3     objc_setAssociatedObject(self, @selector(funnyName), funnyName, OBJC_ASSOCIATION_COPY_NONATOMIC);
     4 }
     5 
     6 -(NSString *)funnyName
     7 {
     8     return objc_getAssociatedObject(self, @selector(funnyName));
     9 }
    10 
    11 -(void)setDictAction:(void (^)(NSString *))dictAction
    12 {
    13     objc_setAssociatedObject(self, @selector(dictAction), dictAction, OBJC_ASSOCIATION_COPY_NONATOMIC);
    14 }
    15 
    16 -(void (^)(NSString *))dictAction
    17 {
    18     return objc_getAssociatedObject(self, @selector(dictAction));
    19 }

    在我们程序中就可以使用字典新增加的两个属性了:

     1 NSDictionary *dict = [NSDictionary new];
     2     dict.funnyName = @"SoFunny";
     3     NSLog(@"dict.funnyName = %@",dict.funnyName);
     4     void(^action)(NSString *str)  = ^(NSString *str){
     5         NSLog(@"打印了这个字符串:%@",str);
     6     };
     7     //设置block
     8     dict.dictAction = action;
     9     
    10     //调用dict的action
    11     dict.dictAction(@"新增加变量dicAction");

    在打印台可以看见打印成功打印到我们想要的东西:


    初尝runtime,若是有什么表述不当的地方还请指出。后续将继续更新runtime的学习。

    戳我看代码

  • 相关阅读:
    redis五中数据类型
    MySQL索引
    mysql中如何设计计数器表(待续)
    mysql 数据类型选择原则
    1.开篇(听说你还在艰难的啃react源码)
    关于wamp的HTML, PHP, mysql 三者的操作与联系
    关于wamp的HTML, PHP, mysql 三者的操作与联系
    关于wamp的HTML, PHP, mysql 三者的操作与联系
    SQL server T-sql语句查询执行顺序
    SQL server T-SQL索引详解
  • 原文地址:https://www.cnblogs.com/develop-SZT/p/5348364.html
Copyright © 2020-2023  润新知