一、分類的本質
1传惠、我們先準備一個.m
文件包含主類和分類
#import <Foundation/Foundation.h>
//類
@interface MCStudyCate : NSObject
@property(nonatomic, strong) NSString* nickName;
@end
@implementation MCStudyCate
@end
//分類
@interface MCStudyCate (KC)
@property(nonatomic, strong) NSString* cate_pro1;
@property(nonatomic, strong) NSString* cate_pro2;
-(void)cate_instanceMethod1;
-(void)cate_instanceMethod2;
-(void)cate_instanceMethod3;
+(void)cate_classMethod;
@end
@implementation MCStudyCate (KC)
-(void)cate_instanceMethod1{
NSLog(@"%s", __func__);
}
-(void)cate_instanceMethod2{
NSLog(@"%s", __func__);
}
+(void)cate_classMethod{
NSLog(@"%s", __func__);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
MCStudyCate *studyCate = [MCStudyCate alloc];
NSLog(@"%p",studyCate);
}
return 0;
}
使用clang
工具將.m
文件轉化為.cpp
,其對應命令行為$ clang -rewrite-objc main.m -o main.cpp
2、在.cpp
文件中有_category_t
具體實現(xiàn)睦疫,名字解析
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;
};
并且會拿著這個結構體去實例化具體分類的對象皆辽,其每個字段的解釋具體含義如下
-
name
:分類的名稱透绩; -
cls
:分類的所屬類 -
instance_methods
:分類中添加的實例方法 -
class_methods
:分類中添加的類方法 -
protocols
:分類中添加的協(xié)議列表 -
properties
:分類中添加的屬性列表
3、接下分類實現(xiàn)的具體結構
其中的實例方法和類方法我也明確標出棋弥,發(fā)現(xiàn)
- 3.1 分類的屬性不會自動實現(xiàn)
setter/getter
方法核偿,但屬性列表中存在 - 3.2 從
cate_instanceMethod3
可以看出,分類中申明但未實現(xiàn)的方法不存在于方法列表中
二顽染、分類的加載
1漾岳、分類綁定到類當中
我們知道objc源碼中初始化函數(shù)_objc_init
,代碼文件存在于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();
runtime_init();
exception_init();
cache_init();
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
而在_dyld_objc_notify_register
中map_images
存在如下調用邏輯,大家可以根據源碼查看:
map_images -> map_images_nolock -> _read_images -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories
而 attachCategories
里面就是分類給主類添加屬性粉寞、方法尼荆、協(xié)議的具體實現(xiàn)
其實這里是根據主類
+load
非懶加載去分析的,不過在一般的懶加載情況下其后面的調用邏輯realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories
殊途同歸
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
/*
* Only a few classes have more than 64 categories during launch.
* This uses a little stack, and avoids malloc.
*
* Categories must be added in the proper order, which is back
* to front. To do that with the chunking, we iterate cats_list
* from front to back, build up the local buffers backwards,
* and call attachLists on the chunks. attachLists prepends the
* lists, so the final result is in the expected order.
*/
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
auto rwe = cls->data()->extAllocIfNeeded();
// mlists -> 二維數(shù)組
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
我們去看看方法的添加過程methods.attachLists
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 {
// 算法 list + list - 分類
// list + newlist
// array
// newlist list
// 1 list -> many lists 1 + 3
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount; // 容量 和
setArray((array_t *)malloc(array_t::byteSize(newCount))); // 創(chuàng)建一個數(shù)組
array()->count = newCount; // 4
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
我們在這里我可以發(fā)現(xiàn)
- 1.1唧垦、 category方法的添加不是覆蓋捅儒,而是將老方法平移到數(shù)組的后方,將新方法插入到前面
- 1.2振亮、category方法越后加入巧还,就會排到前面去,而方法的查找是順序的坊秸,所以后加入的方法先找到.
2麸祷、分類加載到內存
經過上面的分析我們已經知道分類中的方法、屬性褒搔、協(xié)議如何綁定到類阶牍,但我們還不知道分類是如何加載到內存的,不過在上面的過程中我們知道分類需要在UnattachedCategories
才能得到站超,其實在UnattachedCategories
中除了attachToClass
還有addForClass
方法
void addForClass(locstamped_category_t lc, Class cls)
{
runtimeLock.assertLocked();
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: found category %c%s(%s)",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), lc.cat->name);
}
auto result = get().try_emplace(cls, lc);
if (!result.second) {
result.first->second.append(lc);
}
}
通過try_emplace
方法得知
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
這個創(chuàng)建一個存儲桶的結構(這是一個鍵值對形式的結構)荸恕,向里面存內容,結合上面的函數(shù)調用get().try_emplace(cls, lc)
得知死相,以cls
為key
融求,lc
為value
進行存儲。現(xiàn)在就分析到這算撮,再走下去就跟我們本來的研究方向走偏了生宛。
我們現(xiàn)在看下addForClass
方法是哪里調用的
很清晰的看到,調用
addForClass
的方法其實都在objc-runtime-new.mm
中的load_categories_nolock
內,順藤摸瓜我們很容易找到其調用邏輯為addForClass <- load_categories_nolock <- loadAllCategories <- load_images
,竟然回到了_objc_init
中向dyld
注冊的load_images
方法總結一下流程圖為