美文网首页JavaEEiOS开发之笔记摘录
RunTime运行时之动态添加属性

RunTime运行时之动态添加属性

作者: 平安喜乐698 | 来源:发表于2018-03-05 16:13 被阅读0次
    目录
    
    
    

    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:
    

    相关文章

      网友评论

        本文标题:RunTime运行时之动态添加属性

        本文链接:https://www.haomeiwen.com/subject/hktqfftx.html