成员变量和属性

2018-08-14  本文已影响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;
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

上一篇下一篇

猜你喜欢

热点阅读