詳解iOS中分類Cateogry

首發於個人我的博客html

分類的基本使用

  • 首先咱們定義一個類 YZPerson 繼承自 NSObject
@interface YZPerson : NSObject
@end
複製代碼
  • 而後定義一個分類 YZPerson+test1.h
#import "YZPerson.h"

@interface YZPerson (test1)
-(void)run;
@end


#import "YZPerson+test1.h"

@implementation YZPerson (test1)
-(void)run{
    NSLog(@"%s",__func__);
}
@end
複製代碼
  • 在控制器 ViewController 中使用
- (void)viewDidLoad {
    [super viewDidLoad];
    
    YZPerson *person = [[YZPerson alloc] init];
    [person run];
}
複製代碼
  • 執行結果爲

CateogryDemo[23773:321096] -[YZPerson(test1) run]git

注意點:若是原來的類和分類中有一樣的方法,那麼執行的結果的是分類中的,例如程序員

#import <Foundation/Foundation.h>

@interface YZPerson : NSObject
-(void)run;
@end



#import "YZPerson.h"

@implementation YZPerson
-(void)run{
    NSLog(@"%s",__func__);
}
@end
複製代碼
  • 執行結果不會發生變化,依然是

CateogryDemo[23773:321096] -[YZPerson(test1) run]github

緣由在後面分析數組

分類的結構

打開終端,進入項目下,執行以下命令,生成C語言的文件

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc YZPerson+test1.mbash

生成 YZPerson+test1.cpp文件

摘取主要代碼以下app

struct _category_t {
	const char *name;
	struct _class_t *cls;
	const struct _method_list_t *instance_methods;
	const struct _method_list_t *class_methods;
	const struct _protocol_list_t *protocols;
	const struct _prop_list_t *properties;
};

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_YZPerson_$_test1 __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_objc_method),
	1,
	{{(struct objc_selector *)"run", "v16@0:8", (void *)_I_YZPerson_test1_run}}
};


extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_YZPerson;

static struct _category_t _OBJC_$_CATEGORY_YZPerson_$_test1 __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
	"YZPerson",
	0, // &OBJC_CLASS_$_YZPerson,
	(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_YZPerson_$_test1,
	0,
	0,
	0,
};


static void OBJC_CATEGORY_SETUP_$_YZPerson_$_test1(void ) {
	_OBJC_$_CATEGORY_YZPerson_$_test1.cls = &OBJC_CLASS_$_YZPerson;
}

複製代碼

說明編譯完以後每個分類都會生成一個iphone

_category_t源碼分析

的結構體,裏面有名稱,對象方法列表,類方法列表,協議方法列表,屬性列表,若是對應的爲空,好比協議爲空,屬性爲空,那麼結構體中保存的就是0。post

objc-runtime-new.h

打開源碼最新的源碼 runtime源碼看,objc-runtime-new.h中分類結構體是這樣的

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;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

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

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
複製代碼

源碼分析

源碼解讀順序

objc-os.mm
_objc_init
map_images
map_images_nolock

objc-runtime-new.mm
_read_images
remethodizeClass
attachCategories
attachLists
realloc、memmove、 memcpy
複製代碼
  • 先找到 objc-os.mm 類,裏面的
// runtime初始化方法
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();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
複製代碼
  • 繼續跟下去
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

複製代碼
  • 查看
map_images_nolock
複製代碼

找到

if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
複製代碼

到了文件 objc-runtime-new.mm 中

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
	// 關鍵代碼
	 remethodizeClass(cls);
	 // 關鍵代碼
     remethodizeClass(cls->ISA());

}

複製代碼
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)
{
    // cats 分類列表
    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循環 合併分類中的 對象方法 屬性 協議
    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, 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);
}
複製代碼

memmove memcpy

上面的代碼繼續跟下去來到了

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;
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;
            // 內存挪動
            memmove(array()->lists + addedCount, 
            				array()->lists,
                    oldCount * sizeof(array()->lists[0]));
            // 內存拷貝
            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]));
        }
    }

複製代碼

其中關鍵代碼是

// 內存挪動
 memmove(array()->lists + addedCount,
                    array()->lists,
                    oldCount * sizeof(array()->lists[0]));
// 內存拷貝
 memcpy(array()->lists, addedLists,
                   addedCount * sizeof(array()->lists[0]));
複製代碼

關於memcpy與memmove的區別,能夠參考 memcpy與memmove的區別

簡單總結就是:

區別就在於關鍵字restrict, memcpy假定兩塊內存區域沒有數據重疊,而memmove沒有這個前提條件。若是複製的兩個區域存在重疊時使用memcpy,其結果是不可預知的,有可能成功也有可能失敗的,因此若是使用了memcpy,程序員自身必須確保兩塊內存沒有重疊部分

總結

合併分類的時候,其方法列表等,不會覆蓋掉原來類中的方法,是共存的。可是分類中的方法在前面,原來的類中的方法在後面,調用的時候,就會調用分類中的方法,若是多個分類有一樣的方法,後編譯的分類會調用。

問題

Category的使用場合是什麼?

  • 不一樣模塊的功能區分開來,可使用分類實現

Category的實現原理

  • Category編譯以後的底層結構是struct category_t,裏面存儲着分類的對象方法、類方法、屬性、協議信息 在程序運行的時候,runtime會將Category的數據,合併到類信息中(類對象、元類對象中)

Category和Class Extension的區別是什麼?

Class Extension在編譯的時候,它的數據就已經包含在類信息中 Category是在運行時,纔會將數據合併到類信息中

Category中有load方法嗎?load方法是何時調用的?load 方法能繼承嗎?

有load方法 load方法在runtime加載類、分類的時候調用 load方法能夠繼承,可是通常狀況下不會主動去調用load方法,都是讓系統自動調用

Category可否添加成員變量?若是能夠,如何給Category添加成員變量?

  • 不能直接給Category添加成員變量,可是能夠間接實現Category有成員變量的效果,關聯對象

詳細分析見 細說關聯對象

本文相關代碼github地址 github

本文參考資料:

runtime源碼

iOS底層原理

memcpy與memmove的區別

更多資料,歡迎關注我的公衆號,不定時分享各類技術文章。

相關文章
相關標籤/搜索