OC語(yǔ)言-分類&關(guān)聯(lián)對(duì)象&擴(kuò)展

分類
分類做了什么
  1. 聲明私有方法: 把分類的頭文件放到類的.m中,就滿足了類既能使用這個(gè)方法,又對(duì)外不暴露
  2. 分解體積龐大的類文件: 按功能進(jìn)行方法分類
  3. 把Framework的私有方法公開(kāi)
分類的特點(diǎn)
  1. 在運(yùn)行時(shí)進(jìn)行決議: 在編寫完分類文件之后,并沒(méi)有把分類中添加的內(nèi)容附加到類上,而是在運(yùn)行時(shí)通過(guò)runtime把分類內(nèi)容真實(shí)的添加到類上
  2. 可以為系統(tǒng)類添加分類: 項(xiàng)目中經(jīng)??吹経IView的獲取坐標(biāo)的分類方法
分類和擴(kuò)展的區(qū)別
  1. 擴(kuò)展不是運(yùn)行時(shí)決議
  2. 擴(kuò)展無(wú)法給系統(tǒng)類添加分類
分類中都可以添加哪些內(nèi)容
  1. 實(shí)例方法
  2. 類方法
  3. 協(xié)議
  4. 實(shí)例屬性(只聲明了對(duì)應(yīng)的get和set方法,但沒(méi)有在分類中添加上相應(yīng)的實(shí)例變量)
  5. 可以通過(guò)關(guān)聯(lián)對(duì)象來(lái)為分類添加實(shí)例變量
分類結(jié)構(gòu)體

category_t就是創(chuàng)建的分類文件
成員屬性name是分類名稱
成員變量cls表示宿主類
下面四個(gè)結(jié)構(gòu)體表示實(shí)例方法列表,類方法列表,協(xié)議以及實(shí)例屬性的列表

加載調(diào)用棧

程序啟動(dòng)后,在運(yùn)行時(shí)會(huì)調(diào)用_objc_init方法,這個(gè)方法也就是runtime初始化
然后調(diào)用一系列的方法,做了些程序鏡像以及內(nèi)存鏡像的相關(guān)處理,最終會(huì)去加載分類,images方法的命名在這里表示的是鏡像,_read_images是讀取鏡像,就是加載一些可執(zhí)行文件到內(nèi)存中進(jìn)行一些處理
分類的加載內(nèi)容和邏輯都在remethodizeClass內(nèi)部

源碼分析

這里分析分類添加實(shí)例方法的邏輯

static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;
    runtimeLock.assertWriting();
   //1.判斷當(dāng)前類是否為元類對(duì)象,這取決于我們添加的是實(shí)例方法還是類方法,因?yàn)榧僭O(shè)添加的是實(shí)例方法,isMeta為NO
    isMeta = cls->isMetaClass();

    //2.從類中獲取還沒(méi)有拼接整合的所有分類,
    if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
        if (PrintConnecting) {
            _objc_inform("CLASS: attaching categories to class '%s' %s", 
                         cls->nameForLogging(), isMeta ? "(meta)" : "");
        }
        //如果獲取到了,則拼接到宿主類上
        attachCategories(cls, cats, true /*flush caches*/);        
        free(cats);
    }
}


###拼接到宿主類的具體實(shí)現(xiàn)###

static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    //先對(duì)分類判空
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

   /*
     聲明三個(gè)局部變量,對(duì)應(yīng)的都是二維數(shù)組,分別表示方法列表,屬性列表和協(xié)議列表
     數(shù)據(jù)結(jié)構(gòu)是數(shù)組,數(shù)組中的每個(gè)元素又是數(shù)組,里面數(shù)組的元素是method_t這樣的結(jié)構(gòu),method_t代表的就是一個(gè)方法
     [[method_t,method_t],[method_t],[method_t,method_t,method_t],...]
     */
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    //下面三個(gè)代表方法的參數(shù),屬性參數(shù),協(xié)議參數(shù)
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    //獲取宿主類分類的總數(shù),傳遞進(jìn)來(lái)的分類列表的參數(shù)cats,他的最后一個(gè)元素是最后參加編譯的
    int i = cats->count;
    bool fromBundle = NO;
    while (i--) {//倒序遍歷,最先訪問(wèn)最后編譯的分類
        //獲取一個(gè)分類
        auto& entry = cats->list[I];
        //獲取該分類的方法列表
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            //最后編譯的分類最先被添加到分類數(shù)組中
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        //屬性列表添加規(guī)則
        property_list_t *proplist = entry.cat->propertiesForMeta(isMeta);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
        //協(xié)議列表添加規(guī)則
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    //獲取宿主類中的rw數(shù)據(jù),其中包含宿主類的方法列表信息
    auto rw = cls->data();
    //主要是針對(duì)分類中有關(guān)于內(nèi)存管理相關(guān)方法情況下的一些特殊處理
    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    //把剛剛處理過(guò)的二維數(shù)組的數(shù)據(jù)拼接到宿主類的方法列表上面,rw代表類,methods代表類的方法列表,attachLists是將含有mcount個(gè)元素的mlist元素拼接到rw的methods上
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}



