關(guān)聯(lián)對(duì)象詳解

分類(lèi)(category)與關(guān)聯(lián)對(duì)象(Associated Object)作為objective-c的擴(kuò)展機(jī)制的兩個(gè)特性:分類(lèi):可以通過(guò)它來(lái)擴(kuò)展方法,Associated Object:可以通過(guò)它來(lái)擴(kuò)展屬性。
在iOS開(kāi)發(fā)中,可能Category比較常見(jiàn),相對(duì)的Associated Object,就用的比較少,要用它之前,必須導(dǎo)入<objc/runtime.h>的頭文件。

一 基本使用

關(guān)聯(lián)對(duì)象提供了以下API:

// 1.添加關(guān)聯(lián)對(duì)象:
void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
// 2.獲得關(guān)聯(lián)對(duì)象:
id objc_getAssociatedObject(id object, const void * key)
// 3.移除所有的關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object)

在添加關(guān)聯(lián)對(duì)象的方法中有一個(gè)policy屬性,它是一個(gè)枚舉值,對(duì)應(yīng)我們平時(shí)定義屬性時(shí)設(shè)置的修飾詞

objc_AssociationPolicy 對(duì)應(yīng)的修飾符
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC strong, nonatomic
OBJC_ASSOCIATION_COPY_NONATOMIC copy, nonatomic
OBJC_ASSOCIATION_RETAIN strong, atomic
OBJC_ASSOCIATION_COPY copy, atomic

下面是使用示例:

#import <Foundation/Foundation.h>

@interface Person : NSObject

@end

#import "Person.h"
#import <objc/runtime.h>
@implementation Person

@end

#import "Person.h"
@interface Person (Baba)

@property(assign,nonatomic) int age;
@property(copy,nonatomic) NSString *name;

@end

#import "Person+Baba.h"
#import <objc/runtime.h>
@implementation Person (Baba)

-(void)setAge:(int)age {

    objc_setAssociatedObject(self, @selector(age),@(age), OBJC_ASSOCIATION_ASSIGN);
}

- (int)age{
   return [objc_getAssociatedObject(self, @selector(age)) intValue];
}

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);

}

- (NSString *)name {
   return objc_getAssociatedObject(self, @selector(name));
}

@end

#include<stdio.h>
#include "Person.h"
#import <Foundation/Foundation.h>
#import "Person+Baba.h"
 int main()
{
    Person *person = [[Person alloc] init];
    person.age = 10;
    person.name = @"張三";
    NSLog(@"姓名:%@,年齡:%d",person.name,person.age);

}

output:
2019-02-11 13:08:18.630262+0800 test[7310:121043] 姓名:張三,年齡:10

我們利用關(guān)聯(lián)對(duì)象給分類(lèi)添加屬性,使用是很簡(jiǎn)單的,就三個(gè)方法,下面我們看一下它的底層實(shí)現(xiàn)原理,當(dāng)然了,我們主要研究三個(gè)關(guān)聯(lián)對(duì)象方法的底層實(shí)現(xiàn)。

二 底層原理

我們先來(lái)看一下 void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)底層實(shí)現(xiàn),我們?cè)趏bjc源代碼,objc-runtime.mm可以看到


void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

id objc_getAssociatedObject(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}

void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object);
    }
}

我們跟進(jìn)去看一下

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()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) {
                    old_association = j->second;
                    j->second = ObjcAssociation(policy, new_value);
                } else {
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else {
                // create the new association (first time).
                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);
}

我們可以看到與實(shí)現(xiàn)關(guān)聯(lián)對(duì)象相關(guān)的幾個(gè)對(duì)象對(duì)象如下:
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
它們之間的關(guān)系圖如下:

image

根據(jù)文初示例代碼 objc_setAssociatedObject(self, @selector(age),@(age), OBJC_ASSOCIATION_ASSIGN);我們給出對(duì)應(yīng)結(jié)構(gòu)圖如下:

image

關(guān)聯(lián)對(duì)象底層就是這幾個(gè)對(duì)象相互配合實(shí)現(xiàn)的
AssociationsManager:操作所有的關(guān)聯(lián)屬性 和 獲取關(guān)聯(lián)屬性 移除關(guān)聯(lián)屬性
AssociationsHashMap:存儲(chǔ)這通過(guò)傳遞進(jìn)來(lái)的對(duì)象地址作為key, ObjectAssociationMap為value的映射對(duì)象,可以存儲(chǔ)不同對(duì)象的關(guān)聯(lián),擴(kuò)展性強(qiáng)。
ObjectAssociationMap:傳遞進(jìn)來(lái)的key為key, ObjcAssociation作為value
ObjcAssociation:存儲(chǔ)這關(guān)聯(lián)策略和關(guān)聯(lián)的值。
下面我們來(lái)看具體的源碼分析:

