JavaEEiOS开发之笔记摘录

RunTime运行时之动态添加属性

2018-03-05  本文已影响0人  平安喜乐698
目录


1. 方法1: 动态关联对象

添加关联对象,传nil可以移除关联对象
void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
获取关联对象
id objc_getAssociatedObject(id object, const void * key)
移除所有的关联对象
void objc_removeAssociatedObjects(id object)
object:
  在分类中填self

key:
  key与关联的对象是一一对应关系。
  必须全局唯一。
  通常用@selector(methodName)作为key。

value:
  要关联的对象。

policy:
  关联策略。有五种关联策略。
  OBJC_ASSOCIATION_ASSIGN 等价于 @property(assign)。
  OBJC_ASSOCIATION_RETAIN_NONATOMIC等价于 @property(strong, nonatomic)。
  OBJC_ASSOCIATION_COPY_NONATOMIC等价于@property(copy, nonatomic)。
  OBJC_ASSOCIATION_RETAIN等价于@property(strong,atomic)。
  OBJC_ASSOCIATION_COPY等价于@property(copy, atomic)。
优点:
  使用方便快速
缺点:
  不能像遍历属性一样的遍历我们所有关联对象。
  不能移除制定的关联对象,只能通过removeAssociatedObjects方法移除所有关联对象。

例1

@property (nonatomic, assign) NSTimeInterval timeInterVal;

- (void)setTimeInterVal:(NSTimeInterval)timeInterVal{
    objc_setAssociatedObject(self, @selector(timeInterVal), @(timeInterVal), OBJC_ASSOCIATION_ASSIGN);
}
- (NSTimeInterval)timeInterVal{
    return [objc_getAssociatedObject(self, _cmd) doubleValue];
}

例2

@interface Person(Name)
@property (nonatomic,copy) NSString *name;
@end

@implementation Person(Name)
static NSString *name;
- (void)setName:(NSString *)nameStr{
    objc_setAssociatedObject(self,&name,nameStr,OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *) name{
    return objc_getAssociatedObject(self, &name);
}
@end
  1. 原理
关联对象并不是存储在被关联对象本身内存中,而是存储在全局的统一的一个AssociationsManager中。

从AssociationsManager关联对象管理者中找到关联对象,然后在查找关联对象的关联属性进行读取值。
关联结构图
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            //查看这个对象是否设置过关联
            if (i != associations.end()) {
                //找到了证明这个对象设置过关联
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                //查找这个key有没有对应的ObjcAssociation
                if (j != refs->end()) {
                    //有的话直接替换成新值
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    //没有的话根据这个key创建一个新的ObjcAssociation
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
               //没有设置过根据这个disguised_object创建一个新的ObjectAssociationMap
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            // setting the association to nil breaks the association.
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i !=  associations.end()) {
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    refs->erase(j);
                }
            }
        }
    }
    // release the old value (outside of the lock).
    if (old_association.hasValue()) ReleaseValue()(old_association);
}
id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}
id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            ObjectAssociationMap *refs = i->second;
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) {
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                    objc_retain(value);
                }
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    return value;
}
void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}
void _object_remove_assocations(id object) {
    vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        if (associations.size() == 0) return;
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
            // copy all of the associations that need to be removed.
            ObjectAssociationMap *refs = i->second;
            for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
                elements.push_back(j->second);
            }
            // remove the secondary table.
            delete refs;
            associations.erase(i);
        }
    }
    // the calls to releaseValue() happen outside of the lock.
    for_each(elements.begin(), elements.end(), ReleaseValue());
}

方法2. 动态添加Ivar

优点:
  能够通过遍历Ivar查到
缺点:
  不能在已存在的class中添加Ivar。
  即必须通过objc_allocateClassPair动态创建一个class,才能调用class_addIvar创建Ivar,最后通过objc_registerClassPair注册class
// 自定义方法
+ (void)addIvarWithtarget:(id)target withPropertyName:(NSString *)propertyName withValue:(id)value {  
    if (class_addIvar([target class], [propertyName UTF8String], sizeof(id), log2(sizeof(id)), "@")) {  
        YYLog(@"创建属性Ivar成功");  
    }  
}  
  
// 自定义方法
+ (id)getIvarValueWithTarget:(id)target withPropertyName:(NSString *)propertyName {  
    Ivar ivar = class_getInstanceVariable([target class], [propertyName UTF8String]);  
    if (ivar) {  
        id value = object_getIvar(target, ivar);  
        return value;  
    } else {  
        return nil;  
    }  
}  

方法3. 动态添加property

优点:
  可在已有的类中动态添加property,遍历属性时可遍历到。

缺点:
  相比方法1繁琐,且需要手动存取值。

使用到的几个方法

  1. class_getInstanceVariable:获取成员变量ivar
判断是否存在该成员变量ivar,如果存在则无需进行后续操作,不存在就动态添加。
 /**
    targetClass:  表示要添加的属性的类
    propertyName: 表示要添加的属性名
   */
   Ivar ivar = class_getInstanceVariable(targetClass, [[NSString stringWithFormat:@"_%@", propertyName] UTF8String]);
  1. class_addProperty:添加属性