###拼接列表的具體實(shí)現(xiàn)###
 /* addedLists是傳來(lái)的二維數(shù)組
       [[method_t,method_t],[method_t],[method_t,method_t,method_t],...]
       --------------------  --------   --------------------------
        分類A中的方法列表A        B               C
    
        addedCount = 3
    */
    void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;//先判空

        if (hasArray()) {
            //列表中原有元素總數(shù) oldCount = 2
            uint32_t oldCount = array()->count;
            //拼接之后的元素總數(shù)
            uint32_t newCount = oldCount + addedCount;
            //根據(jù)新的總數(shù)重新分配內(nèi)存
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            //重新設(shè)置元素總數(shù)為5
            array()->count = newCount;
            /*
             內(nèi)存移動(dòng)
             [[],[],[],[原有的第一個(gè)元素],[原有的第二個(gè)元素]]
             */
            memmove(array()->lists + addedCount, array()->lists, 
                    oldCount * sizeof(array()->lists[0]));
            /*
             內(nèi)存拷貝
             [
             A ---> [addedLists中的第一個(gè)元素],
             B ---> [addedLists中的第二個(gè)元素],
             C ---> [addedLists中的第三個(gè)元素],
             [原有的第一個(gè)元素],
             [原有的第二個(gè)元素]
             ]
             
             這就是分類方法會(huì)覆蓋宿主類方法的原因,宿主類的重名方法仍然存在,但是在我們消息發(fā)送過(guò)程中,是根據(jù)選擇器名稱來(lái)查找的,一旦查找到對(duì)應(yīng)的實(shí)現(xiàn)就會(huì)返回,由于分類方法位于數(shù)組靠前的位置,若分類和宿主類方法重名,會(huì)先查找到分類的方法
             */
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }

根據(jù)上面的代碼可以得出下面兩圖


倒序遍歷分類,添加到數(shù)組中

將分類數(shù)組拼接到宿主類的方法列表上
***總結(jié)

rw->methods.attachLists(mlists, mcount);這句代碼才將分類的方法真正的添加到宿主類上,這說(shuō)明了分類是運(yùn)行時(shí)決議

  1. 分類添加的方法可以"覆蓋"原類方法,之所以雙引號(hào)是因?yàn)橄到y(tǒng)方法仍然存在
  2. 若我們添加兩個(gè)分類,兩個(gè)分類中都有同名方法,哪個(gè)會(huì)生效,取決于分類的編譯順序,因?yàn)槭堑剐蚓幾g,最后編譯的分類同名方法才會(huì)最終生效,其他都會(huì)被覆蓋掉
  3. 名字相同的分類會(huì)引起編譯報(bào)錯(cuò),因?yàn)樵谏删唧w分類的時(shí)候,經(jīng)過(guò)runtime在編譯過(guò)程中會(huì)把我們添加的分類名字以下劃線方式拼接到宿主類上,如果名字相同,就會(huì)類似于我們定義了兩個(gè)同名變量,會(huì)引起編譯報(bào)錯(cuò)
關(guān)聯(lián)對(duì)象
  1. 為分類添加成員變量
/*
添加關(guān)聯(lián)對(duì)象
首先設(shè)定value值,然后通過(guò)key建立和value的對(duì)應(yīng)關(guān)系,通過(guò)policy策略,將這個(gè)對(duì)應(yīng)關(guān)系,關(guān)聯(lián)到object對(duì)象上
policy是表示value是以何種形式(copy,assign,return)關(guān)聯(lián)到宿主對(duì)象上
*/
void objc_setAssociatedObject(id object, const void *key,
                         id value, objc_AssociationPolicy policy)

/*
取出關(guān)聯(lián)對(duì)象的值
根據(jù)指定的key,到object對(duì)象中去獲取和key相對(duì)應(yīng)的關(guān)聯(lián)值,然后作為函數(shù)的返回值,返回給調(diào)用方
*/
id objc_getAssociatedObject(id object, const void *key)

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

關(guān)聯(lián)對(duì)象是由系統(tǒng)提供的,由AssociationsManager類來(lái)管理,這個(gè)類有一個(gè)成員變量叫做AssociationsHashMap,我們創(chuàng)建的每一對(duì)象的關(guān)聯(lián)對(duì)象,都存儲(chǔ)在AssociationsHashMap這個(gè)容器中
它是通過(guò)hash來(lái)實(shí)現(xiàn)的一個(gè)map,它是一個(gè)全局容器

關(guān)聯(lián)對(duì)象被添加到哪里呢?由上面可以看出,不論我們?yōu)槟膫€(gè)對(duì)象建立關(guān)聯(lián)值,所有類的關(guān)聯(lián)對(duì)象都放在同一個(gè)全局容器中

