面試題:
1.Category的實(shí)現(xiàn)原理
析:
Category編譯之后的底層結(jié)構(gòu)是struct_category_t,
里面存儲(chǔ)著分類的對(duì)象方法、類方法、屬性 、協(xié)議信息,
在程序運(yùn)行的時(shí)候,runtime會(huì)將Category的數(shù)據(jù),合并到類信息中(類對(duì)象,元類對(duì)象中)
2.Category能否添加成員變量?如果可以,如何給Category添加成員變量?
析:
不能直接給Category添加成員變量,但是可以間接實(shí)現(xiàn)Category有成員變量的效果
3.Category和Class Extension的區(qū)別是什么?
析:
Class Extension在編譯的時(shí)候,它的數(shù)據(jù)就已經(jīng)包含在類信息中
Category是在運(yùn)行時(shí),才會(huì)將數(shù)據(jù)合并到類信息中
4.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)用
5.load、initialize方法的區(qū)別是啥?它們在Category中的調(diào)用順序?以及出現(xiàn)繼承時(shí)他們之間的調(diào)用過程?
接下來我們逐步分析:
準(zhǔn)備:首先搭建好可以跑runtime源碼的過程,參考配置運(yùn)行objc4-750和使用
gitHub_Demo
1.Category簡單的使用
//Person類
@interface Person : NSObject{
double _height;
}
-(void)run;
@end
@implementation Person
-(void)run{
NSLog(@"%s",__FUNCTION__);
}
@end
//Person+addCategory --->Person的分類
@interface Person (addCategory)
-(void)speak;
@property (nonatomic,strong)NSString *name;
@end
@implementation Person (addCategory)
-(void)speak{
NSLog(@"%s--->%f",__FUNCTION__,_height);
}
@end
//myPerson --->Person的子類
@interface myPerson : Person
@end
@implementation myPerson
-(void)speak{
NSLog(@"%s",__FUNCTION__);
}
@end
//Person+addCategory2 ----->Person的分類
@interface Person (addCategory2)
-(void)speak;
@end
@implementation Person (addCategory2)
-(void)speak{
NSLog(@"%s",__FUNCTION__);
}
-(void)run{
NSLog(@"%s",__FUNCTION__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
myPerson *p = [[myPerson alloc]init];
[p run];
[p speak];
// NSLog(@"name-->%@",p.name);//-[Person name]: unrecognized selector sent to instance 0x600001c9cf50
}
return 0;
}
發(fā)現(xiàn):---------------------
1.在不修改原有的類的基礎(chǔ)上,為這個(gè)類添加一些方法
2.分類是用于給原有類添加方法的, 它只能添加方法, 不能添加屬性(成員變量)
3.可以在分類中訪問原有類中公開的屬性
4.方法的調(diào)用順序:分類->本類->父類
5.如果分類中有和原有類同名的方法, 會(huì)調(diào)用分類中的方法(說會(huì)忽略原有類的方法)
6.分類中的@property, 只會(huì)生成setter/getter方法的聲明, 不會(huì)生成實(shí)現(xiàn)以及私有的成員變量
7.如果多個(gè)分類中都有和原有類中同名的方法, 那么調(diào)用該方法的時(shí)候執(zhí)行誰由編譯器決定
會(huì)執(zhí)行最后一個(gè)參與編譯的分類中的方法(即編譯的時(shí)候加的.m文件)
2.Category的本質(zhì)
- 首先:打開打印信息的配置 如下:

