前一章介绍了动态创建类,这章主要讲动态添加属性。
一、动态添加实例变量Ivar:
通过函数class_addIvar()
添加属性,更准确的说是添加成员变量,函数定义如下
OBJC_EXPORT BOOL
class_addIvar(Class _Nullable cls, const char * _Nonnull name, size_t size,
uint8_t alignment, const char * _Nullable types)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
使用该方法需要注意:
1、该函数只有在动态添加的类中使用有效,且只能在objc_allocatelasspair()
之后和objc_registerClassPair()
之前调用,在已经存在的类中使用该函数无效;
2、参数cls类不能是元类,不支持向元类添加实例变量;
3、如果成功添加了实例变量,返回YES,否则返回NO(例如,该类已包含具有该名称的实例变量)。
4、Ivar
即instance variable
简写,意为实例变量,也就是我们通常说的成员变量,它和@property
属性不同,它没有自己的setter
和getter
方法。
举个例子,动态创建一个Person类,通过函数class_addIvar()
为其动态添加成员变量:
Class Person = objc_allocateClassPair(NSObject.class, "Person", 0);
class_addIvar(Person, "_name", sizeof(NSString *), log2(sizeof(NSString *)), "@");
class_addIvar(Person, "_dict", sizeof(NSDictionary *), log2(sizeof(NSDictionary *)), "@");
objc_registerClassPair(Person);
给成员变量赋值:先通过函数class_getInstanceVariable()
获取成员变量Ivar
,然后通过函数object_setIvar()
给成员变量赋值,代码如下:
id person = Person.new; // 相当于调用 alloc 和 init 方法
Ivar ivar1 = class_getInstanceVariable(Person, "_name");
Ivar ivar2 = class_getInstanceVariable(Person, "_dict");
NSString *str = @"小玉子";
NSDictionary *dic = @{@"height":@"183cm"};
object_setIvar(person, ivar1, [str copy]);
object_setIvar(person, ivar2, [dic copy]);
通过函数object_getIvar()
获取成员变量的值,代码如下:
id value1 = object_getIvar(person, ivar1);
id value2 = object_getIvar(person, ivar2);
NSLog(@"%@",value1);
NSLog(@"%@",value2);
输出结果:
小玉子
{
height = 183cm;
}
我们来遍历一下Person类中的成员变量和属性,通过函数class_copyIvarList()
获取成员变量列表,通过函数class_copyPropertyList()
获取类的property属性列表,代码如下:
unsigned int ivarCount = 0;
Ivar *ivars = class_copyIvarList(Person, &ivarCount);
for (int i = 0; i < ivarCount; i ++) {
Ivar ivar = ivars[i];
NSLog(@"名字:%s----类型:%s",ivar_getName(ivar),ivar_getTypeEncoding(ivar));
}
free(ivars);
unsigned int propertyCount;
objc_property_t *properties = class_copyPropertyList(Person, &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
NSLog(@"名字:%s----属性:%s",property_getName(property),property_getAttributes(property));
}
free(properties);
打印结果:
名字:_name----类型:@
名字:_dict----类型:@
@
表示对象类型,显然,Person类的成员变量列表中存在我们刚刚动态添加的两个成员变量_name和_dict,而property属性列表却没有值。添加成员变量Ivar
其实就相当于我们通常写的下面代码:
@interface Person : NSObject
{
NSString *_name;
}
// @property (nonatomic, copy) NSString *name;
@end
对于动态创建的类我们通过函数class_addIvar()
添加实例变量, 它会改变一个已有类的内存布局,一般是通过objc_allocateClassPair()
动态创建一个class,才能调用函数class_addIvar()
创建Ivar,最后通过函数objc_registerClassPair()
注册class。而对于已经存在的类我们用函数class_addProperty()
方法来添加属性,下面我们动态添加property属性对比一下。
二、动态添加property属性:
动态添加property属性,必须是已经存在的类,不同于动态添加Ivar
。在添加property属性之前我们先创建一个Person类:
@interface Person : NSObject
// 静态添加property属性 grade
@property (nonatomic, copy) NSString *grade;
@end
这里我们要先知道@property
和Ivar
的区别,当我们添加一个property属性的时候,系统会自动为我们生成属性的setter、getter方法,还会生成一个带有下划线的成员变量也就是Ivar
,所以如果要动态添加property属性,就要满足三点:属性的setter、getter方法和一个对应的Ivar
。看下面一段代码:
- (void)addPropertyWithPropertyName:(NSString *)propertyName
{
Class pClass = Person.class;
// type(举例NSString类型)
objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] };
// C = copy
objc_property_attribute_t ownership0 = { "C", "" };
// N = nonatomic
objc_property_attribute_t ownership = { "N", "" };
// instance variable name
objc_property_attribute_t backingivar = { "V", [[NSString stringWithFormat:@"_%@", propertyName] UTF8String] };
// 对比系统生成的顺序,例如我们静态添加的grade属性
objc_property_attribute_t attrs[] = { type, ownership0, ownership, backingivar};
BOOL isProperty = class_addProperty(pClass, [propertyName UTF8String], attrs, 4);
if (!isProperty) {
// 添加属性失败,替换属性
class_replaceProperty(pClass, [propertyName UTF8String], attrs, 4);
}
//添加get和set方法
class_addMethod(pClass, NSSelectorFromString(propertyName), (IMP)getter, "@@:");
class_addMethod(pClass, NSSelectorFromString([NSString stringWithFormat:@"set%@:",[propertyName capitalizedString]]), (IMP)setter, "v@:@");
}
id getter(id self, SEL _cmd) {
NSString *key = [NSString stringWithFormat:@"_%@",NSStringFromSelector(_cmd)];
Ivar ivar = class_getInstanceVariable([self class], key.UTF8String);
id value = object_getIvar(self, ivar);
return value;
}
void setter(id self, SEL _cmd, id newValue) {
//移除set
NSString *key = [NSStringFromSelector(_cmd) stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@""];
//首字母小写(不支持开发者故意把属性名称首字母大写的可能)
NSString *head = [key substringWithRange:NSMakeRange(0, 1)];
head = [head lowercaseString];
key = [key stringByReplacingCharactersInRange:NSMakeRange(0, 1) withString:head];
//移除后缀 ":"
key = [key stringByReplacingCharactersInRange:NSMakeRange(key.length - 1, 1) withString:@""];
Ivar ivar = class_getInstanceVariable([self class], [NSString stringWithFormat:@"_%@",key].UTF8String);
object_setIvar(self, ivar, newValue);
}
添加操作
[self addPropertyWithPropertyName:@"name"];
Person *p = Person.new;
// KVC 赋值
[p setValue:@"小玉子" forKey:@"name"];
NSLog(@"%@",[p valueForKey:@"name"]);
打印结果:
(null)
通过上述代码,我们为Person类添加了一个名为name的property属性,然后为其手动添加了setter和getter方法,然后用KVC的方式来给属性赋值(使用点语法或者成员变量赋值,Person对象识别不了),然而打印结果属性name的值却为空。具体原因下面说,我们先来遍历一下Person类的property属性列表和Ivar实例变量列表,结果如下:
Ivar 名字:_grade----类型:@"NSString"
Property 名字:name----属性:T@"NSString",C,N,V_name
Property 名字:grade----属性:T@"NSString",C,N,V_grade
打印分析:对于我们为Person类静态添加的grade属性,系统为我们自动生成一个相应的_grade的成员变量(Ivar
),而对于我们动态添加的name属性,系统并没有为我们生成相应的_name成员变量;只有stter和getter方法没有成员变量我们就没办法给属性赋值,如何为其添加Ivar
呢?显然class_addIvar()
方法是不可以的,因为它只能为动态类添加成员变量。这里有两个方法可以保存name值的解决:1.手动为name属性添加成员变量_name
{
NSString *_name;
}
再来看打印结果,属性name的值果然可以保存并获取:
小玉子
Ivar 名字:_name----类型:@"NSString"
Ivar 名字:_grade----类型:@"NSString"
Property 名字:name----属性:T@"NSString",C,N,V_name
Property 名字:grade----属性:T@"NSString",C,N,V_grade
2.通过别的方式在调用setter方法时将值保存起来,然后调用getter方法时再将值取出并返回。
动态添加property属性小结:
1、动态添加property属性需要在已存在的类中添加;
2、动态添加property属性需要手动实现setter和getter方法;
3、动态添加property属性手动添加成员变量或者别的方式保存值,比较麻烦,使用不多;
4、用一个公式表示Ivar和property的区别:@property = Ivar + setter + getter
。
三、添加关联(Associated)属性:
该方法以前讲过传送门。