本文共 6302 字,大约阅读时间需要 21 分钟。
前一章介绍了,这章主要讲动态添加属性。
一、动态添加实例变量Ivar:
通过函数class_addIvar()
添加属性,更准确的说是添加成员变量,函数定义如下
OBJC_EXPORT BOOLclass_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_nameProperty 名字: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_nameProperty 名字: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)属性:
该方法以前讲过。
转载地址:http://sgivi.baihongyu.com/