成员变量、属性

runtime成员变量&属性

1.成员变量

Ivar

objc/runtime.h中,Ivar的描述如下:

1
2
3
4
5
6
7
8
9
10
11
/// An opaque type that represents an instance variable.
typedef struct objc_ivar *Ivar;

struct objc_ivar {
char *ivar_name
char *ivar_type
int ivar_offset
#ifdef __LP64__
int space
#endif
}

Ivar,对象的成员变量,是一个指向objc_ivar结构体的指针,包括名字与类型。

获取成员变量

常用操作函数有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获取所有成员变量
class_copyIvarList

// 获取成员变量名
ivar_getName

// 获取成员变量类型编码
ivar_getTypeEncoding

// 获取指定名称的成员变量
class_getInstanceVariable

// 获取某个对象成员变量的值
object_getIvar

// 设置某个对象成员变量的值
object_setIvar

2.属性

属性的定义

1
2
// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;

属性,一个指向objc_property结构体,通常以@property关键字来声明。

属性成员变量是两个独立的结构体,但两者被紧紧关联在了一起,只不过在不同版本的编译器中稍有不同。

  • iOS 5以前

iOS5以前,使用属性通常需要三步:

  1. 大括号声明成员变量;
  2. @property 声明属性;
  3. @synthesize 手动合成属性与成员变量;

#示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@interface People()
{
//声明成员变量
NSString *_name;//以下划线开头的成员变量
NSString *nick;
}
//声明属性
@property (nonatomic,copy) NSString *name;
@property (nonatomic, copy) NSString *aliasName;
@end

@implementation People
//合成属性 绑定成员变量到指定的属性上 并在 getter、setter中使用此成员变量赋值
@synthesize name = _name;
@synthesize aliasName = nick;

- (void)test{
self.name = @"A";
self.aliasName = @"B";
NSLog(@"++self.name='%@',++self.aliasName='%@',++_name='%@',++nick='%@'",self.name,self.aliasName,_name,nick);
}
@end

1.示例中{ }里声明成员变量时可以使用下划线_开头,也可以不带下划线;

2.@property的作用是自动生成属性的 setter\getter 方法的声明,即

1
2
-(void)setName:(NSString*)aName;
-(NSString*)name;

3.@synthesize的作用是指定一个成员变量,将其绑定到属性上。然后实现属性的 getter\setter 并在其中使用此成员变量存取值;

1
2
3
4
5
6
7
8
9
10
- (NSString *)name{
return _name;
}

- (void)setName:(NSString *)aName{
if (aName && _name && ![aName isEqualToString:_name]) {
return;
}
_name = aName;
}
  • iOS 5以后

iOS5以后,苹果将编译器从 GCC 转为 LLVM,以 @property 声明的属性name,编译器会默认为其生成一个以下划线开头的成员变量,同时实现 setter/getter 方法并在其中使用"_name"存取值,所以编译器帮我们省去了部分操作。 在.m文件中,我们可以直接使用_name,也可以使用self.name。其中,后者实际上是调用了属性name的 getter\setter 函数。

ps:iOS5之后,@synthesize仍可以继续使用。如果@synthesize后为属性指定的这个成员变量并不存在,那么编译器会自动帮我们创建一个新的同类型的成员变量。例如@synthesize aliasName = NotExistIvar;,我们的{ }中并未声明此成员变量NotExistIvar,编译器会自动帮我们创建~

获取属性

属性的常用操作函数:

1
2
3
4
5
6
7
8
9
10
11
// 获取所有属性
class_copyPropertyList

// 获取属性名
property_getName

// 获取属性特性描述字符串
property_getAttributes

// 获取所有属性特性
property_copyAttributeList

其中,property_getAttributes返回的是一个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;

常用的值如下:

1
2
3
4
属性类型  name值:T  value:NSString
编码类型 name值:C(copy)、&(strong\retain)、W(weak)、R(readonly)、空(assign) 等 value:无
非/原子性 name值:空(atomic) N(Nonatomic) value:无
属性名称 name值:V value:自定义的名字

3.查询示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//IvarTool.h
@interface IvarTool : NSObject
{
double ivar_defaultDouble;//默认作用域
@package
int ivar_packInt;
@public
float ivar_publicFloat;
@protected
NSString *ivar_protectedStr;
@private
NSDictionary *ivar_privateDic;
}
@property(nonatomic,copy) NSString *property_string;
//成员变量列表
+ (void)ivarList;
//属性列表
+ (void)propertyList;
@end
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//IvarTool.m
#import "IvarTool.h"
#import <objc/runtime.h>

@interface IvarTool()
{
int ivar_int;
@public
NSArray *ivar_array;
}
@property(nonatomic, strong) NSDictionary *property_dic;
@end

@implementation IvarTool

+ (void)ivarList
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([IvarTool class], &count);

for (int i = 0; i<count; i++)
{
Ivar ivar = ivars[i];
const char *charName = ivar_getName(ivar);
const char *charType = ivar_getTypeEncoding(ivar);

NSLog(@"Name:%s,Type:%s",charName,charType);
}

free(ivars);
}


+ (void)propertyList
{
unsigned int count = 0;
objc_property_t *properties = class_copyPropertyList([IvarTool class], &count);

for (int i = 0; i<count; i++)
{
objc_property_t aProperty = properties[i];
const char *charName = property_getName(aProperty);
const char *charType = property_getAttributes(aProperty);
NSLog(@"Name:%s,Type:%s",charName,charType);
}

free(properties);
}
@end

调用[IvarTool ivarList],查看成员变量,输出日志如下:

1
2
3
4
5
6
7
8
9
Name:ivar_defaultDouble,Type:d
Name:ivar_packInt,Type:i
Name:ivar_publicFloat,Type:f
Name:ivar_protectedStr,Type:@"NSString"
Name:ivar_privateDic,Type:@"NSDictionary"
Name:ivar_int,Type:i
Name:ivar_array,Type:@"NSArray"
Name:_property_string,Type:@"NSString"
Name:_property_dic,Type:@"NSDictionary"

调用[IvarTool propertyList],查看属性列表,输出日志如下:

1
2
Name:property_dic,Type:T@"NSDictionary",&,N,V_property_dic
Name:property_string,Type:T@"NSString",C,N,V_property_string

小结

  • 编译器会自动为属性生成对应的以下划线开头的成员变量;
  • 无论成员变量的访问权限如何,是在.h 还是 .m 中声明的,class_copyIvarList 都能获取的到;
  • 无论属性定义在 .h 还是 .m 中,class_copyPropertyList 能获取全部属性;
  • class_copyIvarList 与 class_copyPropertyList 都只能获取到当前类中定义的成员变量或属性,获取不到其父类的中定义的;

4.访问权限

关于成员变量的访问权限,Apple官方描述如下:

@public: 导入头文件的地方,都能直接访问(.m文件中声明的成员变量除外);

@protected: 本类及其子类实例方法中可见;

@private: 只对本类可见,对其子类不可见;

@package: 比较特殊,访问范围介于publicprivate之间,只要处于同一个镜像或可执行文件中时相当于public,在实现其的镜像或可执行文件之外时相当于private,下面是Apple原文中的描述;

Using the modern runtime, an @package instance variable has @public scope inside the executable image that implements the class, but acts like @private outside.
The @package scope for Objective-C instance variables is analogous to private_extern for C variables and functions. Any code outside the class implementation’s image that tries to use the instance variable gets a link error.
This scope is most useful for instance variables in framework classes, where @private may be too restrictive but @protected or @public too permissive.

默认值:

.h中默认为 @protected;

.m中默认为 @private;

#示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
IvarTool *tool = [[IvarTool alloc] init];

//下面几个会报错
tool -> ivar_defaultDouble;
tool -> ivar_protectedStr;
tool -> ivar_privateDic;
tool -> ivar_int;
tool -> ivar_array;
tool.property_dic;

//下面的调用能正常使用
tool -> ivar_packInt;
tool -> ivar_publicFloat;
tool.property_string;

return YES;
}

若想实现成员变量支持外部访问,需要在头文件的声明中给该成员变量添加@public修饰符,同时在外部访问时使用->。m文件内即使成员变量添加@public修饰符,外部访问时仍会报错,因为它只对m文件内可见。

5.快速序列化

原理:利用runtime提供的函数,遍历model的属性,并对属性进行decodeencode

#示例:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
//ModelSerialiseHelper.h

#ifndef ModelSerialiseHelper_h
#define ModelSerialiseHelper_h

#import <objc/runtime.h>
/*
序列化工具使用示例:

1、TLObject头文件中声明NSCoding协议

@interface TLObject : NSObject<NSCoding>
@property (nonatomic, copy) NSString *mName;
@property (nonatomic) BOOL mIsTrue;
@property (nonatomic) NSInteger mInteger;
@property (nonatomic) UIImage *mImage;
@end

2、m文件中导入头文件和宏即可

#import "TLObject.h"
#import "ModelSerialiseHelper.h"
@implementation TLObject

ModelCodingProtocol()

@end
*/