category_01.png
OBJC_PRINT_CLASS_SETUP:YES
- 我們看下下面的代碼:
//創(chuàng)建一個(gè)Student類
#import <Foundation/Foundation.h>
#import "myProtocol.h"
@interface Student : NSObject
@property (assign,nonatomic) id<myProtocol> delegate;
@end
#import "Student.h"
@implementation Student
@end
========創(chuàng)建一個(gè)protocol======
#import <Foundation/Foundation.h>
@protocol myProtocol <NSObject>
@required
-(void)test;
@optional
-(void)test2;
@end
=======創(chuàng)建分類=======
#import "Student.h"
@interface Student (addCategory)
-(void)run;
+(void)play;
@property (strong,nonatomic) NSString *testPro;
@end
#import "Student+addCategory.h"
@implementation Student (addCategory)
-(void)run{
NSLog(@"%s",__FUNCTION__);
[self.delegate test];
[self.delegate test2];
}
+(void)play{
NSLog(@"%s",__FUNCTION__);
}
-(void)setTestPro:(NSString *)testPro{
NSLog(@"setTestPro");
}
-(NSString*)testPro{
NSLog(@"getTestPro");
return @"111";
}
@end
======創(chuàng)建一個(gè)Preson對(duì)象=====
#import <Foundation/Foundation.h>
#import "myProtocol.h"
@interface Person : NSObject<myProtocol>
@end
#import "Person.h"
@implementation Person
-(void)test{
NSLog(@"執(zhí)行protocol的方法---test");
}
-(void)test2{
NSLog(@"執(zhí)行protocol的方法---test2");
}
@end
=====在main里面=====
#import <Foundation/Foundation.h>
#import "Student.h"
#import "Student+addCategory.h"
#import <objc/runtime.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Student *stu = [[Student alloc]init];
Person *p = [[Person alloc]init];
stu.delegate = p;
[stu run];
[Student play];
unsigned int count;
//實(shí)例對(duì)象方法
Method *methodList = class_copyMethodList(object_getClass(stu), &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methodList[I];
NSString *methodName = NSStringFromSelector(method_getName(method));
NSLog(@"實(shí)例_方法名:%d----%@",i,methodName);
}
free(methodList);
//類對(duì)象方法
Method *methodList2 = class_copyMethodList(object_getClass([Student class]), &count);
for (unsigned int i = 0; i < count; i++) {
Method method = methodList2[I];
NSString *methodName = NSStringFromSelector(method_getName(method));
NSLog(@"類對(duì)象_方法名:%d----%@",i,methodName);
}
free(methodList2);
stu.testPro = @"111";
NSLog(@"---%@",stu.testPro);
}
return 0;
}
=====================部分控制臺(tái)log=======================
objc[20404]: CLASS: realizing class 'Student' (meta) 0x100002818 0x100002268 #0
objc[20404]: CLASS: methodizing class 'Student' (meta)
objc[20404]: METHOD +[Student play]
objc[20404]: CLASS: realizing class 'Student' 0x100002840 0x1000022d8 #0
objc[20404]: CLASS: methodizing class 'Student'
objc[20404]: METHOD -[Student setTestPro:]
objc[20404]: METHOD -[Student testPro]
objc[20404]: METHOD -[Student delegate]
objc[20404]: METHOD -[Student setDelegate:]
objc[20404]: METHOD -[Student run]
objc[20404]: CLASS: realizing class 'Person' (meta) 0x100002868 0x100002678 #0
objc[20404]: CLASS: methodizing class 'Person' (meta)
objc[20404]: CLASS: realizing class 'Person' 0x100002890 0x100002740 #0
objc[20404]: CLASS: methodizing class 'Person'
objc[20404]: METHOD -[Person test2]
objc[20404]: METHOD -[Person test]
02.Category的底層分析[20404:2857276] -[Student(addCategory) run]
02.Category的底層分析[20404:2857276] 執(zhí)行protocol的方法---test
02.Category的底層分析[20404:2857276] 執(zhí)行protocol的方法---test2
02.Category的底層分析[20404:2857276] +[Student(addCategory) play]
02.Category的底層分析[20404:2857276] 實(shí)例_方法名:0----setTestPro:
02.Category的底層分析[20404:2857276] 實(shí)例_方法名:1----testPro
02.Category的底層分析[20404:2857276] 實(shí)例_方法名:2----delegate
02.Category的底層分析[20404:2857276] 實(shí)例_方法名:3----setDelegate:
02.Category的底層分析[20404:2857276] 實(shí)例_方法名:4----run
02.Category的底層分析[20404:2857276] 類對(duì)象_方法名:0----play
02.Category的底層分析[20404:2857276] setTestPro
02.Category的底層分析[20404:2857276] getTestPro
02.Category的底層分析[20404:2857276] ---111
現(xiàn)象—>在runtime進(jìn)行加載【鏡像】的時(shí)候,底層做了處理
-
接下來我們看看runtime底層到底做了什么?見下
1. void _objc_init(void){ static bool initialized = false; if (initialized) return; initialized = true; environ_init(); tls_init(); static_init(); lock_init(); exception_init(); // 動(dòng)態(tài)的加載【鏡像】并且載入到內(nèi)存中去 _dyld_objc_notify_register(&map_images, load_images, unmap_image); }2. void 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); }3. void map_images_nolock(unsigned mhCount, const char * const mhPaths[], const struct mach_header * const mhdrs[]){ 。。。。。。 if (hCount > 0) { //開始裝載所有的類 _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses); } firstTime = NO; }4. void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses){ 。。。。。。。 // Discover categories. //開始去 【尋找】分類啦 for (EACH_HEADER) { category_t **catlist = _getObjc2CategoryList(hi, &count);//_getObjc2CategoryList-->獲取到分類列表 bool hasClassProperties = hi->info()->hasCategoryClassProperties(); //進(jìn)行遍歷,獲取其中的方法,協(xié)議,屬性等 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) //如果分類的實(shí)例方法、協(xié)議、實(shí)例屬性存在一個(gè) { 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 || (hasClassProperties && 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); } } } } 。。。 }
5.
/***********************************************
* 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.assertLocked();
//判斷是否為元類
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
//獲取該類未h掛載的分類列表
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);
}
}
//說明:
(1)將 Category 的內(nèi)容添加到已經(jīng)存在的 Class 中,最后刷新下 method caches
(2)調(diào)用 attachCategories 函數(shù)之前,會(huì)先使用 unattachedCategoriesForClass 函數(shù)獲取類中還未添加的類別列表。這個(gè)列表類型為 locstamped_category_list_t,它封裝了 category_t 以及對(duì)應(yīng)的 header_info。header_info 存儲(chǔ)了實(shí)體在鏡像中的加載和初始化狀態(tài),以及一些偏移量.
6.
// 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;//如果沒有分類列表 return返回
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();//是否是元類
//分配相應(yīng)的實(shí)例/類方法、屬性、協(xié)議列表指針,相當(dāng)于二維鏈表,一個(gè)分類對(duì)應(yīng)一個(gè)一維鏈表(分別分配內(nèi)存空間)
// 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--) {//循環(huán)分類列表
//取出第 i 個(gè)分類
auto& entry = cats->list[I];
//從分類里取出對(duì)應(yīng)的實(shí)例/類方法表-->存入到mlist數(shù)組里面
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
//從分類里取出對(duì)應(yīng)的實(shí)例/類屬性列表,并加到對(duì)應(yīng)的二維鏈表中-->存入到proplist數(shù)組里面
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
//從分類里取出遵守的協(xié)議列表,并加到對(duì)應(yīng)的二維鏈表中-->存入到protolist數(shù)組里面
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
//遍歷完分類后,取出類/元類加載到內(nèi)存(堆區(qū))的 class_rw_t 結(jié)構(gòu)體
//class_rw_t中存放著類對(duì)象的方法,屬性和協(xié)議等數(shù)據(jù)
auto rw = cls->data();
//準(zhǔn)備方法列表:加鎖掃描方法列表,將新方法放在每一個(gè)分類的方法前面(對(duì)每個(gè)分類方法進(jìn)行排序)
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
//attachList方法內(nèi)部將分類和本類相應(yīng)的對(duì)象方法,屬性,和協(xié)議進(jìn)行了合并
// 添加方法到類/元類中
rw->methods.attachLists(mlists, mcount);
free(mlists);// 釋放二維方法列表
if (flush_caches && mcount > 0) flushCaches(cls);//刷新方法緩存
rw->properties.attachLists(proplists, propcount);//添加屬性到類/元類中
free(proplists); //釋放二維屬性列表
//添加遵守的協(xié)議到類/元類中
rw->protocols.attachLists(protolists, protocount);
//釋放二維協(xié)議列表
free(protolists);
}
7.
// attachList方法內(nèi)部將分類和本類相應(yīng)的對(duì)象方法,屬性,和協(xié)議進(jìn)行了合并
//addedLists:需要添加的列表
//addedCount:列表個(gè)數(shù)
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
//realloc ->memmove -> memcpy
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count; //原來個(gè)數(shù)
uint32_t newCount = oldCount + addedCount; //新的個(gè)數(shù)
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
/*
void *memmove(void *__dst, const void *__src, size_t __len);
__dst:移動(dòng)內(nèi)存的目的地
__src:被移動(dòng)的內(nèi)存首地址
__len : 被移動(dòng)的內(nèi)存長度
作用:將__src的內(nèi)存移動(dòng)__len塊內(nèi)存 到 __dst中
*/
//內(nèi)存移動(dòng)
memmove(
array()->lists + addedCount,// array()->lists->類對(duì)象原來的方法列表,屬性列表,協(xié)議列表 + 分類數(shù)組長度
array()->lists,
oldCount * sizeof(array()->lists[0])//原來數(shù)組占據(jù)的空間
);
/*內(nèi)存拷貝
void *memcpy(void *__dst, const void *__src, size_t __n);
__dst : 拷貝內(nèi)存的拷貝目的地
__src : 被拷貝的內(nèi)存首地址
__n : 被移動(dòng)的內(nèi)存長度
將__src的內(nèi)存拷貝__n塊內(nèi)存到__dst中
*/
//內(nèi)存復(fù)制
memcpy(
array()->lists,//原來方法、屬性、協(xié)議列表數(shù)組
addedLists,//分類方法、屬性、協(xié)議列表數(shù)組
addedCount * sizeof(array()->lists[0]) //增加的數(shù)組占據(jù)的空間
);
/*
發(fā)現(xiàn)原來指針并沒有改變,至始至終指向開頭的位置。并且經(jīng)過memmove和memcpy方法之后,分類的方法,屬性,協(xié)議列表被放在了類對(duì)象中原本存儲(chǔ)的方法,屬性,協(xié)議列表前面。
那么為什么要將分類方法的列表追加到本來的對(duì)象方法前面呢,這樣做的目的是為了保證分類方法優(yōu)先調(diào)用,我們知道當(dāng)分類重寫本類的方法時(shí),會(huì)覆蓋本類的方法。
其實(shí)經(jīng)過上面的分析我們知道本質(zhì)上并不是覆蓋,而是優(yōu)先調(diào)用
*/
}
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]));
}
}
對(duì)于最后的內(nèi)存移動(dòng)和復(fù)制 —>具體看下圖
category_02.jpeg

