iOS-动态添加属性

前一章介绍了动态创建类,这章主要讲动态添加属性。

一、动态添加实例变量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、Ivarinstance variable简写,意为实例变量,也就是我们通常说的成员变量,它和@property属性不同,它没有自己的settergetter方法。

举个例子,动态创建一个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

这里我们要先知道@propertyIvar的区别,当我们添加一个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)属性:

该方法以前讲过传送门

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值