iOS底層原理 - 關(guān)聯(lián)對(duì)象使用以及源碼剖析

開篇還是放上幾道面試題

Category能否添加成員變量?如果可以,如何給Category添加成員變量?
不能直接給Category添加成員變量,但是可以間接實(shí)現(xiàn)Category有成員變量的效果

  • 首先大家應(yīng)該都知道分類可以添加屬性,但是不可以添加成員變量,同時(shí)自動(dòng)聲明了set方法,get方法,此時(shí)獲取方法列表是不存在set,fet方法的,必須實(shí)現(xiàn)才會(huì)在方法列表中出現(xiàn),大家可能會(huì)想那我就手動(dòng)實(shí)現(xiàn)set方法,如下圖

分類添加了個(gè)屬性 @property (nonatomic,copy)NSString *categoryName1;

屏幕快照 2018-11-21 下午2.29.11.png
  • 發(fā)現(xiàn)分類中根本沒有這個(gè)成員變量,其實(shí)如果看了分類的底層結(jié)構(gòu)category_t應(yīng)該就知道那里壓根就沒有存儲(chǔ)成員變量的地方,所以想通過正常的方式直接添加成員變量是不可以的,但是可以通過關(guān)聯(lián)對(duì)象,來間接添加成員變量

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

添加關(guān)聯(lián)對(duì)象
void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)

獲得關(guān)聯(lián)對(duì)象
id objc_getAssociatedObject(id object, const void * key)

移除所有的關(guān)聯(lián)對(duì)象
void objc_removeAssociatedObjects(id object)

關(guān)聯(lián)對(duì)象基本用法

1.以全局變量里面存放著自己的地址為key
static void *MyKey = &MyKey;
objc_setAssociatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
2.以全局變量的地址為key
static char MyKey;
pobjc_setAssociatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
pobjc_getAssociatedObject(obj, &MyKey)
3.使用屬性名作為key
objc_setAssociatedObject(obj, @"name", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
pobjc_getAssociatedObject(obj, @"name");
4.使用get方法的@selecor作為key
objc_setAssociatedObject(obj, @selector(setName), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
//get方法里面,下面這兩種寫法都可以
objc_getAssociatedObject(obj, @selector(name))
objc_getAssociatedObject(obj, _cmd)

objc_AssociationPolicy policy的用法

屏幕快照 2018-11-21 下午2.50.26.png

開始分析源碼 源碼地址

  • 搜索objc_setAssociatedObject,找到如下源碼
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()) {
                // 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);
}
  • 看到 AssociationsManager,AssociationsHashMap,ObjectAssociationMap,ObjcAssociation這四個(gè)類,接下來我們一一在仔細(xì)查看對(duì)應(yīng)的類結(jié)構(gòu)
  • AssociationsManager結(jié)構(gòu)如下
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;
    }
};
  • 由上面代碼看到此類只有一個(gè)成員變量,也就是說這個(gè)manager管理著這個(gè)map,所有的信息都存在這個(gè)map里

static AssociationsHashMap *_map;

  • 接下來查看這個(gè)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); }
    };
  • 由上面代碼可以看出這個(gè)map的key和value類型結(jié)構(gòu)為:

disguised_ptr_t ObjectAssociationMap
disguised_ptr_t ObjectAssociationMap
disguised_ptr_t ObjectAssociationMap

  • 那么在接著看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); }
    };
  • 可以看到key value的結(jié)構(gòu)如下:

void * ObjcAssociation
void * ObjcAssociation
void * ObjcAssociation

  • 接著再看ObjcAssociation的結(jié)構(gòu)
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類里有兩個(gè)成員變量,一個(gè)是_policy策略,一個(gè)是_value值
  • 再具體點(diǎn)分析如下圖所示:


    關(guān)聯(lián)對(duì)象分析圖.jpg
  • 最終得到結(jié)論:


    屏幕快照 2018-11-21 下午6.56.32.png
屏幕快照 2018-11-29 下午8.49.45.png

好了看完了底層結(jié)構(gòu),在講講項(xiàng)目中怎么使用

  • 大家可能注意到,policy沒有weak'類型,那得怎么實(shí)現(xiàn)weak關(guān)聯(lián)對(duì)象呢?

