iOS學習篇-談談個人對Category的理解

廢話不多說,直接進入主題。阅爽。路幸。
下面我們由幾個問題展開:
1、什么是Category付翁?
2简肴、Category有什么特點?
3百侧、Category中能添加哪些內容砰识?

什么是Category?

Category(分類)是在Objective-C 2.0之后出現(xiàn)的,主要用于給現(xiàn)有類添加方法佣渴,以便對原有類的一個補充和完善辫狼,因為我們任何類都不是天生完美的。我們看下蘋果的官方文檔是如何解釋的:

You use categories to define additional methods of an existing class—even one whose source code is unavailable to you—without subclassing. You typically use a category to add methods to an existing class, such as one defined in the Cocoa frameworks. The added methods are inherited by subclasses and are indistinguishable at runtime from the original methods of the class. You can also use categories of your own classes to:

  • Distribute the implementation of your own classes into separate source files—for example, you could group the methods of a large class into several categories and put each category in a different file.
  • Declare private methods.

總結一下就是:
1辛润、分解體積龐大的自定義類文件
2膨处、聲明私有方法

Category有什么特點?

我們先看這句解釋

The added methods are inherited by subclasses and are indistinguishable at runtime from the original methods of the class.

可知:
1砂竖、分類是在運行時期決議的真椿。
2、分類中添加的方法子類是可繼承的乎澄。

那么除此之外分類還可以添加什么呢突硝?

Category中可以添加哪些內容?

我們先看下蘋果源碼的實現(xiàn):

struct category_t {
    const char *name; 
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta) {
        if (isMeta) return nil; // classProperties;
        else return instanceProperties;
    }
};

在category_t的結構體中我們可以看到如下:
name:類名
cls:類
instanceMethods:實例方法列表
classMethods:類方法列表
protocols:協(xié)議列表
instanceProperties:屬性列表

現(xiàn)在就很明確了:

分類中可以添加:實例方法置济、類方法解恰、協(xié)議、屬性

所以也由此可知:category是不能添加成員變量的舟肉,從源代碼層面上解釋就是category_t的結構體里面沒有ivars成員變量列表。

那么查库,問題又來了路媚。。樊销。整慎。。有人該會問啦围苫,上面不是說可以添加屬性么裤园,按照正常情況來說,我在類文件里面使用@property聲明一個屬性剂府,編譯器就會自動生成一個帶下劃線的成員變量呀拧揽,這樣不就是能添加成員變量嘛,NONONO,在category里面這樣的想法是不對的淤袜,下面我解釋一下原因:

第一痒谴、在分類里使用@property聲明屬性,只是將該屬性添加到該類的屬性列表(instanceProperties)铡羡,聲明的getter积蔚、setter方法添加到實例方法列表,但是不會生成相應的成員變量烦周,也沒有實現(xiàn)setter尽爆、getter方法。
第二读慎、前面有講到category是在運行時期決議的漱贱,因為在運行期也就是編譯完成之后,對象的內存布局已經(jīng)確定贪壳,如果添加實例變量就需要重新內存對齊饱亿,就會破壞類的內部布局,這對編譯型語言來說是不可取的闰靴。

這里說明一點彪笼,其實有很多開發(fā)者包括我自己以前都會誤認為category是不能添加屬性的,其實這種理解是錯誤的蚂且,屬性是可以添加的配猫,在category中@property聲明一個屬性編譯器并不會報錯,但是在運行時期調用setter杏死、getter方法的時候就會報錯泵肄,錯誤原因是找不到該方法,所以也證明了category能添加屬性淑翼,但是不能添加成員變量腐巢。

但是我們在開發(fā)過程中需要往分類中添加成員變量怎么辦呢?
這個問題其實大家都知道改如何解決玄括,那就是runtime的關聯(lián)對象冯丙,說到這,可能有人又會有疑問啦遭京,既然說category中添加成員變量會遇到內存對齊問題胃惜,為什么通過runtime就可以添加呢,所以請帶著疑問繼續(xù)往下繼續(xù)看:

先看一下關聯(lián)對象的實現(xiàn)源碼

void objc_setAssociatedObject_non_gc(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);
}
class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { _lock.lock(); }
    ~AssociationsManager()  { _lock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

通過源代碼我們可以看見哪雕,關聯(lián)對象是由AssociationsManager進行管理船殉,AssociationsManager結構體里面是由一個靜態(tài)AssociationsHashMap來存儲所有的關聯(lián)對象的,
disguised_ptr_t disguised_object = DISGUISE(object);這里是獲取要關聯(lián)屬性的對象的指針地址斯嚎,作為map的key利虫,value對應的是ObjectAssociationMap挨厚,看下ObjectAssociationMap里面都有什么

class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> 

其中void * 就是我們在objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy) 中傳入的key,
然后看下ObjcAssociation

 class ObjcAssociation {
        uintptr_t _policy;
        id _value;
};

這里面的value就是我們關聯(lián)的對象成員變量的值列吼。

到這里幽崩,關聯(lián)對象的解釋基本就結束了,所以看得出來寞钥,通過關聯(lián)對象給category添加成員變量由于是存儲在一個全局的AssociationsManager里面慌申,所以并不會對現(xiàn)有類的內存造成影響。

objc_getAssociatedObject_gc就不做詳細介紹了理郑,大家可以靜下心來閱讀蘋果的源代碼蹄溉。

注: 本文章解釋的比較淺顯,也易懂您炉,如有解釋不當?shù)牡胤綒g迎留言交流柒爵,謝謝!W簟棉胀!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市冀膝,隨后出現(xiàn)的幾起案子唁奢,更是在濱河造成了極大的恐慌,老刑警劉巖窝剖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件麻掸,死亡現(xiàn)場離奇詭異,居然都是意外死亡赐纱,警方通過查閱死者的電腦和手機脊奋,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疙描,“玉大人诚隙,你說我怎么就攤上這事∑鹨龋” “怎么了久又?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長待错。 經(jīng)常有香客問我籽孙,道長烈评,這世上最難降的妖魔是什么火俄? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮讲冠,結果婚禮上瓜客,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好谱仪,可當我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布玻熙。 她就那樣靜靜地躺著,像睡著了一般疯攒。 火紅的嫁衣襯著肌膚如雪嗦随。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天敬尺,我揣著相機與錄音枚尼,去河邊找鬼。 笑死砂吞,一個胖子當著我的面吹牛署恍,可吹牛的內容都是我干的。 我是一名探鬼主播蜻直,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼盯质,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了概而?” 一聲冷哼從身側響起呼巷,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎到腥,沒想到半個月后朵逝,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡乡范,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年配名,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晋辆。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡渠脉,死狀恐怖,靈堂內的尸體忽然破棺而出瓶佳,到底是詐尸還是另有隱情芋膘,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布霸饲,位于F島的核電站为朋,受9級特大地震影響,放射性物質發(fā)生泄漏厚脉。R本人自食惡果不足惜习寸,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望傻工。 院中可真熱鬧霞溪,春花似錦孵滞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至殴蓬,卻和暖如春匿级,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背染厅。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工根蟹, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糟秘。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓简逮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親尿赚。 傳聞我的和親對象是個殘疾皇子散庶,可洞房花燭夜當晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內容