文章目录
  1. 1. @property
    1. 1.1. class_ro_t
    2. 1.2. prop_list_t
    3. 1.3. ivar_list_t
    4. 1.4. method_list_t
  2. 2. Runtime中的属性
    1. 2.1. 定义
    2. 2.2. 特征字符串
    3. 2.3. 属性的操作函数
  3. 3. Model赋值
    1. 3.1. Model赋值
    2. 3.2. YYModel中Model赋值
  4. 4. 快速归档
    1. 4.1. 归档
    2. 4.2. 实现NSCoding协议
    3. 4.3. YYModel的轮子
  5. 5. 参考

在Obj-c开发中@property关键字可以声明一个属性,他可以由编译器为类添加成员变量和相对应的存取方法。那么它是如何实现的呢、对属性我们可以怎样操作、又该如何应用这些操作。这篇文章将从Objc源码入手分析属性相关的一些细节

@property

在Obj-c开发中@property关键字可以声明一个属性,它的语法是这样的:

@property (参数1,参数2) 类型 名字;

我们不分析它如何使用,这里只分析他如何实现,我们先新建一个文件FDPropertyTest.m写入如下Obj-c代码,然后使用个clang改写为C代码

1
2
3
4
5
6
7
8
#import <objc/NSObject.h>

@interface ProperClass: NSObject
@property (nonatomic, strong) NSObject *objcProper;
@end

@implementation ProperClass
@end

clang -rewrite-objc FDPropertyTest.m

这里只列出关键部分

class_ro_t

我们知道这里定义了类的方法列表,成员变量表和属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static struct _class_ro_t _OBJC_CLASS_RO_$_ProperClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
0, __OFFSETOFIVAR__(struct ProperClass, _objcProper), sizeof(struct ProperClass_IMPL),
(unsigned int)0,
0,
"ProperClass",
//方法
(const struct _method_list_t *)&_OBJC_$_INSTANCE_METHODS_ProperClass,
0,
//成员变量
(const struct _ivar_list_t *)&_OBJC_$_INSTANCE_VARIABLES_ProperClass,
0,
//属性
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_ProperClass,
};

这里我们可以看到属性已经被存储到类对象中了,已经不仅仅是一个中间变量(当然这只是C代码,更底层的代码,属性会不会存储到独立的内存空间中还不得而知)

prop_list_t

定义了一个名为objcProper属性

1
2
3
4
5
6
7
8
9
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_ProperClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"objcProper","T@\"NSObject\",&,N,V_objcProper"}}
};

ivar_list_t

同时定义了一个名为_objcProper的成员变量

1
2
3
4
5
6
7
8
9
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[1];
} _OBJC_$_INSTANCE_VARIABLES_ProperClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
1,
{{(unsigned long int *)&OBJC_IVAR_$_ProperClass$_objcProper, "_objcProper", "@\"NSObject\"", 3, 8}}
};

method_list_t

定义了存取方法,并添加到类对象的方法列表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//getter
static NSObject * _I_ProperClass_objcProper(ProperClass * self, SEL _cmd) { return (*(NSObject **)((char *)self + OBJC_IVAR_$_ProperClass$_objcProper)); }

//setter
static void _I_ProperClass_setObjcProper_(ProperClass * self, SEL _cmd, NSObject *objcProper) { (*(NSObject **)((char *)self + OBJC_IVAR_$_ProperClass$_objcProper)) = objcProper; }

//类对象的方法列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[2];
} _OBJC_$_INSTANCE_METHODS_ProperClass __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
2,
{{(struct objc_selector *)"objcProper", "@16@0:8", (void *)_I_ProperClass_objcProper},
{(struct objc_selector *)"setObjcProper:", "v24@0:8@16", (void *)_I_ProperClass_setObjcProper_}}
};

从上面的代码可以看出@property为类添加了属性、成员变量和对应的存取方法。@property是Obj-c基础的语法关键字,它的实现是由LLVM编译器实现的。

Runtime中的属性

定义

属性在Runtime库中的定义是:

1
2
3
4
5
6
7
8
9
10
// in objc-private.h
typedef struct property_t *objc_property_t;

// in objc-runtime-new.h
struct property_t {
// 属性名
const char *name;
// 特征字符串
const char *attributes;
};

特征字符串

上面介绍@Property语法中参数描述了应该怎样和一个属性交互、编译器如何合成相应的访问方法以及自定义存取方法名。他们对应了属性定义中的特征字符串。如上面objcProper属性的特征字符串就是"T@\"NSObject\",&,N,V_objcProper" runtime中用objc_property_attribute_t来描述一个属性的特征,它的定义是:

1
2
3
4
5
/// Defines a property attribute
typedef struct {
const char *name; /**< The name of the attribute */
const char *value; /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

常见的属性描述和含义如下

属性含义 name value
属性类型 T NSObject/i/…
编码类型 C(copy) &(strong) W(weak) 空(assign) 等
非/原子性 空(atomic) N(Nonatomic)
变量名 V 属性名

属性的操作函数

常用的属性操作函数有

1
2
3
4
class_copyPropertyList()        //获取属性列表
property_getName() //获取属性名
property_getAttributes() //获取属性的特征字符串
property_copyAttributeList() //获取属性的特征列表

应用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)propertyApply
{
//获取类的属性列表
objc_property_t * propertyList = class_copyPropertyList([self class], &outCount);

for (unsigned int i = 0; i < outCount; i ++) {
objc_property_t property = propertyList[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);
}

free(propertyList);
}

打印结果

2016-12-20 17:34:25.104 FDRTBase[2594:593492] 属性描述为 T@”NSObject”,&,V_strongObject 的 strongObject

