前言
相信大家对这两个词都不陌生,但是大家会很容易将这两个词混淆,所以在探究之前,先来说下什么是成员变量,什么是属性。
成员变量就是我们在开发中,类似下面这样定义的变量,例如:
@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的值为:男