iOS:Category詳解

目錄
一,作用
二淋袖,本質(zhì)
三,同名方法調(diào)用優(yōu)先級(jí)

一锯梁,作用

1即碗,給系統(tǒng)類或第三方類添加屬性

  • 因?yàn)闊o(wú)法修改系統(tǒng)類或第三方類的代碼,所以只能利用分類進(jìn)行添加
  • 因?yàn)樵诜诸愔胁荒芴砑映蓡T變量涝桅,所以系統(tǒng)不會(huì)自動(dòng)生成帶下劃線的成員變量和get/set方法
  • 如果想正常使用分類中的屬性拜姿,那必須利用runtime的關(guān)聯(lián)函數(shù)來(lái)手動(dòng)實(shí)現(xiàn)get/set方法
@interface UIView (Add)
@property (nonatomic, copy) NSString *name;
@end

@implementation UIView (Add)
- (NSString *)name {
    return objc_getAssociatedObject(self, @"name");
}
- (void)setName:(NSString *)name {
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
@end

2烙样,給系統(tǒng)類或第三方類添加方法

  • 因?yàn)闊o(wú)法修改系統(tǒng)類或第三方類的代碼冯遂,所以只能利用分類進(jìn)行添加
@interface UIView (Add)
- (void)removeAllSubviews;
@end

@implementation UIView (Add)
- (void)removeAllSubviews {
    [self.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        [obj removeFromSuperview];
    }];
}
@end

3,拆分自定義類的代碼

  • 如果某類的代碼量比較大谒获,可以將代碼按照功能拆分到各個(gè)分類中
@interface ApiRequest : NSObject
@end

@interface ApiRequest (Login)
@end

@interface ApiRequest (Home)
@end

@interface ApiRequest (My)
@end
二蛤肌,本質(zhì)
@interface Person (Add) <NSCopying>
@property (nonatomic, assign) NSInteger age;
- (void)eat;
+ (void)run;
@end

@implementation Person (Add)
- (void)eat {
    NSLog(@"eat");
}
+ (void)run {
    NSLog(@"run");
}
@end

將上述代碼用clang轉(zhuǎn)為C++代碼,Person (Add)的底層代碼如下批狱,可以看到分類的本質(zhì)是結(jié)構(gòu)體

// 底層結(jié)構(gòu)
struct _category_t {
    const char *name;                              // 類名
    struct _class_t *cls;                          // 指向類的指針
    const struct _method_list_t *instance_methods; // 實(shí)例方法列表
    const struct _method_list_t *class_methods;    // 類方法列表
    const struct _protocol_list_t *protocols;      // 協(xié)議列表
    const struct _prop_list_t *properties;         // 屬性列表
};

// 用_category_t實(shí)例化Person (Add)
static struct _category_t _OBJC_$_CATEGORY_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person",
    0, // &OBJC_CLASS_$_Person,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Add,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Add,
    (const struct _protocol_list_t *)&_OBJC_CATEGORY_PROTOCOLS_$_Person_$_Add,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_Add, 
};

// Person (Add)的實(shí)例方法列表
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"eat", "v16@0:8", (void *)_I_Person_Add_eat}}
};

// Person (Add)的類方法列表
static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    1,
    {{(struct objc_selector *)"run", "v16@0:8", (void *)_C_Person_Add_run}}
};

// Person (Add)的協(xié)議列表
static struct /*_protocol_list_t*/ {
    long protocol_count;  // Note, this is 32/64 bit
    struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    1,
    &_OBJC_PROTOCOL_NSCopying
};

// Person (Add)的屬性列表
static struct /*_prop_list_t*/ {
    unsigned int entsize;  // sizeof(struct _prop_t)
    unsigned int count_of_properties;
    struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_Add __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_prop_t),
    1,
    {{"age","Tq,N"}}
};

注意:因?yàn)?code>_category_t中沒(méi)有成員變量列表裸准,所以在分類中不能添加成員變量

三,同名方法調(diào)用優(yōu)先級(jí)
  • 原類與分類:優(yōu)先調(diào)用分類的
  • 多個(gè)分類:優(yōu)先調(diào)用后編譯的(編譯順序從上往下)

1赔硫, 代碼驗(yàn)證

編譯順序
// Person
@interface Person : NSObject
- (void)eat;
@end

@implementation Person
- (void)eat {
    NSLog(@"Person eat");
}
@end

// Person (Add1)
@interface Person (Add1)
- (void)eat;
@end

@implementation Person (Add1)
- (void)eat {
    NSLog(@"Person (Add1) eat");
}
@end

// Person (Add2)
@interface Person (Add2)
- (void)eat;
@end

@implementation Person (Add2)
- (void)eat {
    NSLog(@"Person (Add2) eat");
}
@end

// 使用
Person *person = [Person new];
[person eat];

// 打印
Person (Add1) eat