成功(返回YES)则添加属性,失败(返回NO)则动态替换属性):

    // value:属性的赋值,根据属性值,判断属性类型
    objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([value class])] UTF8String] };
    objc_property_attribute_t ownership = { "&", "N" };
    objc_property_attribute_t backingivar  = { "V", [[NSString stringWithFormat:@"_%@", propertyName] UTF8String] };
    objc_property_attribute_t attrs[] = { type, ownership, backingivar };
   /**
    targetClass:   表示要添加的属性的类
    propertyName:  表示要添加的属性名
    attrs:        类特性列表
    attrsCount:    类特性个数
   */
   unsigned int attrsCount = 3;
   class_addProperty(targetClass, [propertyName UTF8String], attrs, attrsCount)
  1. class_replaceProperty:替换属性
属性添加失败时执行

class_replaceProperty(targetClass, [propertyName UTF8String], attrs, attrsCount);
  1. class_addMethod:添加方法
 (void)addMethod(Class _Nullable targetClass, NSString * propertyName)
{
    class_addMethod(targetClass, NSSelectorFromString(propertyName), (IMP)customGetter, "@@:");
    class_addMethod(targetClass, NSSelectorFromString([NSString stringWithFormat:@"set%@:",[propertyName capitalizedString]]), (IMP)customSetter, "v@:@");
}


// 以属性名为key去存储新属性值.
id customGetter(id targetClass, SEL _targetCmd) {
    if (dictCustomerProperty == nil) {
        dictCustomerProperty = [NSMutableDictionary new];
    }
    NSString *key = NSStringFromSelector(_targetCmd);
    return [dictCustomerProperty objectForKey:key];
}

void customSetter(id targetClass, SEL _targetCmd, id newValue) {
    //移除set
    NSString *key = [NSStringFromSelector(_targetCmd) 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:@""];
    
    if (dictCustomerProperty == nil) {
        dictCustomerProperty = [NSMutableDictionary new];
    }
    
    [dictCustomerProperty setObject:newValue forKey:key];
}

封装

创建一个类DynamicProperty

static NSMutableDictionary *dictCustomerProperty;

// 在目标target上添加属性,属性名propertyname,值value
+ (void)addPropertyWithtarget:(id)target withPropertyName:(NSString *)propertyName withValue:(id)value {
    //先判断有没有这个属性,没有就添加,有就返回
    Ivar ivar = class_getInstanceVariable([target class], [[NSString stringWithFormat:@"_%@", propertyName] UTF8String]);
    if (ivar) {
        return;
    }
    
    objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([value class])] UTF8String] };
    objc_property_attribute_t ownership = { "&", "N" };
    objc_property_attribute_t backingivar  = { "V", [[NSString stringWithFormat:@"_%@", propertyName] UTF8String] };
    objc_property_attribute_t attrs[] = { type, ownership, backingivar };
    if (class_addProperty([target class], [propertyName UTF8String], attrs, 3)) {
        
        //添加get和set方法
        class_addMethod([target class], NSSelectorFromString(propertyName), (IMP)customGetter, "@@:");
        class_addMethod([target class], NSSelectorFromString([NSString stringWithFormat:@"set%@:",[propertyName capitalizedString]]), (IMP)customSetter, "v@:@");
        
        //赋值
        [target setValue:value forKey:propertyName];
        NSLog(@"%@", [target valueForKey:propertyName]);
        
        NSLog(@"创建属性Property成功");
    } else {
        class_replaceProperty([target class], [propertyName UTF8String], attrs, 3);
        //添加get和set方法
        class_addMethod([target class], NSSelectorFromString(propertyName), (IMP)customGetter, "@@:");
        class_addMethod([target class], NSSelectorFromString([NSString stringWithFormat:@"set%@:",[propertyName capitalizedString]]), (IMP)customSetter, "v@:@");
        
        //赋值
        [target setValue:value forKey:propertyName];
    }
}

id customGetter(id self1, SEL _cmd1) {
    if (dictCustomerProperty == nil) {
        dictCustomerProperty = [NSMutableDictionary new];
    }
    NSString *key = NSStringFromSelector(_cmd1);
    return [dictCustomerProperty objectForKey:key];
}

void customSetter(id self1, SEL _cmd1, id newValue) {
    //移除set
    NSString *key = [NSStringFromSelector(_cmd1) 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:@""];
    
    if (dictCustomerProperty == nil) {
        dictCustomerProperty = [NSMutableDictionary new];
    }
    
    [dictCustomerProperty setObject:newValue forKey:key];
}

在需要添加属性的分类中

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [DynamicProperty addPropertyWithtarget:[self new] withPropertyName:@"name" withValue:[NSString new]];
        [DynamicProperty addPropertyWithtarget:[self new] withPropertyName:@"childArr" withValue:[NSArray new]];
    });
}

方法4. 通过KVC添加

这种方式不属于动态运行时处理。

重写setValue:forUndefinedKey、valueForUndefinedKey:
上一篇 下一篇

猜你喜欢

热点阅读