為了表示我對(duì)簡(jiǎn)書『飽醉豚』事件的不滿便斥,簡(jiǎn)書不再更新,后續(xù)有文章只更新 個(gè)人博客和 掘金
分類的基本使用
- 首先我們定義一個(gè)類 YZPerson 繼承自 NSObject
@interface YZPerson : NSObject
@end
- 然后定義一個(gè)分類 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];
}
- 執(zhí)行結(jié)果為
CateogryDemo[23773:321096] -[YZPerson(test1) run]
注意點(diǎn):如果原來(lái)的類和分類中有同樣的方法秽浇,那么執(zhí)行的結(jié)果的是分類中的谦炒,例如
#import <Foundation/Foundation.h>
@interface YZPerson : NSObject
-(void)run;
@end
#import "YZPerson.h"
@implementation YZPerson
-(void)run{
NSLog(@"%s",__func__);
}
@end
- 執(zhí)行結(jié)果不會(huì)發(fā)生變化鸠蚪,依然是
CateogryDemo[23773:321096] -[YZPerson(test1) run]
原因在后面分析
分類的結(jié)構(gòu)
打開終端肠阱,進(jìn)入項(xiàng)目下,執(zhí)行如下命令街佑,生成C語(yǔ)言的文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc YZPerson+test1.m
生成 YZPerson+test1.cpp文件
摘取主要代碼如下
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;
}
說(shuō)明編譯完之后每一個(gè)分類都會(huì)生成一個(gè)
_category_t
的結(jié)構(gòu)體谢翎,里面有名稱,對(duì)象方法列表沐旨,類方法列表森逮,協(xié)議方法列表,屬性列表,如果對(duì)應(yīng)的為空磁携,比如協(xié)議為空褒侧,屬性為空,那么結(jié)構(gòu)體中保存的就是0。
objc-runtime-new.h
打開源碼最新的源碼 runtime源碼看闷供,objc-runtime-new.h中分類結(jié)構(gòu)體是這樣的
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);
}
- 繼續(xù)跟下去
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)
{
// 關(guān)鍵代碼
remethodizeClass(cls);
// 關(guān)鍵代碼
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);
}
}
主要代碼合注釋已經(jīng)在代碼中展示了
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
// cats 分類列表
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// 方法數(shù)組 二維數(shù)組
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
// 屬性數(shù)組 二維數(shù)組
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
// 協(xié)議數(shù)組 二維數(shù)組
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;
// 這個(gè)while循環(huán) 合并分類中的 對(duì)象方法 屬性 協(xié)議
while (i--) {
// 取出某個(gè)分類
auto& entry = cats->list[i];
// 取出分類中的對(duì)象方法
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;
}
// 取出分類中的協(xié)議
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
// 得到類對(duì)象里面的數(shù)據(jù)
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 所有分類的對(duì)象方法吊档,附加到類對(duì)象的方法列表中
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
// 所有分類的屬性篙议,附加到類對(duì)象的屬性列表中唾糯,
rw->properties.attachLists(proplists, propcount);
free(proplists);
// 所有分類的協(xié)議,附加到類對(duì)象的協(xié)議列表中
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
memmove memcpy
上面的代碼繼續(xù)跟下去來(lái)到了
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;
// 內(nèi)存挪動(dòng)
memmove(array()->lists + addedCount,
array()->lists,
oldCount * sizeof(array()->lists[0]));
// 內(nèi)存拷貝
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]));
}
}
其中關(guān)鍵代碼是
// 內(nèi)存挪動(dòng)
memmove(array()->lists + addedCount,
array()->lists,
oldCount * sizeof(array()->lists[0]));
// 內(nèi)存拷貝
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
關(guān)于memcpy與memmove的區(qū)別鬼贱,可以參考 memcpy與memmove的區(qū)別
簡(jiǎn)單總結(jié)就是:
區(qū)別就在于關(guān)鍵字restrict, memcpy假定兩塊內(nèi)存區(qū)域沒有數(shù)據(jù)重疊移怯,而memmove沒有這個(gè)前提條件。如果復(fù)制的兩個(gè)區(qū)域存在重疊時(shí)使用memcpy这难,其結(jié)果是不可預(yù)知的舟误,有可能成功也有可能失敗的,所以如果使用了memcpy,程序員自身必須確保兩塊內(nèi)存沒有重疊部分
總結(jié)
合并分類的時(shí)候姻乓,其方法列表等嵌溢,不會(huì)覆蓋掉原來(lái)類中的方法,是共存的蹋岩。但是分類中的方法在前面赖草,原來(lái)的類中的方法在后面,調(diào)用的時(shí)候剪个,就會(huì)調(diào)用分類中的方法秧骑,如果多個(gè)分類有同樣的方法,后編譯的分類會(huì)調(diào)用扣囊。
問題
Category的使用場(chǎng)合是什么乎折?
- 不同模塊的功能區(qū)分開來(lái),可以使用分類實(shí)現(xiàn)
Category的實(shí)現(xiàn)原理
- Category編譯之后的底層結(jié)構(gòu)是struct category_t侵歇,里面存儲(chǔ)著分類的對(duì)象方法骂澄、類方法、屬性惕虑、協(xié)議信息
在程序運(yùn)行的時(shí)候坟冲,runtime會(huì)將Category的數(shù)據(jù),合并到類信息中(類對(duì)象枷遂、元類對(duì)象中)
Category和Class Extension的區(qū)別是什么樱衷?
Class Extension在編譯的時(shí)候,它的數(shù)據(jù)就已經(jīng)包含在類信息中
Category是在運(yùn)行時(shí)酒唉,才會(huì)將數(shù)據(jù)合并到類信息中
Category中有l(wèi)oad方法嗎矩桂?load方法是什么時(shí)候調(diào)用的?load 方法能繼承嗎?
有l(wèi)oad方法
load方法在runtime加載類侄榴、分類的時(shí)候調(diào)用
load方法可以繼承雹锣,但是一般情況下不會(huì)主動(dòng)去調(diào)用load方法,都是讓系統(tǒng)自動(dòng)調(diào)用
Category能否添加成員變量癞蚕?如果可以蕊爵,如何給Category添加成員變量?
- 不能直接給Category添加成員變量桦山,但是可以間接實(shí)現(xiàn)Category有成員變量的效果,關(guān)聯(lián)對(duì)象
本文相關(guān)代碼github地址 github
本文參考資料:
更多資料攒射,歡迎關(guān)注個(gè)人公眾號(hào),不定時(shí)分享各種技術(shù)文章恒水。