#define ModelCodingProtocol()\
- (id)initWithCoder:(NSCoder *)coder\
{\
Class cls = [self class];\
while (cls != [NSObject class])\
{\
unsigned int count = 0;\
Ivar *ivarList = class_copyIvarList([cls class], &count);\
for (int i = 0; i < count; i++)\
{\
const char *varName = ivar_getName(*(ivarList + i));\
NSString *key = [NSString stringWithUTF8String:varName];\
NSString *aStr = [key substringToIndex:1];\
if ([aStr isEqualToString:@"_"]) {\
key = [key substringFromIndex:1];\
}\
id varValue = [coder decodeObjectForKey:key];\
if (varValue) {\
[self setValue:varValue forKey:key];\
}\
}\
free(ivarList);\
cls = class_getSuperclass(cls);\
}\
return self;\
}\
- (void)encodeWithCoder:(NSCoder *)coder\
{\
Class cls = [self class];\
while (cls != [NSObject class])\
{\
unsigned int count = 0;\
Ivar *ivarList = class_copyIvarList([cls class], &count);\
for (int i = 0; i < count; i++)\
{\
const char *varName = ivar_getName(*(ivarList + i));\
NSString *key = [NSString stringWithUTF8String:varName];\
NSString *aStr = [key substringToIndex:1];\
if ([aStr isEqualToString:@"_"]) {\
key = [key substringFromIndex:1];\
}\
id varValue = [self valueForKey:key];\
if (varValue) {\
[coder encodeObject:varValue forKey:key];\
}\
}\
free(ivarList);\
cls = class_getSuperclass(cls);\
}\
}\

#define ModelCopyProtocol()\
- (id)copyWithZone:(NSZone *)zone\
{\
id copy = [[[self class] allocWithZone:zone] init];\
Class cls = [self class];\
while (cls != [NSObject class])\
{\
unsigned int count = 0;\
Ivar *ivarList = class_copyIvarList([cls class], &count);\
for (int i = 0; i < count; i++)\
{\
const char *varName = ivar_getName(*(ivarList + i));\
NSString *key = [NSString stringWithUTF8String:varName];\
NSString *aStr = [key substringToIndex:1];\
if ([aStr isEqualToString:@"_"]) {\
key = [key substringFromIndex:1];\
}\
id varValue = [self valueForKey:key];\
if (varValue) {\
[copy setValue:varValue forKey:key];\
}\
}\
free(ivarList);\
cls = class_getSuperclass(cls);\
}\
return copy;\
}\

#endif /* ModelSerialiseHelper_h */

上述代码为ModelSerialiseHelper.h,其中:

  • ModelCopyProtocol() 封装了copy协议的相关实现代码;
  • ModelCodingProtocol() 封装了encode和decode的相关代码;

在需要实现这两个协议的model中,导入ModelSerialiseHelper.h并引用上述两个宏定义即可。

6.json字典转Model

原理:获取模型的属性列表,通过kvc从字典中取值并赋值给对应的属性字段。

#示例:

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
34
35
36
37
38
39
40
#import "NSObject+JsonToModel.h"
#import <objc/runtime.h>

@implementation NSObject (JsonToModel)

- (instancetype)initModelWithDictionary:(NSDictionary *)jsonDic
{
if (self = [self init])
{
NSMutableArray * keysArr;
if (!jsonDic) {
return nil;
}

keysArr = [NSMutableArray array];

//获取类的属性及属性对应的类型
unsigned int count;
objc_property_t * properties = class_copyPropertyList([self class], &count);
for (int i = 0; i < count; i++)
{
objc_property_t property = properties[i];
//获取属性名
const char *charName = property_getName(property);
NSString *propertyName = [NSString stringWithCString:charName encoding:NSUTF8StringEncoding];

if (propertyName.length) {
[keysArr addObject:propertyName];
}
}
free(properties);

//给属性赋值
for (NSString * key in keysArr) {
[self setValue:jsonDic[key] forKey:key];
}
}
return self;
}
@end

调用示例:

1
2
NSDictionary *dic = @{@"name":@"xxx",@"sex":@(1),@"age":@(20)};
PersonModel *person = [[PersonModel alloc] initModelWithDictionary:dic];

这里的示例只是一个简单的场景,实际应用中可能会有更复杂的需求,视情况做响应修改即可。


相关参考:

#©Apple


成员变量、属性
https://davidlii.cn/2017/08/25/runtime-ivar.html
作者
Davidli
发布于
2017年8月25日
许可协议