extension(類擴展)和 category(類別)

extension(類擴展)

這裏寫圖片描述

簡單來說,extension在.m文件中添加,所以其權限爲private,所以只能拿到源碼的類添加extension。另外extension是編譯時決議,和interface和implement裏的代碼融合在一塊了一般。


category(類別)

category能在不繼承類的情況下給類動態添加方法。

1、創建category

這裏寫圖片描述

關於@dynamic的特性及用法可參考:
https://blog.csdn.net/qq_28446287/article/details/79094491


2、category的優缺點

  • 可以將類的實現代碼分散到多個不同的文件或框架中
    這裏寫圖片描述

  • 創建對私有方法的前向引用
    OC語法中,你不能對一個類的方法定義爲private,只有+、-,對實例變量可以private、public。
    具體可參考此文檔http://www.cnblogs.com/stevenwuzheng/p/5457487.html

  • 向對象添加非正式協議
    對NSObject進行一個類別叫做非正式協議,可以只實現想要的方法


3、category在runtime中的源碼

typedef struct objc_category *Category;
struct objc_category 
{
    //類別的名字
    char * _Nonnull category_name                            OBJC2_UNAVAILABLE;
    //該類的名字
    char * _Nonnull class_name                               OBJC2_UNAVAILABLE;
    //實例方法列表
    struct objc_method_list * _Nullable instance_methods     OBJC2_UNAVAILABLE;
    //類方法列表
    struct objc_method_list * _Nullable class_methods        OBJC2_UNAVAILABLE;
    //協議列表
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

明顯看出,Category沒有容納變量的地方。


4、category的原理

objective-c的運行依賴runtime,runtime依賴於dyld動態加載,查看objc-os.mm文件發現其調用棧如下:

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;

    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    // Register for unmap first, in case some +load unmaps something
    _dyld_register_func_for_remove_image(&unmap_image);
    dyld_register_image_state_change_handler(dyld_image_state_bound,
                                             1/*batch*/, &map_2_images);
    dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}

category被附加到類上是在map_images的時候發生的。在new-ABI的標準下,_objc_init函數裏調用的map_iamges最終會調用objc-runtime-new.mm中的_read_images函數。_read_images中部分代碼:

 // Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
            }

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                /* ||  cat->classProperties */) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }

    ts.log("IMAGE TIMES: discover categories");

從代碼可以看出:

將category和它的主類(或元類)註冊到哈希表中.
如果主類(或元類)已經實現,那麼重建它的方列表。

category中的實例方法和屬性被整合到主類中,而類方法被整合到元類中。而協議被同時整合到了主類和元類中。

/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void remethodizeClass(Class cls)
{
    category_list *cats;
    bool isMeta;

    runtimeLock.assertWriting();

    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);
    }
}

如註釋所說,此函數將未處理的category整合到主類(或元類),整合cls的方法、協議、屬性列表,更新cls及其子類的方法緩存。

查看其中的attachCategories函數:

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
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
    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) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = entry.cat->propertiesForMeta(isMeta);
        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);
}

通過while循環,遍歷所有的category,得每個category的方法列表mlist、proplist和protolist並存入主類(或元類)的mlists、proplists和protolists中。從而更新類的數據字段data()中mlist、proplist和protolist的值。

category沒有替換掉原來類的方法,也就是說如果category和原來類有method1,那麼在將category整合到原來類之後,類的method_list會有兩個method1

category中的method1會被放在類的method_list前面,而原來類的method1被放 到了method_list後面,在調用時候會先調用method_list前面的,所以看起來是將原來類的method1覆蓋了,實際上並不是那麼回事。


5、category的兩個面試題

3.1 一個類和它的category同時擁有一個方法,在調用時候調用哪一個?答案參考“2、category的原理”

3.2 一個類有多個category並都擁有同一個方法,在調用時候調用哪一個?答案參考“2、category的原理”

舉個例子:

//#import "type.m"
- (void)test
{
    NSLog(@"type class!!");
}

//#import "type+xxxx.m"
- (void)test
{
    NSLog(@"category xxxx");
}

//#import "type+xxxx1.m"
- (void)test
{
    NSLog(@"category xxxx1");
}

//#import "type+xxxx2.m"
- (void)test
{
    NSLog(@"category xxxx2");
}

這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述

可以知道,輸出的結果跟compile source文件中的category的.m文件放置順序有關。且放最底部的時候輸出(主類.m文件的放置不影響,理由參考”2、category的原理”)


6、category動態添加變量

@interface type (xxxx)

@property (nonatomic, assign)  NSInteger number;

@end


static void *numberKey = &numberKey;

@implementation type (xxxx)

- (void)setNumber:(NSInteger)number
{
    objc_setAssociatedObject(self, &numberKey, [NSString stringWithFormat:@"%ld",number], OBJC_ASSOCIATION_ASSIGN);

}

- (NSInteger)number
{
    return [objc_getAssociatedObject(self, &numberKey) integerValue];
}

@end

objc_setAssociatedObject和objc_getAssociatedObject的描述可參考:https://www.cnblogs.com/liuting-1204/p/6526342.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章