category_02.jpeg
大概的流程如下:
objc-os.mm
- _objc_init
- map_images
- map_images_nolock
objc_runtime-new.mm
- _read_images
- remethodizeClass
- attachCategories
- methods.attachLists、properties.attachLists、protocols.attachLists
- realloc、memmove、memcpy
3.我們從另外一個(gè)角度看看本質(zhì)
在控制臺(tái)輸入:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Student+addCategory.m -o category.cpp
發(fā)現(xiàn).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[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Student_$_addCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
3,
{{(struct objc_selector *)"run", "v16@0:8", (void *)_I_Student_addCategory_run},
{(struct objc_selector *)"setTestPro:", "v24@0:8@16", (void *)_I_Student_addCategory_setTestPro_},
{(struct objc_selector *)"testPro", "@16@0:8", (void *)_I_Student_addCategory_testPro}}
};
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_Student_$_addCategory __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"play", "v16@0:8", (void *)_C_Student_addCategory_play}}
};
總結(jié):
category的底層原理、Category 不能直接添加成員變量的原因
- 將category中的方法,屬性,協(xié)議數(shù)據(jù)放在category_t結(jié)構(gòu)體中,
- 然后將結(jié)構(gòu)體內(nèi)的方法列表拷貝到類對(duì)象的方法列表中。
- 在 App 啟動(dòng)加載鏡像文件時(shí),會(huì)在
_read_images函數(shù)間接調(diào)用到attachCategories函數(shù),完成向類中添加 Category的工作。原理就是向class_rw_t中的method_array_t,property_array_t,protocol_array_t數(shù)組中分別添加method_list_t,property_list_t,protocol_list_t指
- 在 App 啟動(dòng)加載鏡像文件時(shí),會(huì)在
- Category可以添加屬性,但是并不會(huì)自動(dòng)生成成員變量及set/get方法。因?yàn)閏ategory_t結(jié)構(gòu)體中并不存在成員變量。通過之前對(duì)對(duì)象的分析我們知道成員變量是存放在實(shí)例對(duì)象中的,并且編譯的那一刻就已經(jīng)決定好了。而分類是在運(yùn)行時(shí)才去加載的。那么我們就無法再程序運(yùn)行時(shí)將分類的成員變量中添加到實(shí)例對(duì)象的結(jié)構(gòu)體中。因此分類中不可以添加成員變量
友情鏈接: