Category
是什麼category
是 Object-C
2.0 以後添加的語言特性。c++
Category
framework
的私有方法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;
struct property_list_t *_classProperties;
// Fields below this point are not always present on disk.
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
複製代碼
變量 | 註解 |
---|---|
const char *name |
類名 |
classref_t cls |
原來的類的指針(一開始爲空,編譯時期最後根據name綁定) |
struct method_list_t *instanceMethods |
分類聲明的實例方法列表 |
struct method_list_t *classMethods |
分類聲明的類方法列表 |
struct protocol_list_t *protocols |
分類遵照協議列表 |
struct property_list_t *instanceProperties |
分類聲明的實例屬性列表 |
struct property_list_t *_classProperties |
分類聲明的類屬性列表 |
method_list_t
method_list_t
是一個範型容器,裏面存放的是 method_t
結構體數組
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
bool isFixedUp() const;
void setFixedUp();
uint32_t indexOfMethod(const method_t *meth) const {
uint32_t i =
(uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
assert(i < count);
return i;
}
};
複製代碼
template <typename Element, typename List, uint32_t FlagMask>
struct entsize_list_tt {
uint32_t entsizeAndFlags;
uint32_t count;
Element first;
};
複製代碼
變量 | 註解 |
---|---|
typename Element |
元素類型 |
typename List |
用於指定容器類型 |
uint32_t FlagMask |
標記位 |
method_t
struct method_t {
SEL name;
const char *types;
MethodListIMP imp;
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
複製代碼
變量 | 註解 |
---|---|
SEL name |
方法名 |
const char *types |
方法簽名 |
MethodListIMP |
方法指針 |
Category
的加載image
這裏指的不是圖片,是Mach-O
格式的二進制文件,dyld
就是蘋果加載image
的動態加載器main
函數啓動前,系統內核會啓動dyld
把App
依賴的各類庫加載到內存,其中包括libobjc (OC和runtime)
。_objc_init
是Objcet-C runtime
的入口函數,這裏面主要功能就是讀取Mach-O
文件OC
對應的Segment sction
,並根據其中的代碼信息完成OC
的內存佈局,以及初始化runtime
相關數據結構_objc_init
分析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();
//關於線程key的綁定--好比每線程數據的析構函數
tls_init();
//運行系統的C++靜態構造函數,在dyld調用咱們的靜態構造函數以前,libc會調用_objc_init(),因此咱們必須本身作
static_init();
//無源碼,就是說objc的異常徹底纔有c++那一套
lock_init();
//初始化異常處理系統,好比註冊異常的回調函數,來監控異常
exception_init();
//僅供objc運行時使用,註冊處理程序,以便在映射、取消映射和初始化objc鏡像文件時調用
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
複製代碼
主要關注最後一句代碼安全
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
複製代碼
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
複製代碼
這句代碼註冊了dyld
關於加載images
的回調,有如下三個事件:bash
image
映射到內存時image
被init時image
被移時image
映射到內存時當image
被dyld
加載到內存後會調用回調_dyld_objc_notify_mapped
數據結構
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
實際上調用了map_images_nolock
方法。app
進入map_images_nolock
內部,這個方法實現至關長,咱們只須要關注其內部調用的_read_images
方法。函數
_read_images
的做用就是讀取Segment sction
來初始化。佈局
_read_images
中,會讀取類的各類信息。其中: category_t **catlist = _getObjc2CategoryList(hi, &count);
就是讀取分類的信息了。ui
在讀取到分類信息之後,會調用 addUnattachedCategoryForClass(cat, cls, hi);
這把當前分類加載到類當中。this
addUnattachedCategoryForClass
方法 的最後會調用NXMapInsert(cats, cls, list);
這是在作底層數據結構的映射,咱們能夠簡單理解爲創建了cats(分類結構體)
和cls(類)
的關聯。
在創建了關聯之後,就須要把分類中的方法和屬性一一添加到類當中。remethodizeClass(cls->ISA());
這句對當前類進行重排列。在 remethodizeClass
內部會調用attachCategories(cls, cats, true /*flush caches*/);
attachCategories
會從新聲明方法列表,協議列表,屬性列表的內存空間並把方法、協議、屬性添加到當前類。
//在 attachCategories 方法中
rw->properties.attachLists(proplists, propcount);
free(proplists);
複製代碼
//在 attachCategories 方法中
rw->protocols.attachLists(protolists, protocount);
free(protolists);
複製代碼
//在 attachCategories 方法中
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
複製代碼
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
prepareMethodLists
方法裏調用了fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
fixupMethodList
方法是準備好須要添加的方法列表,主要作了如下工做:
mlist
裏的方法都填充當前類的類名mlist
裏的方法根據內存地址排序attachLists
方法把方法添加到原來的類的方法列表裏:
改變原來方法列表的大小,主要經過memmove
方法、memcpy
方法 實現,這兩個方法都用於內存拷貝。
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
複製代碼
void *memcpy(void *__dst, const void *__src, size_t __n);
void *memmove(void *__dst, const void *__src, size_t __len);
複製代碼
memcpy
方法是把當前src
指向的位置拷貝len
的長度放到 dst
的位置
memmove
方法在當前src
指向的位置加上n
的長度與 dst
的位置不重疊的時候和memcpy
方法一致,當發生內存重疊的時候memmove
可以保證源串在被覆蓋以前將重疊區域的字節拷貝到目標區域中。
當src
位置在dst
位置後面,memmove
和memcpy
都能很好的實現。
當src
位置在dst位
位置前面,也就內存拷貝重疊的時候memcpy
可能致使原數據改變而失敗,使用memmove
更加安全。
咱們能夠看到,分類添加方法實際上是發生了內存移動。因此類原有的方法即便在分類中從新實現,也只是被覆蓋而不會消失,還能夠經過訪問方法列表調用。
方法調用實際上是遍歷方法列表找到合適的方法而後調用,在調用過程會先使用二分查找。若是先找到後面有合適的方法,並不會馬上返回該方法IMP指針
,而是向前查找是否有同名方法,若是有就返回前面方法的IMP指針
不然返回後面的。因此會確保前面的方法先被調用。
image
被init時_dyld_objc_notify_register(&map_images, load_images, unmap_image);
複製代碼
void _dyld_objc_notify_register(_dyld_objc_notify_mapped mapped,
_dyld_objc_notify_init init,
_dyld_objc_notify_unmapped unmapped);
複製代碼
二進制文件初始化完成後會調用loadImages
load
方法調用規則load
方法必定在其父類的load
方法調用後調用load
方法必定在當前類load
方法調用後調用load
方法調用順序和編譯順序有關loadImages
loadImages
裏會調用 prepare_load_methods((const headerType *)mh);
loadImages
裏會調用 call_load_methods();
prepare_load_methods((const headerType *)mh);
Mach-O
文件加載類的列表,遍歷調整當前類的順序void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
//從 Macho 文件加載類的列表
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
//數組:[<cls,method>,<cls,method>,<cls,method>] 有順序
schedule_class_load(remapClass(classlist[i]));
}
//針對分類的操做!
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
複製代碼
schedule_class_load
方法會基於當前類的指針進行遞歸調用。從當前類開始找父類直到NSObject
爲止,而後開始一級一級向下調用load
方法。
這個遞歸調用就是爲了保證當前類的load
方法必定在其父類的load
方法調用後調用。
//遞歸調用
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
// Ensure superclass-first ordering
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
複製代碼
add_class_to_loadable_list
方法就是分配內存空間並把當前的類添加到一個全局的容器loadable_classes
之中。添加順序也是NSObject
-> ...
-> superclass
-> class
。添加完成後咱們就能夠獲得一個當前類類的全局容器,裏面存放了當前class
以及method
。//分配空間
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes, loadable_classes_allocated * sizeof(struct loadable_class));
}
//添加到全局容器
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
複製代碼
處理完當前類之後經過add_category_to_loadable_list
方法對分類作相同的處理,獲得loadable_categories
這個裝有全部分類的容器。Mach-O
文件中哪一個分 類在前面,哪一個分類就會被先調用
call_load_methods();
do {
// 1. Repeatedly call class +loads until there aren't any more while (loadable_classes_used > 0) { //先調用類的 load 方法 call_class_loads(); } // 2. Call category +loads ONCE more_categories = call_category_loads(); // 3. Run more +loads if there are classes OR more untried categories } while (loadable_classes_used > 0 || more_categories); 複製代碼
load
方法 (loadable_classes容器
)load
方法 (loadable_categories容器
)(*load_method)(cls, SEL_load);
拿到load
方法的指針而後發送一個消息objc_init
_dyld_objc_notify_register
:註冊回調
map_images
:映射
map_images_nolock
_read_images
:讀取image
addUnattachedCategoryForClass
:添加category
到class
remethodizeClass
:從新分配類的內存
attachCategories
:添加操做
prepareMethodLists
:去重、綁定類名、排序attachLists
:改變列表大小 並添加元素
memmove
:內存移動拷貝memcpy
:內存拷貝load_images
:加載
prepare_load_methods
:準備加載
schedule_class_load
:遞歸調用load
add_class_to_loadable_list
:獲取全局class
容器並調用load
loadable_classes
:全局容器_getObjc2NonlazyCategoryList
:獲取category
add_category_to_loadable_list
:獲取全局category
容器並調用load
loadable_categories
:全局容器