美文网首页
成员变量和属性

成员变量和属性

作者: 码农老张 | 来源:发表于2018-08-14 14:20 被阅读0次

    上一篇文章里面有提到成员变量和属性变量,这里专门写点关于它们的笔记。
    成员变量就是我们在开发中,类似下面这样定义的变量,例如:

    @interface Person : NSObject
    {
        @public
        NSString *_name;
        CGFloat _age;
    }
    @end
    

    则_name,_age便是成员变量。

    属性就是在开发中,我们用 @property 关键字声明的变量,如:

    @property (nonatomic,copy) NSString *name;
    @property (nonatomic,assign) CGFloat *age;
    

    该方法会自动生成_name和_age成员变量,name,age便是我们声明的属性

    Student.m
    #import "Student.h"
    
    @interface Student()
    {
      NSString *_address;
    }
    @property (nonatomic,copy) NSString *name;
    @end
    @implementation Student
    @end
    

    将Student.m文件用clang -rewrite-objc Student.m重新编译下得到Student.cpp,从该文件中,我们可以得到如下信息:

    struct Student_IMPL {
      struct NSObject_IMPL NSObject_IVARS;
      NSString *_address;
      NSString *_name;
    };
    
    static NSString * _I_Student_name(Student * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Student$_name)); }
    
    static void _I_Student_setName_(Student * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Student, _name), (id)name, 0, 1); }
    

    编译器将属性自动转换成了成员变量,并且自动生成了getter和setter方法。因此两者最直观的区别是属性会有相应的getter方法和setter方法,而成员变量没有,另外,外部访问属性可以用"."来访问,访问成员变量需要用"->"来访问

    成员变量(Ivar)

    定义

    runtime.h文件中对Ivar的定义为:

     typedef struct objc_ivar *Ivar;
    

    其为指向结构体objc_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
    } OBJC2_UNAVAILABLE;
    
    • ivar_name
      成员变量名称,可以用const char * ivar_getName(Ivar ivar)来获得
    • ivar_type
      成员变量类型,可以用const char * ivar_getTypeEncoding(Ivar ivar) 来获得,这里得到的类型,并不是变量真正的成员变量类型,而是经过类型编码的c字符串。
    • ivar_offset
      基地址偏移量。其实在访问变量的时候,是先找到类所在的地址,然后根据地址偏移量,去找到我们要访问的变量的。我们可以用ptrdiff_t ivar_getOffset(Ivar ivar)来得到某个变量的偏移量。通过这个偏移量,我们也可以访问到类的私有变量。
      那么变量在类中是怎么存储的呢?继续来看的类的定义:
    struct objc_class {
      Class isa  OBJC_ISA_AVAILABILITY;
      #if !__OBJC2__
      Class super_class         OBJC2_UNAVAILABLE;  // 父类
      const char *name          OBJC2_UNAVAILABLE;  // 类名
      long version              OBJC2_UNAVAILABLE;  // 类的版本号
      long info                 OBJC2_UNAVAILABLE;  // 类信息
      long instance_size        OBJC2_UNAVAILABLE;  // 类的实例大小
      struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 成员变量列表
      struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法列表
      struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
      struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议列表
      #endif
    } OBJC2_UNAVAILABLE;
    

    来看结构体objc_ivar_list定义:

    struct objc_ivar_list {
      int ivar_count                                    OBJC2_UNAVAILABLE;
      #ifdef __LP64__
      int space                                         OBJC2_UNAVAILABLE;
      #endif
      /* variable length structure */
      struct objc_ivar ivar_list[1]                     OBJC2_UNAVAILABLE;
    } OBJC2_UNAVAILABLE;
    

    在类的定义中,用objc_ivar_list类型的结构体指针变量来记录类的所有成员变量的相关信息。objc_ivar_list中存放着一个objc_ivar结构体数组,objc_ivar结构体中存放着类的单个成员变量的所有信息。

    对变量的操作函数

    BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types)
    向类中添加成员变量,该方法只能在动态创建类的时候使用,不能向已存在的类中添加成员变量。
    Ivar * class_copyIvarList(Class cls, unsigned int *outCount)
    获得成员变量列表,outCount如果有返回值,则返回的是类中成员变量的个数,如果NULL,则没有返回成员变量的个数
    const char * ivar_getName( Ivar ivar)
    返回成员变量的name
    const char * ivar_getTypeEncoding( Ivar ivar)
    返回成员变量的类型编码
    ptrdiff_t ivar_getOffset( Ivar ivar)
    返回成员变量的基地址偏移量
    id object_getIvar(id object, Ivar ivar)
    可以用这种便捷方式来获得成员变量的值
    void object_setIvar(id object, Ivar ivar, id value)
    设置成员变量的值
    代码示例

    Person.h
    
    @interface Person : NSObject
    {
      @public
      NSString *_name;
      CGFloat _age;
    
      @private
      int _temp;
    }
    @property (nonatomic,assign) CGFloat height;
    @end
    
    Person.m
    
    @implementation Person
    - (NSString *)description{
       return [NSString stringWithFormat:@"私有变量_temp的值为%d",_temp];
    }  
    @end
    
    main.m
    #import <Foundation/Foundation.h>
    #import "Person.h"
    #import <objc/runtime.h>
    
    int main(int argc, const char * argv[]) {
      @autoreleasepool {
        // 添加成员变量
        Class cls =  objc_allocateClassPair([NSObject class],"myClass", 0);
        BOOL res = class_addIvar(cls, "sex", sizeof(NSString *), log2(sizeof(NSString *)), "@");
        if(res){
            NSLog(@"添加成功");
        }else{
            NSLog(@"添加失败");
        }
        
        Person *p = [[Person alloc] init];
        unsigned int outCount = 0;
        NSLog(@"=============获取成员变量列表============");
        Ivar *ivars = class_copyIvarList([p class], &outCount);
        NSLog(@"成员变量个数: %d",outCount);
        for (int i = 0; i<outCount; i++) {
            Ivar ivar = ivars[i];
            NSLog(@"变量名称: %s,类型: %s,偏移量: %td",ivar_getName(ivar),ivar_getTypeEncoding(ivar),ivar_getOffset(ivar));
        }
        free(ivars);
        NSLog(@"=============访问私有变量============");
        NSLog(@"实例变量p地址:%p",p);
        Ivar tempIvar = class_getInstanceVariable([p class], "_temp");
        NSLog(@"私有变量_temp的偏移量:%td",ivar_getOffset(tempIvar));
        int *temp = (int *)((__bridge void *)(p) + ivar_getOffset(tempIvar));
        NSLog(@"私有变量_temp的地址:%p",temp);
        *temp = 10;
        NSLog(@"%@",p);
      }
      return 0;
    }
    

    输出结果为:

    添加成功
    =============获取成员变量列表============
    成员变量个数: 4
    变量名称: _name,类型: @"NSString",偏移量: 8
    变量名称: _age,类型: d,偏移量: 16
    变量名称: _temp,类型: i,偏移量: 24
    变量名称: _height,类型: d,偏移量: 32
    =============访问私有变量============
    实例变量p地址:0x100200000
    私有变量_temp的偏移量:24
    私有变量_temp的地址:0x100200018
    私有变量_temp的值为10
    属性(Property)

    在类的定义中,我们没有发现存储属性的变量,那么属性是怎么存储的呢?从上面重新编译Student.m生成的Student.cpp中,我们可以看到编译器将属性转换成了成员变量,但是仍然找不到属性是用什么存储的。怎么办呢?我们可以从添加属性的方法入手,添加属性的方法:

    BOOL class_addProperty(Class cls, const char *name,const objc_property_attribute_t *attrs, unsigned int n)
    其方法实现如下:

    static bool _class_addProperty(Class cls, const char *name, 
                   const objc_property_attribute_t *attrs, unsigned int count, 
                   bool replace){
      if (!cls) return NO;
      if (!name) return NO;
    
      property_t *prop = class_getProperty(cls, name);
      if (prop  &&  !replace) {
        // already exists, refuse to replace
        return NO;
      } 
      else if (prop) {
        // replace existing
        rwlock_writer_t lock(runtimeLock);
        try_free(prop->attributes);
        prop->attributes = copyPropertyAttributeString(attrs, count);
        return YES;
      }
      else {
        rwlock_writer_t lock(runtimeLock);
        
        assert(cls->isRealized());
        
        property_list_t *proplist = (property_list_t *)
            malloc(sizeof(*proplist));
        proplist->count = 1;
        proplist->entsizeAndFlags = sizeof(proplist->first);
        proplist->first.name = strdup(name);
        proplist->first.attributes = copyPropertyAttributeString(attrs, count);
        
        cls->data()->properties.attachLists(&proplist, 1);
        
        return YES;
      }
    }
    

    从中我们可以看到:其最终是用property_list_t来存储单个属性信息的。

    对属性操作的函数

    objc_property_t class_getProperty(Class cls, const char *name)
    获得类的某个属性的信息
    objc_property_t * class_copyPropertyList(Class cls, unsigned int *outCount)
    获得类的属性列表,不包含父类的属性,outCount中返回类的属性个数。
    const char *property_getName(objc_property_t property)
    获得属性名称
    const char *property_getAttributes(objc_property_t property)
    获得属性的属性信息,即属性的描述信息。
    char *property_copyAttributeValue(objc_property_t property, const char *attributeName)
    获得属性的某个描述信息的值
    objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount)
    属性的描述信息列表
    代码演练

      int main(int argc, const char * argv[]) {
        @autoreleasepool {
          Person *p = [[Person alloc] init];
          NSLog(@"=========动态添加属性==========");
          objc_property_attribute_t type= {"T","@\"NSString\""}; // type
          objc_property_attribute_t refType = {"C",""}; // copy
          objc_property_attribute_t backValue = {"V","_sex"}; // 返回值
          objc_property_attribute_t attrs[] = {type, refType, backValue}; 
          BOOL flag = class_addProperty([p class], "sex",attrs, 3);
          if(flag){
              NSLog(@"属性添加成功");
          }else{
              NSLog(@"属性添加失败");
          }
          NSLog(@"=========获得属性列表==========");
          unsigned int outCount = 0;
          objc_property_t *props = class_copyPropertyList([p class], &outCount);
          for(int i=0; i<outCount; i++){
            objc_property_t p = props[i];
            NSLog(@"属性: %s,描述信息:%s",property_getName(p),property_getAttributes(p));
          }
          free(props);
        
          NSLog(@"=============获取成员变量列表============");
          unsigned int outIvarCount = 0;
          Ivar *ivars = class_copyIvarList([p class], &outIvarCount);
          NSLog(@"成员变量个数: %d",outIvarCount);
          for (int i = 0; i<outIvarCount; i++) {
            Ivar ivar = ivars[i];
            NSLog(@"变量名称: %s",ivar_getName(ivar));
          }
          free(ivars);
        }
         return 0;
      }
    

    输出结果:

    =========动态添加属性==========
    属性添加成功
    =========获得属性列表==========
    属性: sex,描述信息:T@"NSString",C,V_sex
    属性: height,描述信息:Td,N,V_height
    =============获取成员变量列表============
    成员变量个数: 4
    变量名称: _name
    变量名称: _age
    变量名称: _temp
    变量名称: height
    由代码输出结果,我们可以看到类的属性的一些信息,同时我们也可以看到,我们动态添加的属性,是不会自动生成对应的成员变量的。因此我们在给动态添加的属性赋值的时候,是不能直接用
    属性名称去赋值的。那怎么办呢?其实,我们用@property声明的属性,系统会自动生成getter和setter方法,我们也可以仿造系统的做法,同样的给我们新添加的属性,增加getter和setter方法。给类增加这两个方法,由多种实现方式,但是在不改变原有类的代码的基础上,我们需要用到对象关联

    对象关联(Associative References)

    对象关联是动态添加属性的常用方法,相关操作函数如下:

    void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy)
    给对象设置一个关联的值, objc_AssociationPolicy:关联策略,其实就是值的引用类型,是retain,copy,weak或assign
    id objc_getAssociatedObject(id object, void *key)
    得到对象关联的值
    void objc_removeAssociatedObjects(id object)
    移除所有对象的关联值
    这里演示下,将上面的属性sex添加完善一下。
    代码演练:

    static const void *sexTag = &sexTag;
    NSString *sex(id self, SEL _cmd) {
    return objc_getAssociatedObject(self, sexTag);
    }
    void setSex(id self, SEL _cmd, NSString *sex) {
    objc_setAssociatedObject(self, sexTag, sex, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    int main(int argc, const char * argv[]) {
    @autoreleasepool {
    Person *p = [[Person alloc] init];
    NSLog(@"=========动态添加属性==========");
    objc_property_attribute_t type= {"T","@"NSString""};
    objc_property_attribute_t refType = {"C",""};
    objc_property_attribute_t backValue = {"V","_sex"};
    objc_property_attribute_t attrs[] = {type, refType, backValue};
    BOOL flag = class_addProperty([p class], "sex",attrs, 3);
    if(flag){
    NSLog(@"属性添加成功");
    class_addMethod([p class], @selector(sex), (IMP)sex, "@@:");
    class_addMethod([p class], @selector(setSex:), (IMP)setSex, "v@:@");
    }else{
    NSLog(@"属性添加失败");
    }
    NSLog(@"=============属性赋值及获取============");
    [p performSelector:@selector(setSex:) withObject:@"男"];
    NSLog(@"属性sex的值为:%@",[p performSelector:@selector(sex)]);
    输出结果为:

    =========动态添加属性==========
    属性添加成功
    =============属性赋值及获取============
    属性sex的值为:男
    链接:https://www.jianshu.com/p/be00d998a4ed

    相关文章

      网友评论

          本文标题:成员变量和属性

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