@property 详解

我们先来看两个版本代码
不使用@property版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@interface User : NSObject
- (NSString *)name;
- (void)setName:(NSString *)name;
@end

@implement User {
NSString *_name;
}

- (NSString *)name {
return _name;
}

- (void)setName:(NSString *)name {
_name = [name copy];
}

@end

使用@property版本

1
2
3
4
5
6
7
8
@interface User : NSObject
@property (nonatomic, copy) NSString *name;
@end

@implement User
//@synthesize的作用是定义实例变量_name,并生成getter和setter方法,Xcode4.5以后就可以省略@synthesize
//@synthesize name;
@end

这两个版本的代码基本上是等价的。由此可以看出@property默认为我们做了第一个版本中的事。接下来我们更深入了解下在runtime发生的变化。
有必要先了解两个结构体 objc_ivarobjc_property

objc_ivar

1
2
3
4
5
6
7
8
9
typedef objc_ivar * Ivar;
struct objc_ivar {
char *ivar_name;
char *ivar_type;
int ivar_offset;
#ifdef __LP64__
int space;
#endif
}

获取该类现在的成员变量

1
2
3
4
5
6
7
8
9
unsigned int outCount = 0;
Ivar * ivars = class_copyIvarList([User class], &outCount);
for (unsigned int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
const char * name = ivar_getName(ivar);
const char * type = ivar_getTypeEncoding(ivar);
NSLog(@"类型为 %s 的 %s ",type, name);
}
free(ivars);

打印结果:

runtimeIvar[602:16885] 类型为 @”NSString” 的 _name

objc_property

1
2
3
4
5
typedef struct objc_property *objc_property_t;
struct objc_property {
const char *name;
const char *attributes;
};

获取该类现在的属性及描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int outCount = 0;
objc_property_t * properties = class_copyPropertyList([User class], &outCount);
for (unsigned int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
//属性名
const char * name = property_getName(property);
//属性描述
const char * propertyAttr = property_getAttributes(property);
NSLog(@"属性描述为 %s 的 %s ", propertyAttr, name);

//属性的特性
unsigned int attrCount = 0;
objc_property_attribute_t * attrs = property_copyAttributeList(property, &attrCount);
for (unsigned int j = 0; j < attrCount; j ++) {
objc_property_attribute_t attr = attrs[j];
const char * name = attr.name;
const char * value = attr.value;
NSLog(@"属性的描述:%s 值:%s", name, value);
}
free(attrs);
NSLog(@"\n");
}
free(properties);

打印结果

runtimeIvar[661:27041] 属性描述为 T@”NSString”,C,N,V_name 的 name
runtimeIvar[661:27041] 属性的描述:T 值:@”NSString”
runtimeIvar[661:27041] 属性的描述:C 值:
runtimeIvar[661:27041] 属性的描述:N 值:
runtimeIvar[661:27041] 属性的描述:V 值:_name

其中T代表类型,具体参考Type Encoding,C代表copy,N代表nonatomic,V代表属性name对应的实例变量名称,这里是_name。

在runtime里,一个类有以下三个列表:

  • ivar_list: 成员变量列表
  • method_list: 方法列表
  • prop_list: 属性列表

在增加一个属性时,系统会在 ivar_list 中添加成员变量描述(Ivar),在 method_list 中添加 setter 和 getter 方法的描述,在 prop_list 中添加属性的描述(objc_property_t)。

category 中如何使用 @property

主要要在 setter 和 getter 中用到这两个 runtime 方法: objc_setAssociatedObjectobjc_getAssociatedObject

1
2
3
4
5
6
7
8
9
10
11
12
13
@interface MyClass (Category)
@property (strong, nonatomic) NSString *text;
@end

@implementation MyClass (Category)
- (NSString *)text{
return objc_getAssociatedObject(self, @selector(text));
}

- (void)setText:(NSString *)text{
objc_setAssociatedObject(self, @selector(text), text, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

也可以直接用宏定义

PS:
Ivar详解
iOS runtime实战应用:成员变量和属性
《招聘一个靠谱的iOS》面试题参考答案
Objective-C: Property / instance variable in category