筆記-關聯對象

目錄

  • 面試題
  • 僞代碼實現
  • Category添加屬性的幾種用法
  • Category爲什麼不能添加成員變量
  • 底層源碼解讀

1、面試題

面試題一、Category能否添加成員變量?如果可以,如何給Category添加成員變量?

- 不能直接給Category添加成員變量
  因爲Category是在運行時決定的。編譯時,對象的內存佈局已經確定,如果添加實例變量就會破壞類的內部佈局。
  Category中使用@property只會生成setter和getter方法的聲明,並不會自動生成實例變量以及存取方法

- 可以間接實現Category有成員變量的效果
  可以通過RunTime中關聯對象在Category中爲類添加成員變量

2、僞代碼實現

我們知道,如果我們自己手動實現setter/getter方法,剩下的就是一個成員變量的存取了,下面用兩種方法實現一下,看看是否存在瑕疵

  • 創建全局對象(❌)
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LYPerson *p1 = [[LYPerson alloc] init];
        p1.name = @"1";

        LYPerson *p2 = [[LYPerson alloc] init];
        p2.name = @"2";

        NSLog(@"p1 name = %@ ;  p2 name = %@", p1.name, p2.name);
    }
}
@implementation LYPerson (Test)

NSString *name_;

- (void)setName:(NSString *)name {
    name_ = name;
}

- (NSString *)name {
    return name_;
}

@end

2019-07-24 22:17:32.291885+0800 test[88511:509458] p1 name = 2 ;  p2 name = 2

結論:如果創建多個實例對象,那麼這些對象將公用一個全局對象,後面的將會把前面的值覆蓋掉
  • 創建全局字典
@implementation LYPerson (Test)

NSMutableDictionary *names_;

+ (void)load {
    names_ = [NSMutableDictionary dictionary];
}

- (void)setName:(NSString *)name {
    NSString *key = [NSString stringWithFormat:@"%p", self];
    weights_[key] = key;
}

- (NSString *)name {
    NSString *key = [NSString stringWithFormat:@"%p", self];
    return weights_[key];
}

@end

- 能實現,但會存在問題,例如線程安全,比較麻煩

3、基本用法

  • 用法一
#import "LYPerson+Test.h"
#import <objc/runtime.h>

@implementation LYPerson (Test)

static const char LYNameKey;

- (void)setName:(NSString *)name {
    /**
     對象關聯

     @param object 關聯對象
     @param key
     @param value  關聯值
     @param policy 關聯策略
               objc_AssociationPolicy                   對應修飾符
            - OBJC_ASSOCIATION_ASSIGN = 0,              assign
            - OBJC_ASSOCIATION_RETAIN_NONATOMIC         strong, nonatomic
            - OBJC_ASSOCIATION_COPY_NONATOMIC           copy, nonatomic
            - OBJC_ASSOCIATION_RETAIN                   strong, atomic
            - OBJC_ASSOCIATION_COPY                     copy, atomic
     @return
     */
    objc_setAssociatedObject(self, &LYNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)name {
    return objc_getAssociatedObject(self, &LYNameKey);
}

@end
  • 用法二
#define MJNameKey @"name"

- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, LYNameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

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

// @"name" 直接寫出來的字符串,放在常量區
  • 用法三
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

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

//隱式參數:get方法可以用_cmd代替;_cmd = @selector(name)

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

- 優點:1、可讀性更高,2、有編譯器提示

5、爲什麼不能添加成員變量

// Category在內存中的結構
struct _category_t {
    const char *name;      // 類名
    struct _class_t *cls;
    const struct _method_list_t *instance_methods; // 實例方法列表
    const struct _method_list_t *class_methods;    // 類方法列表
    const struct _protocol_list_t *protocols;      // 協議列表 (分類裏面也能遵守協議)
    const struct _prop_list_t *properties;         // 屬性列表 (分類裏能實現屬性)
};

- 分類底層結構決定了他不能添加成員變量,沒有數組是用來存放成員變量的
- 用關聯對象的方式給分類添加屬性
  不是存儲在類對象、實例對象中,所以不會影響類對象、實例對象在內存中的存儲結構

6、底層源碼解讀(待完善)

實現關聯對象技術的核心對象有

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation
總結:
- 關聯對象並不是存儲在被關聯對象本身中
- 關聯對象存儲在全局的統一的一個AssociationManager中
- 設置關聯對象爲nil,就相當於移除關聯對象
// 添加
objc_setAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>, <#id  _Nullable value#>, <#objc_AssociationPolicy policy#>)
// 獲取
objc_getAssociatedObject(<#id  _Nonnull object#>, <#const void * _Nonnull key#>)
// 移除
objc_removeAssociatedObjects(<#id  _Nonnull object#>)

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

通過上圖我們可以總結爲:一個實例對象就對應一個ObjectAssociationMap,而ObjectAssociationMap中存儲着多個此實例對象的關聯對象的key以及ObjcAssociation,爲ObjcAssociation中存儲着關聯對象的value和policy策略。
由此我們可以知道關聯對象並不是放在了原來的對象裏面,而是自己維護了一個全局的map用來存放每一個對象及其對應關聯屬性表格。

通過分析可以得出:一個實例對象會有一個 AssociationsManager 操作類和一個存儲Map ObjectAssociationMap


objc_getAssociatedObject

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

objc_removeAssociatedObjects

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());
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章