成员变量&属性&关联对象

参考文章

OC-成员变量和属性

写在前面

  • 成员变量&属性&关联对象是OC语言中大多数变量的来源(不知道有没有别的)。

  • OC语言是一门动态语言,动态的体现方式就是加入了面向对象特性和smallTalk式的消息传递机制,而它的实现方式就是一个用C和编译语言写的一个runtime库。

  • 由于OC是一门动态语言,所以有一部分东西是在运行时动态的添加进去,比如分类,就是动态的被添加,所以本来不可以添加属性的分类,可以在运行时添加关联方式。

成员变量(Ivar)

在.m中成员变量的修饰符为@private.
在.h中成员变量的修饰符@protected.

@private — 作用范围只能在自身类
@protected — 作用范围在自身类和继承自己的子类,什么都不写,默认是此属性。
@public — 作用范围最大,在任何地方
@package —只要处在同一个框架中,就能直接访问对象的成员变量

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

 typedef struct objc_ivar *Ivar;
  1. 变量在类中是怎么存储的呢?
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;
  1. 来看结构体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结构体中存放着类的单个成员变量的所有信息

  1. 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)来得到某个变量的偏移量。通过这个偏移量,我们也可以访问到类的私有变量。

属性(Property)

在类的定义中,我们没有发现存储属性的变量,那么属性是怎么存储的呢?从上面重新编译Student.m生成的Student.cpp中,我们可以看到编译器将属性转换成了成员变量,但是仍然找不到属性是用什么存储的。怎么办呢?我们可以从添加属性的方法入手,添加属性的方法:
(文章目录2的那部分转化的C++代码,属性是通过objc_setProperty 添加)

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来存储单个属性信息的。

关联对象

有时候,类的实例可能是由某种机制所创建的,而开发者无法令这种机制创建出自己所写的子类实例。objective-C中有一项强大的特性可以解决此问题,这就是“关联对象”(Associated Object)。
可以给某对象关联许多其他对象,这些对象通过“键”来区分。存储对象值的时候。可以指名“存储策略”(storage policy),用以维护相应的“内存管理语义”。存储策略由名为objc_AssociationPolicy的枚举所定义。
我们可以把某对象想象成NSDictionary,把关联对象的值理解为字典中的条目,于是,存取关联对象的值就相当于NSDictionary对象上调用[object setObject: value forKey:key]与[object objectForKey:key]方法。然而俩者之间有个重要差别:设置关联对象时用的键(key)是个不透明的指针。如果在俩个键上调用’isEqual:'方法的返回值是YES,那么NSDictionary就认为俩者相等;然而在设置关联对象值时,若想令俩个键匹配到同一个值,则二者必须是完全相同的指针才行。鉴于此,在设置关联对象值时,通常使用静态全局变量做键。
在这里插入图片描述
可以用来实现给分类添加属性,详见Category

1: .和->

C语言中的.和->

p.member相当于&p+offset_member
p->member相当于p+offset_member

.使用.语法

.语法是在预编译时调用set,get方法,因此本质是set,get方法

注意点:

1>要使用点语法要保证有对象,或者拿到了对象

2>要使用.语法必须确保有成员变量的set,get方法

3>点语法不可与&取址符搭配使用,原因同set,get

使用指针

既使用”对象名->_成员名“这种指针的方式对成员变量进行赋值

注意点:

1>当成员是@public时,可以在外部直接使用指针方式对成员进行访问

2>当成员是@protected时,不可在类的外部使用,可以在类中,子类中使用,也可以在其他类中使用,但是前提是必须拿到对象。

3>当成员是@private时,不可在子类中使用此方法,只可使用get,set方法对其进行访问。

<属性自动合成存取方法,所以点语法就🉑️,成员变量是指针方式,所以是->>

.(点语法)是访问类的属性,本质是调用set、get方法。

->是访问成员变量

2: 属性也会生成成员变量

@interface Student()
{
  NSString *_address;
}
@property (nonatomic,copy) NSString *name;
@end
@implementation Student
@end

转C++后

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方法,而成员变量没有,另外,外部访问属性可以用".“来访问,访问成员变量需要用”->"来访问

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值