2.1 AssociationsManager
class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap. 這個(gè)_ map 里邊存儲(chǔ)的有關(guān)聯(lián)列表
public:
    AssociationsManager()   { _lock.lock(); }
    ~AssociationsManager()  { _lock.unlock(); }

    AssociationsHashMap &associations() { //可以看成是只初始化一次 類(lèi)似與單例
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

AssociationsManager 是一個(gè) C++的類(lèi) 用來(lái)進(jìn)行對(duì)關(guān)聯(lián)對(duì)象的屬性添加 和 查找 移除等操作,它里邊有個(gè) spinlock_t鎖 對(duì) _map 這個(gè)全局唯一的實(shí)例 進(jìn)行加鎖和解鎖 ,由于懶漢模式的單例 需要在多個(gè)線(xiàn)程訪(fǎng)問(wèn) _map 時(shí)候進(jìn)行加鎖保護(hù)

2.2 AssociationsHashMap
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };

關(guān)聯(lián)列表是一個(gè) hashMap 類(lèi)似于 OC 的 NSDictionary ,其中用 disguised_ptr_t 作為key, ObjectAssociationMap *作為一個(gè) value disguised_ptr_tuintptr_t 的類(lèi)型 intptr_tuintptr_t類(lèi)型用來(lái)存放指針地址。它們提供了一種可移植且安全的方法聲明指針,而且和系統(tǒng)中使用的指針長(zhǎng)度相同,對(duì)于把指針轉(zhuǎn)化成整數(shù)形式來(lái)說(shuō)很有用。可以把disguised_ptr_t理解為一個(gè)指針類(lèi)型的變量

2.3 ObjectAssociationMap
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
    public:
        void *operator new(size_t n) { return ::malloc(n); }
        void operator delete(void *ptr) { ::free(ptr); }
    };

ObjectAssociationMap也是一個(gè) HashMap 存放的是 一個(gè)void * key就是關(guān)聯(lián)屬性時(shí)傳進(jìn)來(lái)的 key , ObjcAssociation 存放的關(guān)聯(lián)屬性策略和值的信息

2.4 ObjcAssociation
class ObjcAssociation {
        uintptr_t _policy;
        id _value;
    public:
        ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
        ObjcAssociation() : _policy(0), _value(nil) {}

        uintptr_t policy() const { return _policy; }
        id value() const { return _value; }

        bool hasValue() { return _value != nil; }
    };

ObjcAssociation 關(guān)聯(lián)屬性信息類(lèi) 存放了關(guān)聯(lián)策略 和 傳遞進(jìn)來(lái)關(guān)聯(lián)的值 id 類(lèi)型


