OC-成员变量和属性

前言

相信大家对这两个词都不陌生,但是大家会很容易将这两个词混淆,所以在探究之前,先来说下什么是成员变量,什么是属性。

成员变量就是我们在开发中,类似下面这样定义的变量,例如:

@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的值为:男
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的...
    有一种再见叫青春阅读 574评论 0 3
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,170评论 0 7
  • Objective-C语言是一门动态语言,他将很多静态语言在编译和链接时期做的事情放到了运行时来处理。这种动态语言...
    tigger丨阅读 1,379评论 0 8
  • 溽暑袭津城 艳阳曝万物 热浪蔫垂柳 蜃气浮苍穹 棠香溢满苑 海风熏汗躯 何消心坎炙 敞怀纳微凉
    Ocaptain_lyl阅读 134评论 0 0