参考文章
写在前面
-
成员变量&属性&关联对象是OC语言中大多数变量的来源(不知道有没有别的)。
-
OC语言是一门动态语言,动态的体现方式就是加入了面向对象特性和smallTalk式的消息传递机制,而它的实现方式就是一个用C和编译语言写的一个runtime库。
-
由于OC是一门动态语言,所以有一部分东西是在运行时动态的添加进去,比如分类,就是动态的被添加,所以本来不可以添加属性的分类,可以在运行时添加关联方式。
成员变量(Ivar)
在.m中成员变量的修饰符为@private.
在.h中成员变量的修饰符@protected.
@private — 作用范围只能在自身类
@protected — 作用范围在自身类和继承自己的子类,什么都不写,默认是此属性。
@public — 作用范围最大,在任何地方
@package —只要处在同一个框架中,就能直接访问对象的成员变量
runtime.h文件中对Ivar的定义为:
typedef struct objc_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结构体中存放着类的单个成员变量的所有信息
- 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方法,而成员变量没有,另外,外部访问属性可以用".“来访问,访问成员变量需要用”->"来访问