2.5 objc_setAssociatedObject
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    // retain the new value (if any) outside the lock.
   /// 舊的關(guān)聯(lián)對(duì)象 因?yàn)殛P(guān)聯(lián)屬性時(shí)如果傳 nil 可能會(huì)替換舊的關(guān)聯(lián)屬性 ,這就是移除某個(gè)關(guān)聯(lián)屬性時(shí)傳 nil 的原因
    ObjcAssociation old_association(0, nil);
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        AssociationsManager manager;
       ///獲取關(guān)聯(lián)屬性列表 ,取出來(lái)的列表是以對(duì)象為單位的 ,即某個(gè)對(duì)象的關(guān)聯(lián)列表 ,這樣就可以單獨(dú)的關(guān)聯(lián)某個(gè)對(duì)象的關(guān)聯(lián)屬性 而不與其他對(duì)象隔離開(kāi)
        AssociationsHashMap &associations(manager.associations()); 
      /// 將要添加關(guān)聯(lián)屬性的對(duì)象產(chǎn)生一個(gè)內(nèi)存地址 做 key 存儲(chǔ) 它的關(guān)聯(lián)屬性
        disguised_ptr_t disguised_object = DISGUISE(object);
      /// 如果要關(guān)聯(lián)的值不為空 ,不為空時(shí) 就需要判斷這個(gè)屬性和 key 是不是第一天添加 ,即  void *key, id value 都是第一次傳遞進(jìn)來(lái) 
        if (new_value) {
            AssociationsHashMap::iterator i = associations.find(disguised_object);
           /// 根據(jù)這個(gè)對(duì)象取出的這個(gè)對(duì)象關(guān)聯(lián)列表存在 
            if (i != associations.end()) {
               ///取出這個(gè)對(duì)象關(guān)聯(lián)所有的屬性列表 
                ObjectAssociationMap *refs = i->second;
               ///根據(jù) 可以 取出某個(gè)屬性的關(guān)聯(lián)字典 如果為空 就添加到關(guān)聯(lián)字典里邊 ,不為空就對(duì)舊值就行替換操作
                ObjectAssociationMap::iterator j = refs->find(key);
                if (j != refs->end()) { ///取出來(lái)的字典不為空 
                    old_association = j->second; //取出舊值 后邊對(duì)這個(gè)舊值進(jìn)行 release 操作
                   ///將新值存放到 key 對(duì)應(yīng)的字典中去 
                    j->second = ObjcAssociation(policy, new_value);
                } else { ///沒(méi)有舊值直接將新值添加到字典里
                    (*refs)[key] = ObjcAssociation(policy, new_value);
                }
            } else { 
                  如果 key 對(duì)象的字典不存在 就創(chuàng)建一個(gè)字典 (hashMap 類(lèi)似于字典的功能,本文為了方便理解將它稱(chēng)為字典)
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs; 
              ///將要關(guān)聯(lián)屬性和策略封裝到一個(gè)ObjcAssociation類(lèi)里邊 并根據(jù) key 添加到這個(gè)字典里
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
          ///如果添加關(guān)聯(lián)的屬性為空時(shí) 就需要取出之前關(guān)聯(lián)的值 并把它擦除掉 相當(dāng)于removeObjectForKey 
        ///還是根據(jù)對(duì)象內(nèi)存地址找到它的關(guān)聯(lián)屬性列表 ,然后通過(guò) key 找到它關(guān)聯(lián)屬性的實(shí)體(ObjcAssociation這個(gè)類(lèi)) 最后擦除掉 相當(dāng)于 free 從內(nèi)存中移除
            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);
}

objc_setAssociatedObject 添加關(guān)聯(lián)屬性的 API

2.6 objc_getAssociatedObject
id _object_get_associative_reference(id object, void *key) {
    id value = nil;
    uintptr_t policy = OBJC_ASSOCIATION_ASSIGN; 
    {
      ///還是通過(guò) AssociationsManager 找到所有關(guān)聯(lián)對(duì)象類(lèi)別 ,然后通過(guò)傳入 object 找到某個(gè)對(duì)象的關(guān)聯(lián)列表 ,然后通過(guò) key 找到這個(gè)對(duì)象關(guān)聯(lián)屬性列表的某個(gè)實(shí)體(ObjcAssociation) 最后根據(jù)關(guān)聯(lián)策略返回這個(gè)屬性的值 
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.associations());
        disguised_ptr_t disguised_object = DISGUISE(object);
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) { ///如果這個(gè)對(duì)象的關(guān)聯(lián)列表存在
            ObjectAssociationMap *refs = i->second;
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) { ///如果對(duì)象關(guān)聯(lián)列表的屬性存在
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
                ///取出關(guān)聯(lián)值和策略 發(fā)送消息 類(lèi)似與 [obj retain]
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
            }
        }
    }
   /// 如果這個(gè)對(duì)象是延時(shí)釋放的類(lèi)型 類(lèi)似與 OC Array String 這些不是 alloc 來(lái)的對(duì)象 都要執(zhí)行 [obj autorelease]來(lái)釋放 
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);
    }
    return value;
}

objc_getAssociatedObject 關(guān)聯(lián)對(duì)象取值的操作

2.7 objc_removeAssociatedObjects
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);
       ///如果這個(gè)對(duì)象有關(guān)聯(lián)的屬性列表 那么久便利它關(guān)聯(lián)的屬性列表 然后通過(guò)便利將這些關(guān)聯(lián)內(nèi)容 一個(gè)個(gè)從字典里邊擦除  先擦除對(duì)象列表關(guān)聯(lián)的屬性列表 然后將這個(gè)對(duì)象關(guān)聯(lián)屬性的 hashMap 擦除掉 相當(dāng)于 [dict removeAllObjects] 然后再?gòu)娜?AssociationsManager 移除 這個(gè)對(duì)象關(guān)聯(lián)的字典 , 又相當(dāng)于 從一個(gè)全局大字典里 把 dict這個(gè)對(duì)象的小字典 給移除了 
        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());
}