2016-12-20 17:34:25.104 FDRTBase[2594:593492] 属性的描述:T 值:@”NSObject”

2016-12-20 17:34:25.105 FDRTBase[2594:593492] 属性的描述:& 值:

2016-12-20 17:34:25.105 FDRTBase[2594:593492] 属性的描述:V 值:_strongObject

2016-12-20 17:34:25.105 FDRTBase[2594:593492] 属性描述为 T@”NSObject”,W,V_weakObject 的 weakObject

2016-12-20 17:34:25.105 FDRTBase[2594:593492] 属性的描述:T 值:@”NSObject”

2016-12-20 17:34:25.106 FDRTBase[2594:593492] 属性的描述:W 值:

2016-12-20 17:34:25.106 FDRTBase[2594:593492] 属性的描述:V 值:_weakObject

Model赋值

Model赋值

在开发中相信最常用的就是接口数据需要转化成Model了(当然如果你是直接从Dict取值的话。。。),很多开发者也都使用著名的第三方库如JsonModel、Mantle或MJExtension等。我们也可以使用runtime提供的属性操作函数去解析json来给Model赋值。它的原理是遍历Model自身所有属性,如果属性在json中有对应的值,则将其赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- (instancetype)initWithDictionary:(NSDictionary *)dictionary
{
self = [self init];

if (self) {
NSMutableArray *keys = [NSMutableArray array];

unsigned int outCount = 0;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);

for (int i = 0; i < outCount; i++) {
objc_property_t property = properties[i];

// 获得属性名
[keys addObject:[NSString stringWithCString:property_getName(property)
encoding:NSUTF8StringEncoding]];
}

[keys enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([dictionary valueForKey:obj]) {
[self setValue:dictionary[obj] forKey:obj];
}
}];

//手动释放
free(properties);
}

return self;
}

YYModel中Model赋值

YYModel是个应用比较广泛的Json-Model转化库,库中的YYClassInfo类获取类属性的方式同样是用了class_copyPropertyList函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//YYClassInfo.h
@property (nullable, nonatomic, strong, readonly) NSDictionary<NSString *, YYClassPropertyInfo *> *propertyInfos;

· · ·

//获取方式
{
· · ·
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(cls, &propertyCount);
if (properties) {
NSMutableDictionary *propertyInfos = [NSMutableDictionary new];
_propertyInfos = propertyInfos;
for (unsigned int i = 0; i < propertyCount; i++) {
YYClassPropertyInfo *info = [[YYClassPropertyInfo alloc] initWithProperty:properties[i]];
if (info.name) propertyInfos[info.name] = info;
}
free(properties);
}
· · ·
}

但是他的赋值用了更底层的objc_msgSend

1
2
3
4
5
6
7
8
9
10
//YYModel的赋值方式
static void ModelSetValueForProperty(__unsafe_unretained id model,
__unsafe_unretained id value,
__unsafe_unretained _YYModelPropertyMeta *meta)
{
//···
//可以看出直接使用方法转发
((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
//···
}

快速归档

归档

归档时iOS开发中一种数据持久化存储方式,几乎任何类型的对象都能够被归档储存。iOS提供了一对归档和解档的类NSKeyedArchiverNSKeyedUnarchiver

1
2
3
4
5
6
7
8
9
10
// NSKeyedArchiver
// 一个对象归档为NSData
+ (NSData *)archivedDataWithRootObject:(id)rootObject;
// 将一个对象归档到指定路径的文件中
+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path;

// NSKeyedUnarchiver 常用方法
+ (nullable id)unarchiveObjectWithData:(NSData *)data;
+ (nullable id)unarchiveTopLevelObjectWithData:(NSData *)data error:(NSError **)error NS_AVAILABLE(10_11, 9_0) NS_SWIFT_UNAVAILABLE("Use 'unarchiveTopLevelObjectWithData(_:) throws' instead");
+ (nullable id)unarchiveObjectWithFile:(NSString *)path;

上面的归档和解档方法会自动调用NSCoding协议函数,所以一个类要想具有归档功能,必须实现NSCoding协议

1
2
3
4
5
//NSCoding协议
@protocol NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder; // NS_DESIGNATED_INITIALIZER
@end

实现NSCoding协议

我们可以使用Runtime提供的函数来遍历一个类的成员变量,并对属性进行encode和decode操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Runtime实现归档
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];

if (self) {
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);

for (int i = 0; i < outCount; i++) {
Ivar var = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(var)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
free(ivars);
}

return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder
{
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([self class], &outCount);

for (int i = 0; i < outCount; i++) {
Ivar ivar = ivars[i];
NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}

free(ivars);
}

YYModel的轮子

YYModel库中提供了YYShadow类他实现了NSCoding类,所以如果我们要实现归档功能,我们可以简单的继承YYShadow类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)modelEncodeWithCoder:(NSCoder *)aCoder{
//...
case YYEncodingTypeObject: {
id value = ((id (*)(id, SEL))(void *)objc_msgSend)((id)self, propertyMeta->_getter);
if (value && (propertyMeta->_nsType || [value respondsToSelector:@selector(encodeWithCoder:)])) {
if ([value isKindOfClass:[NSValue class]]) {
if ([value isKindOfClass:[NSNumber class]]) {
[aCoder encodeObject:value forKey:propertyMeta->_name];
}
} else {
[aCoder encodeObject:value forKey:propertyMeta->_name];
}
}
}
//...
}

这里可以使用了属性并没有使用Ivar,

思考:这里用应该用property,用Ivar不太好

参考

Objective-C中的@property