2炒俱,源碼分析(源碼下載地址

  • 方法一:重新組織類中的方法
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories

    // 獲取所有的分類
    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);
    }
}
  • 方法二:將分類中的方法附加到原類中
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations

    // 創(chuàng)建方法列表數(shù)組
    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));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    // 倒序遍歷分類列表
    while (i--) {
        // 取出分類
        auto& entry = cats->list[i];
        // 取出分類中的方法列表
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            // 將方法列表正序放入方法列表數(shù)組中
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    // 方法三
    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ù)組附加到原類的方法列表數(shù)組中
void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;
        uint32_t newCount = oldCount + addedCount;
        // 擴(kuò)大原類方法列表數(shù)組的容量
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        // 將原類的方法列表移動(dòng)到數(shù)組末位
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        // 將分類的方法列表數(shù)組復(fù)制到原類方法列表數(shù)組的首位
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
    } 
    else {
        // 1 list -> many lists
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
}
  • 上述三個(gè)方法的邏輯圖
步驟一
步驟二
步驟三

3,注意點(diǎn)

  • 分類是在運(yùn)行期合并到原類中的

  • 分類方法并沒(méi)有覆蓋原類方法,只是放在原類方法的前面权悟,而方法調(diào)用是順序查找砸王,所以優(yōu)先調(diào)用分類方法

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self logMethodNamesWithClass:[Person class]];
}

// 打印類對(duì)象或元類對(duì)象中的方法名
- (void)logMethodNamesWithClass:(Class)cls {
    unsigned int count;
    Method *methodList = class_copyMethodList(cls, &count);
    NSMutableString *methodNames = [NSMutableString string];
    for (int i = 0; i < count; i++) {
        Method method = methodList[i];
        SEL selector = method_getName(method);
        NSString *methodName = NSStringFromSelector(selector);
        [methodNames appendString:methodName];
        [methodNames appendString:@", "];
    }
    free(methodList);
    NSLog(@"%@---%@", cls, methodNames);
}

// 打印
Person---eat, eat, eat,
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市峦阁,隨后出現(xiàn)的幾起案子谦铃,更是在濱河造成了極大的恐慌,老刑警劉巖榔昔,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驹闰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡撒会,警方通過(guò)查閱死者的電腦和手機(jī)嘹朗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)茧彤,“玉大人骡显,你說(shuō)我怎么就攤上這事≡啵” “怎么了惫谤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)珠洗。 經(jīng)常有香客問(wèn)我溜歪,道長(zhǎng),這世上最難降的妖魔是什么许蓖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任蝴猪,我火速辦了婚禮,結(jié)果婚禮上膊爪,老公的妹妹穿的比我還像新娘自阱。我一直安慰自己,他們只是感情好米酬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布沛豌。 她就那樣靜靜地躺著,像睡著了一般赃额。 火紅的嫁衣襯著肌膚如雪加派。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,292評(píng)論 1 301
  • 那天跳芳,我揣著相機(jī)與錄音芍锦,去河邊找鬼。 笑死飞盆,一個(gè)胖子當(dāng)著我的面吹牛娄琉,可吹牛的內(nèi)容都是我干的次乓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼孽水,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼檬输!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起匈棘,我...
    開(kāi)封第一講書(shū)人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤丧慈,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后主卫,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體逃默,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年簇搅,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了完域。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡瘩将,死狀恐怖吟税,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情姿现,我是刑警寧澤肠仪,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站备典,受9級(jí)特大地震影響异旧,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜提佣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一吮蛹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拌屏,春花似錦潮针、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至务唐,卻和暖如春雳攘,著一層夾襖步出監(jiān)牢的瞬間带兜,已是汗流浹背枫笛。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留刚照,地道東北人刑巧。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親啊楚。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吠冤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 參考篇:iOS-分類(Category) 前言:本文簡(jiǎn)述Category原理,如有錯(cuò)誤請(qǐng)留言指正恭理。 第一部分:有關(guān)...
    夢(mèng)蕊dream閱讀 4,885評(píng)論 1 30
  • ??Category是我們?cè)陂_(kāi)發(fā)中經(jīng)常用到的拯辙,它可以在我們不改變?cè)蓄惖那疤嵯聛?lái)動(dòng)態(tài)地給類添加方法,通過(guò)這篇文章颜价,...
    Hello小小酥閱讀 2,446評(píng)論 3 5
  • 我們知道涯保,所有的OC類和對(duì)象,在runtime層都是用struct表示的周伦,category也不例外夕春,在runtim...
    戀空K閱讀 259評(píng)論 0 4
  • Category 概念: OC中特有預(yù)防,它表示一個(gè)指向分類的結(jié)構(gòu)體指針,原則上只能增加方法,不能增加成員變量.定...
    CrystalZhu閱讀 233評(píng)論 0 0
  • 只要你確定做一件事 剩下的就是不斷完善的過(guò)程 認(rèn)定 并注入你的熱愛(ài) —玉紅(2018.7.21)
    玉紅最棒閱讀 110評(píng)論 0 0