objc_removeAssociatedObjects 移除該對(duì)象所有的關(guān)聯(lián)屬性列表
關(guān)聯(lián)對(duì)象原理就講完了,下面我們來(lái)看它的具體應(yīng)用場(chǎng)景

三 應(yīng)用場(chǎng)景
3.1 給系統(tǒng)提供的類(lèi)添加屬性

通過(guò)給分類(lèi)添加屬性,可以衍生到,當(dāng)我們遇到某個(gè)系統(tǒng)提供的類(lèi),我們想要給它添加一個(gè)屬性,那么我們就可以創(chuàng)建這個(gè)類(lèi)的分類(lèi),然后使用關(guān)聯(lián)對(duì)象添加屬性,比如我們給NSString 添加一個(gè)isEmail 屬性,標(biāo)示它是不是一個(gè)郵箱地址。

#import <Foundation/Foundation.h>

@interface NSString (Email)

@property(assign,nonatomic) BOOL isEmail;

@end

#import "NSString+Email.h"
#import "objc/runtime.h"

@implementation NSString (Email)

-(void)setIsEmail:(BOOL)isEmail{

    objc_setAssociatedObject(self, @selector(isEmail),@(isEmail), OBJC_ASSOCIATION_ASSIGN);
}

- (BOOL)isEmail{

   return [objc_getAssociatedObject(self, @selector(isEmail)) boolValue];
}

@end

#include<stdio.h>
#import <Foundation/Foundation.h>
#import "NSString+Email.h"
 int main()
{
    NSString *string = @"56885688@163.com";
    string.isEmail = true;
    if(string.isEmail){
        NSLog(@"這是一個(gè)郵箱地址");
    } else {
        NSLog(@"這是不是郵箱地址");
    }

}

output:
2019-02-11 16:21:35.993353+0800 test[12438:215884] 這是一個(gè)郵箱地址

3.2 關(guān)聯(lián)block(關(guān)聯(lián)回調(diào),關(guān)聯(lián)執(zhí)行邏輯)

UIButton為例,使用關(guān)聯(lián)對(duì)象完成一個(gè)功能函數(shù):為UIButton增加一個(gè)分類(lèi),定義一個(gè)方法,使用block去實(shí)現(xiàn)button的點(diǎn)擊回調(diào)

UIButton+Handle.h

#import <UIKit/UIKit.h>
#import <objc/runtime.h>    // 導(dǎo)入頭文件

// 聲明一個(gè)button點(diǎn)擊事件的回調(diào)block
typedef void(^ButtonClickCallBack)(UIButton *button);

@interface UIButton (Handle)

// 為UIButton增加的回調(diào)方法
- (void)handleClickCallBack:(ButtonClickCallBack)callBack;

@end

UIButton+Handle.m
#import "UIButton+Handle.h"

// 聲明一個(gè)靜態(tài)的索引key,用于獲取被關(guān)聯(lián)對(duì)象的值
static char *buttonClickKey;

@implementation UIButton (Handle)

- (void)handleClickCallBack:(ButtonClickCallBack)callBack {
    // 將button的實(shí)例與回調(diào)的block通過(guò)索引key進(jìn)行關(guān)聯(lián):
    objc_setAssociatedObject(self, &buttonClickKey, callBack, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

    // 設(shè)置button執(zhí)行的方法
    [self addTarget:self action:@selector(buttonClicked) forControlEvents:UIControlEventTouchUpInside];
}

- (void)buttonClicked {
    // 通過(guò)靜態(tài)的索引key,獲取被關(guān)聯(lián)對(duì)象(這里就是回調(diào)的block)
    ButtonClickCallBack callBack = objc_getAssociatedObject(self, &buttonClickKey);

    if (callBack) {
        callBack(self);
    }
}

@end

viewController 里使用

[self.testButton handleClickCallBack:^(UIButton *button) {
        NSLog(@"block --- click 回調(diào)");
    }];

3.3 更多使用示例

關(guān)聯(lián)對(duì)象開(kāi)發(fā)中有哪些應(yīng)用場(chǎng)景,我們不能窮盡,但是我們可以多學(xué)習(xí)別人的使用場(chǎng)景,怎么學(xué)習(xí)呢?很簡(jiǎn)單,github上直接搜索objc_getAssociatedObject 選中code,就有很多別人寫(xiě)的示例:

image

鏈接:http://www.itdecent.cn/p/b40929723d8c

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容