iOS:Category詳解

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

一,作用

1,給系統(tǒng)類(lèi)或第三方類(lèi)添加屬性

  • 因?yàn)闊o(wú)法修改系統(tǒng)類(lèi)或第三方類(lèi)的代碼,所以只能利用分類(lèi)進(jìn)行添加
  • 因?yàn)樵诜诸?lèi)中不能添加成員變量,所以系統(tǒng)不會(huì)自動(dòng)生成帶下劃線的成員變量和get/set方法
  • 如果想正常使用分類(lèi)中的屬性,那必須利用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)類(lèi)或第三方類(lèi)添加方法

  • 因?yàn)闊o(wú)法修改系統(tǒng)類(lèi)或第三方類(lèi)的代碼,所以只能利用分類(lèi)進(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,拆分自定義類(lèi)的代碼

  • 如果某類(lèi)的代碼量比較大,可以將代碼按照功能拆分到各個(gè)分類(lèi)中
@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)的底層代碼如下,可以看到分類(lèi)的本質(zhì)是結(jié)構(gòu)體

// 底層結(jié)構(gòu)
struct _category_t {
    const char *name;                              // 類(lèi)名
    struct _class_t *cls;                          // 指向類(lèi)的指針
    const struct _method_list_t *instance_methods; // 實(shí)例方法列表
    const struct _method_list_t *class_methods;    // 類(lèi)方法列表
    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)的類(lèi)方法列表
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)有成員變量列表,所以在分類(lèi)中不能添加成員變量

三,同名方法調(diào)用優(yōu)先級(jí)
  • 原類(lèi)與分類(lèi):優(yōu)先調(diào)用分類(lèi)的
  • 多個(gè)分類(lèi):優(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,源碼分析(源碼下載地址

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

    runtimeLock.assertLocked();

    isMeta = cls->isMetaClass();

    // Re-methodizing: check for more categories

    // 獲取所有的分類(lè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);
    }
}
  • 方法二:將分類(lèi)中的方法附加到原類(lèi)中
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;
    // 倒序遍歷分類(lèi)列表
    while (i--) {
        // 取出分類(lèi)
        auto& entry = cats->list[i];
        // 取出分類(lè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);
}
  • 方法三:將分類(lèi)的方法列表數(shù)組附加到原類(lèi)的方法列表數(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ò)大原類(lèi)方法列表數(shù)組的容量
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        // 將原類(lèi)的方法列表移動(dòng)到數(shù)組末位
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        // 將分類(lèi)的方法列表數(shù)組復(fù)制到原類(lèi)方法列表數(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)

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

  • 分類(lèi)方法并沒(méi)有覆蓋原類(lèi)方法,只是放在原類(lèi)方法的前面,而方法調(diào)用是順序查找,所以?xún)?yōu)先調(diào)用分類(lèi)方法

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

// 打印類(lèi)對(duì)象或元類(lèi)對(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)系作者
【社區(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)容

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

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