我們可以關(guān)聯(lián)個(gè)retain對(duì)象,讓這個(gè)對(duì)象持有他
恰好系統(tǒng)有提供這個(gè)類NSMapTable,這個(gè)map可以設(shè)置weak引用里面的值,和我們常用的字典類似

@interface NSObject()
@property (nonatomic,strong) NSMapTable *keyMapTable;
@end
@implementation NSObject (Association)
- (void)setWeakAssociatedObject:(id)object forKey:(NSString *)key
{
    [self.keyMapTable setObject:object forKey:key];
}
-(id)weakAssociatedObjectForKey:(NSString *)key
{
   return  [self.keyMapTable objectForKey:key];
}

#pragma mark - 私有方法
-(NSMapTable *)keyMapTable
{
    if (objc_getAssociatedObject(self, _cmd)==nil) {
        self.keyMapTable=[NSMapTable weakToWeakObjectsMapTable];
    }
    return objc_getAssociatedObject(self, _cmd);
}
-(void)setKeyMapTable:(NSMapTable *)keyMapTable
{
     objc_setAssociatedObject(self, @selector(keyMapTable), keyMapTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
  • 再來看一個(gè)問題,我們大家可能在添加retain關(guān)聯(lián)對(duì)象的時(shí)候,經(jīng)常用getter方法作為key,但是用起來每增加個(gè)成員變量都得寫一遍很麻煩,所以大家可能就會(huì)想到使用屬性名作為key,這樣寫一遍公共的方法就好了,但是我告訴你這樣是存在潛在問題的,那么就來看看下面代碼有啥問題:
- (void)setAssociatedObject:(id)object forKey:(NSString *)key {
    objc_setAssociatedObject(self, &key, object, OBJC_ASSOCIATION_RETAIN);
}
- (id)associatedObjectForKey:(NSString *)key {
    return objc_getAssociatedObject(self, &key);
}

從上面的代碼可以看出就是使用傳進(jìn)來的字符串為地址,每次傳進(jìn)來的字符串一樣,大家可能就以為這個(gè)地址就一樣了,其實(shí)不然,當(dāng)字符串相同地址不一樣時(shí),set完在取值就取不到了,值為nil

屏幕快照 2018-11-22 上午9.53.07.png
  • 上圖這種情況,雖然字符串的值相同,但是地址值不同,所以導(dǎo)致取出的值為nil,常見場(chǎng)景就是set的時(shí)候用的是常量字符串,取值的時(shí)候用的是后臺(tái)返回的數(shù)據(jù)里的值導(dǎo)致不一樣,如果你取值時(shí)還是用常量字符串那么就沒問題,因?yàn)槌A孔址刂芬粯?/li>
  • 解決方式如下
@interface NSObject()
@property (nonatomic,strong) NSMutableDictionary *strongKeyBuffer;
@end
@implementation NSObject (Association)
-(NSMutableDictionary *)strongKeyBuffer{
    if (objc_getAssociatedObject(self, _cmd)==nil) {
        self.strongKeyBuffer=[NSMutableDictionary dictionary];
    }
    return objc_getAssociatedObject(self, _cmd);
}
-(void)setStrongKeyBuffer:(NSMutableDictionary *)strongKeyBuffer{
    objc_setAssociatedObject(self, @selector(strongKeyBuffer), strongKeyBuffer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
/**
 * set 方法,以供以后添加屬性時(shí)候給這個(gè)屬性的 set 方法調(diào)用
 * @param   object      要關(guān)聯(lián)的對(duì)象,也就是要設(shè)置的新的屬性值
 * @param   key         屬性名稱,傳入新增屬性的名稱
    解決常亮字符串地址不一致問題 導(dǎo)致取值為nil
 **/
- (void)setAssociatedObject:(id)object forKey:(NSString *)key {
    const char *cKey = [self.strongKeyBuffer[key] pointerValue]; // 先獲取key
    if (cKey == NULL) { // 字典中不存在就創(chuàng)建
        cKey = key.UTF8String;
        self.strongKeyBuffer[key] = [NSValue valueWithPointer:cKey];
    }
    objc_setAssociatedObject(self, cKey, object, OBJC_ASSOCIATION_RETAIN);
}
#pragma mark -  get 方法,以供以后添加屬性時(shí)候給這個(gè)屬性的 get 方法調(diào)用
- (id)associatedObjectForKey:(NSString *)key {
    const char *cKey = [self.strongKeyBuffer[key] pointerValue];
    if (cKey == NULL) {
        return nil;
    } else {
        return objc_getAssociatedObject(self, cKey);
    }
}

用一個(gè)可變字典存放添加過哪些key,和真正set時(shí)用的地址是啥,這里這么用
[NSValue valueWithPointer:key.UTF8String];
當(dāng)然你也可以不這么用,主要思想是用個(gè)字典存儲(chǔ)添加過的key,然后取值的時(shí)候通過傳進(jìn)來的key,找到set時(shí)用的地址值,然后再用此地址值取值,所以就不會(huì)為nil了

下面附上我封裝的類.h .m文件

  • NSObject+Association.h
//
//  NSObject+Association.h
//  NSObject+Association
//
//  Created by liusong on 2018/4/3.
//  Copyright ? 2018年 liusong. All rights reserved.
//
#import <Foundation/Foundation.h>

@interface NSObject (Association)

/** 所有要增加的屬性的 set 方法都可以調(diào)用這個(gè)方法來實(shí)現(xiàn) */
- (void)setAssociatedObject:(id)object forKey:(NSString *)key;

/** 所有要增加的屬性的 get 方法都可以調(diào)用這個(gè)方法來實(shí)現(xiàn) */
- (id)associatedObjectForKey:(NSString *)key ;


//以下為增加獲取 weak 屬性
- (void)setWeakAssociatedObject:(id)object forKey:(NSString *)key;
- (id)weakAssociatedObjectForKey:(NSString *)key ;
@end
  • NSObject+Association.m
//
//  NSObject+Association.m
//  NSObject+Association
//
//  Created by liusong on 2018/4/3.
//  Copyright ? 2018年 liusong. All rights reserved.
//

#import "NSObject+Association.h"
#import <objc/runtime.h>
@interface NSObject()
@property (nonatomic,strong) NSMutableDictionary *strongKeyBuffer;
@property (nonatomic,strong) NSMapTable *keyMapTable;
@end

@implementation NSObject (Association)

/**
 * set 方法,以供以后添加屬性時(shí)候給這個(gè)屬性的 set 方法調(diào)用
 * @param   object      要關(guān)聯(lián)的對(duì)象,也就是要設(shè)置的新的屬性值
 * @param   key         屬性名稱,傳入新增屬性的名稱
    解決同一字符串地址不一致問題 導(dǎo)致取值為nil
 **/
- (void)setAssociatedObject:(id)object forKey:(NSString *)key {
    const char *cKey = [self.strongKeyBuffer[key] pointerValue]; // 先獲取key
    if (cKey == NULL) { // 字典中不存在就創(chuàng)建
        cKey = key.UTF8String;
        self.strongKeyBuffer[key] = [NSValue valueWithPointer:cKey];
    }
    objc_setAssociatedObject(self, cKey, object, OBJC_ASSOCIATION_RETAIN);
}
#pragma mark -  get 方法,以供以后添加屬性時(shí)候給這個(gè)屬性的 get 方法調(diào)用
- (id)associatedObjectForKey:(NSString *)key {
    const char *cKey = [self.strongKeyBuffer[key] pointerValue];
    if (cKey == NULL) {
        return nil;
    } else {
        return objc_getAssociatedObject(self, cKey);
    }
}
- (void)setWeakAssociatedObject:(id)object forKey:(NSString *)key
{
    [self.keyMapTable setObject:object forKey:key];
}

-(id)weakAssociatedObjectForKey:(NSString *)key
{
   return  [self.keyMapTable objectForKey:key];
}

#pragma mark - 私有方法
-(NSMapTable *)keyMapTable
{
    if (objc_getAssociatedObject(self, _cmd)==nil) {
        self.keyMapTable=[NSMapTable weakToWeakObjectsMapTable];
    }
    return objc_getAssociatedObject(self, _cmd);
}
-(void)setKeyMapTable:(NSMapTable *)keyMapTable
{
     objc_setAssociatedObject(self, @selector(keyMapTable), keyMapTable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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