iOS Runtime之——关联对象探索

iOS Runtime之——关联对象探索

  • 1.概述
  • 2._object_set_associative_reference创建关联对象
  • 3._object_get_associative_reference读取关联对象
  • 4._object_remove_assocations移除关联对象
  • 5.应用实例一:UITapGestureRecongnizer传递数据

1.概述

作为OC扩展机制的两个重要特性,使用Category来扩展方法,使用Associated Object来扩展属性,二者完美地扩展了OC的扩展机制。上文我们分析了Category的底层原理,本文我们对Associated Object的底层原理进行分析。

  源码基于objc4-750,下载地址:objc4-750。

  通过分析runtime源码发现,关联属性并未定义到category_t中,而是定义到AssociationsManager来进行管理的,包括关联属性的添加、获取、移除等操作。
  定义AssociationsHashMap来对关联列表进行具体操作。AssociationsManager通过输入的对象作为地址,取出对象所对应的关联列表,然后通过key值取出关联列表中的关联属性ObjcAssociationMap,其中ObjectAssociationMap又定义了关联策略和关联值。这样就构成联对象的基本框架结构。

  下面我们分别对_object_set_associative_reference_object_get_associative_reference_object_remove_assocations的源码进行解读。

2._object_set_associative_reference创建关联对象

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
    //移除旧的关联属性,传nil替换
    ObjcAssociation old_association(0, nil);
    //进行内存管理!!!
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
        //初始化 HashMap,以对象为单位
        AssociationsHashMap &associations(manager.associations());
        //取出要关联属性对象的地址,当前对象的地址按位取反(key)
        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()) {
                //取出对象关联的所有属性列表
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                 //
                //取出某个属性的关联字典 如果不为空就对旧值就行替换操作,如果为空就添加到关联字典里边
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    //ObjcAssociation
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                //key对象的hashMap不存在,则创建hashMap:ObjectAssociationMap
                //将要关联的属性和策略封装到ObjcAssociation,并根据key添加到这个nashMap中
                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.
            //关联的属性为空,取出之前的关联值,并移除,相当于removeObjectForKey
            //同样是根据对象内存地址disguised_object找到它的关联属性列表,再根据key值取出ObjectAssociationMap,最后移除
            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);
}

AssociationsManager类:

class AssociationsManager {
    // associative references: object pointer -> PtrPtrHashMap.
    static AssociationsHashMap *_map;
public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

  主要管理AssociationsHashMap对象,同时在运行构造函数时执行加锁,运行析构函数时执行解锁。防止多线程访问时,对同一个_map进行多次访问,确保了_map变量访问的安全性。

3._object_get_associative_reference读取关联对象

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;
}

  _object_get_associative_reference操作思想及逻辑与_object_set_associative_reference类似。

4._object_remove_assocations移除关联对象

关联对象什么时候释放? 是在dealloc时随着原对象释放而释放的。
  首先_object_set_associative_reference函数要执行object->setHasAssociatedObjects():将关联对象与当前的类设置关联关系。

objc_object::setHasAssociatedObjects()
{
    if (isTaggedPointer()) return;
 retry:
    isa_t oldisa = LoadExclusive(&isa.bits);
    isa_t newisa = oldisa;
    if (!newisa.nonpointer  ||  newisa.has_assoc) {
        ClearExclusive(&isa.bits);
        return;
    }
    newisa.has_assoc = true;
    if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;
}

  该函数中设置isa的指针,设置标识has_assoc,该标识用于在对象销毁时,识别并销毁关联对象。
  Dealloc方法的调用顺序是从子类到父类,直至NSObject。NSObject运行的dealloc时,会进入其根函数rootDealloc

- (void)dealloc {
    _objc_rootDealloc(self);
}
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?
    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

  此处正是对包含isa.has_assoc的判断来调用object_dispose

id object_dispose(id obj)
{
    if (!obj) return nil;
    //销毁对象
    objc_destructInstance(obj);
    //释放内存
    free(obj);
    return nil;
}
void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();
        // This order is important.
        //析构c++
        if (cxx) object_cxxDestruct(obj);
        //移除关联对象
        if (assoc) _object_remove_assocations(obj);
        //ARC模式下,调用实例变量的release,移除weak引用
        obj->clearDeallocating();
    }
    return obj;
}
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());
}

  通过上述代码的分析,最终调用ReleaseValue来最终释放关联属性。这与我们的结论不谋而合,关联对象的释放是在dealloc时随着原对象释放而释放的。

总结:

  • 属性是通过方法访问实例变量的;
  • 使用AssociationsManager类管理AssociationsHashMap对象,AssociationsHashMap对象又在底层创建ObjectAssociationMap来存放关联对象;
  • 关联对象和类是通过isa指针关联标识的;
  • 关联对象释放是随着当前对象的生命周期(isa)进行的。

  简单来说关联属性的操作流程就是通过全局hashMap,根据关联对象查找对应的子hashMap,然后根据key值查找对应的属性值和管理策略。

5.应用实例一:UITapGestureRecongnizer传递数据

  比如对于一个控件,需要设置点击事件UITapGestureRecongnizer,并响应相关的操作。但是点击事件只能传递int类型的tag信息,无法携带NSString数据。那么如何实现NSString数据在UITapGestureRecongnizer中传递呢?使用Category,并在Category中添加关联属性
(1)定义分类 UITapGestureRecognizer+AssociatedObject.h

#import 
@interface UITapGestureRecognizer (AssociatedObject)
@property (nonatomic, strong) NSString * str;
@end

(2)实现str属性的setter和getter方法

#import "UITapGestureRecognizer+AssociatedObject.h"
#import 
static char * StrDataKey = "StrDataKey";
@implementation UITapGestureRecognizer (AssociatedObject)
- (void)setStr:(NSString *)str {
    //设置关联属性
    objc_setAssociatedObject(self, StrDataKey, str, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)str {
    //获取管理属性
    return objc_getAssociatedObject(self, StrDataKey);
}
@end

(3)在ViewConyroller中使用:

  • 首先导入Category头文件
#import "UITapGestureRecognizer+AssociatedObject.h"
  • 给控件添加手势并传递NSString数据
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    UITapGestureRecognizer * tapGes = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapAction:)];
    tapGes.str = @"123456";
    [self.testView addGestureRecognizer:tapGes];
}
- (void) tapAction:(UITapGestureRecognizer *)sender {
    UITapGestureRecognizer * tap = (UITapGestureRecognizer *)sender;
    NSLog(@"tap.str:%@", tap.str);
}
@end

  这样便实现了NSString数据在UITapGesturereReconizer事件中的传递,并进行下一步操作。其它类型的数据传递与此类似,修改关联属性的类型即可。

你可能感兴趣的:(iOS)