首先,根據(jù)傳進(jìn)來(lái)的value值和policy策略,封裝成ObjcAssociation這樣的數(shù)據(jù)結(jié)構(gòu)
@selector(text)就是我們傳遞進(jìn)來(lái)的key,key和剛剛的ObjcAssociation建立起一個(gè)映射關(guān)系,放到ObjectAssociationMap這樣的數(shù)據(jù)結(jié)構(gòu)中
當(dāng)前被關(guān)聯(lián)對(duì)象的指針值,和ObjectAssociationMap建立起一個(gè)映射關(guān)系,最終放到AssociationsHashMap里

關(guān)聯(lián)進(jìn)來(lái)的對(duì)象的數(shù)據(jù)結(jié)構(gòu)
/*
 object準(zhǔn)備被關(guān)聯(lián)的對(duì)象,key是我們要g關(guān)聯(lián)的值得標(biāo)識(shí),value是要關(guān)聯(lián)的值,policy是要通過(guò)哪種策略(copy,return或assign)將keyvalue關(guān)聯(lián)到對(duì)象上
 */

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
    ObjcAssociation old_association(0, nil);
    //1.根據(jù)策略對(duì)value進(jìn)行加工
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        //聲明一個(gè)全局管理者,主要管理關(guān)聯(lián)對(duì)象,由C++實(shí)現(xiàn)
        AssociationsManager manager;
        //AssociationsHashMap是個(gè)全局容器,被manager管理,里面放著所有的關(guān)聯(lián)對(duì)象
        AssociationsHashMap &associations(manager.associations());
        //根據(jù)傳進(jìn)來(lái)的對(duì)象,做了一個(gè)轉(zhuǎn)換,對(duì)指針地址按位取反,來(lái)作為全局容器中某一對(duì)象的key,
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {//new_value是傳進(jìn)來(lái)的準(zhǔn)備被關(guān)聯(lián)的值
            //根據(jù)對(duì)象指針查找對(duì)應(yīng)的一個(gè)ObjectAssociationsMap結(jié)構(gòu)的map,object對(duì)象在全局容器中,是和ObjectAssociationsMap建立起的映射關(guān)系
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {//ObjectAssociationsMap之前被創(chuàng)建過(guò)
                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 {////ObjectAssociationsMap之前沒(méi)有被創(chuàng)建過(guò)
                //新創(chuàng)建一個(gè)ObjectAssociationMap,作為全局容器當(dāng)中disguised_object這個(gè)key的value
                //再把新關(guān)聯(lián)的值,通過(guò)策略和nieValue組裝成ObjcAssociationk結(jié)構(gòu)
                ObjectAssociationMap *refs = new ObjectAssociationMap;
                associations[disguised_object] = refs;
                (*refs)[key] = ObjcAssociation(policy, new_value);
                object->setHasAssociatedObjects();
            }
        } else {
            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);
                }
            }
        }
根據(jù)代碼可以得出:怎樣清除某個(gè)關(guān)聯(lián)對(duì)象被關(guān)聯(lián)的值呢,可以調(diào)用objc_setAssociatedObject方法,傳nil即可,就會(huì)把對(duì)應(yīng)key的值給擦除掉
擴(kuò)展

在開(kāi)發(fā)時(shí),一般把擴(kuò)展的聲明放到宿主類的.m文件之中

一般用擴(kuò)展做什么?
  1. 聲明私有屬性,是可以不對(duì)子類暴露的
  2. 聲明私有方法,方便閱讀
  3. 聲明私有成員變量
擴(kuò)展的特點(diǎn):
  1. 編譯時(shí)決議
  2. 只以聲明的形式存在,沒(méi)有具體實(shí)現(xiàn),多數(shù)情況寄生于宿主類的.m中,也就是說(shuō),它不是獨(dú)立存在實(shí)現(xiàn)的一個(gè)文件
    可以把擴(kuò)展理解為類的一個(gè)內(nèi)部的私有聲明
  3. 不能為系統(tǒng)類添加擴(kuò)展
分類和擴(kuò)展的區(qū)別
  1. 分類是運(yùn)行時(shí)決議,擴(kuò)展是編譯時(shí)決議
  2. 分類可以有聲明有實(shí)現(xiàn),而擴(kuò)展只有聲明,它的實(shí)現(xiàn)是直接寫在宿主類當(dāng)中
  3. 可以為系統(tǒng)類添加分類,但不能為系統(tǒng)類添加擴(kuò)展
分類實(shí)現(xiàn)原理

分類實(shí)現(xiàn)原理由運(yùn)行時(shí)決議,不同分類中含有同名分類方法,誰(shuí)最終生效,取決于誰(shuí)最后參與編譯,假如分類中添加的方法恰好是宿主類中的同名方法,分類方法會(huì)"覆蓋"同名的宿主類方法(消息傳遞過(guò)程中會(huì)優(yōu)先查找數(shù)組靠前的元素,若找到了就會(huì)調(diào)用,但宿主類的同名方法仍然存在)

能否為分類添加成員變量(也叫實(shí)例變量)

可以通過(guò)關(guān)聯(lián)對(duì)象來(lái)添加

最后編輯于
?著作權(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)容