iOS底層之關(guān)聯(lián)對象

首先我們來簡單的描述一下分類的一些基本概念:
1、用來給類添加新方法
2、不能給類添加成員屬性,添加了成員變量,也無法取到
3、注意:其實可以通過runtime給分類添加屬性
4、分類中用@property定義變量,只會生成變量的getter,setter方法聲明,不能生成方法實現(xiàn)和帶下劃線的成員變量。

那么問題來了, 分類是不能直接添加屬性的,那么要給分類添加屬性,那就需添加關(guān)聯(lián)對象或者重寫,在.m文件添加setter方法的實現(xiàn)。

請看下面代碼,在main.m中添加了對Person類的分類,在分類中有兩個屬性,且在main函數(shù)中創(chuàng)建Person對象并對分類的屬性賦值,其中,分類的屬性利用runtime來實現(xiàn)了gettersetter方法。

#import <Foundation/Foundation.h>
#import "Person.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>
#import "Person+LGEXT.h"

@interface Person (LG)

@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;

- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;
@end

@implementation Person (LG)
- (void)cate_instanceMethod1{
    NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
    NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
    NSLog(@"%s",__func__);
}
+ (void)cate_sayClassMethod{
    NSLog(@"%s",__func__);
}

- (void)setCate_name:(NSString *)cate_name{
/**
 1: 對象
 2: 標(biāo)識符
 3: value
 4: 策略
 */
    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cate_name{
    return  objc_getAssociatedObject(self, "cate_name");
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [Person alloc];
        person.cate_name = @"OC";
        NSLog(@"%p",person);
    }
    return 0;
}

我們首先在setter方法出打上斷點,來看看它是否會執(zhí)行這個方法:

iShot2020-10-21 16.00.06.png

可以看到,會執(zhí)行這個方法,而當(dāng)我們點擊方法進去之后,你會發(fā)現(xiàn),他們的整個方法實現(xiàn)都在一塊,首先SetAssocHook,然后_base_objc_setAssociatedObject,之后就會執(zhí)行_object_set_associative_reference方法。

iShot2020-10-21 16.02.12.png

_object_set_associative_reference方法中有很多代碼:

void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return;
    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));
    // 包裝了一下 對象
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    // 包裝一下 policy - value
    ObjcAssociation association{policy, value};
    // retain the new value (if any) outside the lock.
    association.acquireValue();
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                object->setHasAssociatedObjects();
            }
            /* establish or replace the association */
            auto &refs = refs_result.first->second; // 空的桶子
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }
    // release the old value (outside of the lock).
    association.releaseHeldValue();
}

上面的代碼很多,將部分代碼收起來,可以看到一共就這么幾行代碼:


iShot2020-10-21 16.06.38.png

從下圖可以看到,167行代碼中的DisguisedPtr是對對象進行包裝,

iShot2020-10-21 16.17.14.png

ObjcAssociation的作用同樣是對policyvalue進行包裝,其中value的值是上面代碼對cate_name賦的值OC;

iShot2020-10-21 16.18.48.png

看下圖,acquireValue()的作用其實是對_valuepolicy的判斷;

iShot2020-10-21 16.21.47.png

接下來就是172-201行的局部作用空間代碼,如下圖:


iShot2020-10-21 16.23.30.png
iShot2020-10-21 16.24.16.png

首先看到AssociationsManager,看上圖,它的重要的地方其實是方框中的c++代碼。至于它的作用,下面我用一個例子來表示:
main.m改成main.mm,在main.m中寫一個結(jié)構(gòu)體:

struct Objc {
    Objc()   { printf("來了");}
    ~Objc()  {  printf("走了"); }
};

然后在函數(shù)中調(diào)用:

iShot2020-10-21 16.27.50.png

看最終執(zhí)行完畢的結(jié)果:


iShot2020-10-21 16.28.09.png

這個函數(shù)的作用其實就是在程序開始時執(zhí)行一次,在函數(shù)結(jié)束完畢后,再執(zhí)行一次。而程序中是對代碼進行加鎖和解鎖;它可以有多個這樣的AssociationsManager。

AssociationsHashMap從名字看,像一個哈希表,里面有很多關(guān)聯(lián)對象,里買呢的類型類似于key-value健值對的形式存在;繼續(xù)執(zhí)行內(nèi)部代碼塊:
當(dāng)執(zhí)行到if (refs_result.second) {行時,我們在控制臺來看一下一些屬性的值:

(lldb) p disguised
(DisguisedPtr<objc_object>) $0 = (value = 18446744069408092480)
(lldb) p association
(objc::ObjcAssociation) $1 = {
  _policy = 3
  _value = 0x0000000100004038 "OC"
}
(lldb) p manager
(objc::AssociationsManager) $2 = {}
(lldb) p associations
(objc::AssociationsHashMap) $3 = {
  Buckets = 0x0000000000000000
  NumEntries = 0
  NumTombstones = 0
  NumBuckets = 0
}
(lldb) p refs_result
(std::pair<objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>, bool>) $4 = {
  first = {
    Ptr = 0x0000000101006810
    End = 0x0000000101006850
  }
  second = true
}
(lldb) 

看到refs_result的時候,可以看到$4的類型很長,我們不用去管它,去看看給它賦值的函數(shù)try_emplace
看下圖,它其實是分兩部分,一部分是false,一部分是true,從判斷中可以看出來,當(dāng)TheBucket為空時,就會執(zhí)行InsertIntoBucket,并返回true,當(dāng)TheBucket有值之后,就會返回false;

iShot2020-10-21 16.42.19.png

下圖是當(dāng)value有值的時候,程序執(zhí)行的代碼塊:

iShot2020-10-21 17.17.46.png

根據(jù)代碼執(zhí)行的流程分析的結(jié)果,程序第一次進入try_emplace走的是InsertIntoBucket,而在第二次執(zhí)行try_emplace走的同樣是InsertIntoBucket方法:
也就是說,第一次是從哈希表讀取refs_result,而disguised是傳入的值,接著判斷refs_result.second是不是第一次來,當(dāng)結(jié)果為真,調(diào)用setHasAssociatedObjects()函數(shù)。
接著引用refs_result.firstsecond值,對引用的refs進行第二次調(diào)用try_emplace,接著插入ObjcAssociation的值。

下面是分別進入兩次后的TheBucket的值:

(lldb) p TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $0 = 0x000000010068b5b0
(lldb) p TheBucket
(objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> *) $1 = 0x0000000102014cd8
(lldb) 

接下來看一下當(dāng)value為空時,程序執(zhí)行else部分:

iShot2020-10-21 16.50.53.png

上圖部分代碼的作用其實就是將關(guān)聯(lián)對象進行消除;

總結(jié): 其實就是兩層哈希map , 存取的時候兩層處理(類似二位數(shù)組)

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

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