Runtime做为Objective-C的运行时的核心一直是iOS开发者必需要学习了解的,从事iOS开发也有一段时间,以前都是断断续续看Runtime的源码或者是经过别人的博客思路来学习了解Runtime。从今年三月份开始系统的研读了Runtime的源码、发现大体和以前了解的相同(网上各路大神太多了,摊手脸!),恰好最近项目不忙,因此决定写几篇博客记录下这段经历,LET'S GO。git
经过翻码发现苹果在库初始化以前由dyld程序注册了三个通知程序,来实现整个RunTime系统的组建、+load的调用、以及资源释放。github
_objc_init源码实现以下:swift
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;
//Alex注释: 读取Runtime相关的环境变量
environ_init();
tls_init();
static_init();
lock_init();
//Alex注释: 初始化libobjc异常处理系统
exception_init();
/* Alex注释: 重要的来了,注册通知程序:这里经过名字咱们能够猜想出苹果经过dyld注册了
* 三个程序分别是map_images、load_images、unmap_image经过名字能够大概猜想出他们
* 的做用、接下来让咱们来揭开这层面纱。
* ***************这里比较遗憾的是没有找到_dyld_objc_notify_register实现的源码
* ***************若是有大神知道欢迎评论指出
*/
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
复制代码
dyld源码 感兴趣的能够去了解。数组
苹果对于map_images的注释以下: Process the given images which are being mapped in by dyld. Calls ABI-agnostic code after taking ABI-specific locks. Google翻译以后大体意思是处理由dyld映射的给定images,获取ABI锁并调用与ABI无关的代码。 images:网上不少翻译为镜像,可是我看源码则更像是资源文件
解析。缓存
map_images的源码实现以下:安全
void map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
//Alex注释: 开启Runtime锁
mutex_locker_t lock(runtimeLock);
//Alex注释: 将具体任务交由map_images_nolock执行
return map_images_nolock(count, paths, mhdrs);
}
复制代码
map_images将具体的处理交由map_images_nolock方法执行,map_images_nolock实现大概150行代码,其主要作了下面这些事情:bash
map_images_nolock源码(有精简)实现以下:app
void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
static bool firstTime = YES;
//Alex注释: 初始化header_info结构体数组
header_info *hList[mhCount];
//Alex注释: 用于记录hList的大小
uint32_t hCount;
//Alex注释: 用于记录方法和消息的个数
size_t selrefCount = 0;
if (firstTime) {
//Alex注释: 获取共享缓存的内存区域
preopt_init();
}
hCount = 0;
int totalClasses = 0;
int unoptimizedTotalClasses = 0;
{
uint32_t i = mhCount;
while (i--) {
const headerType *mhdr = (const headerType *)mhdrs[i];
/*Alex注释:
* 将mhdr 转化为 header_info 添加到 FirstHeader 链表中;若是添加成功,addHeader 返回 header_info 结构体 else NULL。
*/
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) {
continue;
}
if (mhdr->filetype == MH_EXECUTE) {
/*Alex注释:
* 获取 header_info中的sel数量,这里区分了__OBJC2__和其余版本、是由于OBJC
* 2以后提出了消息概念、将以前的SEL变为了message_ref_t { IMP imp; SEL
* sel;};的结构体封装,而这正是后来黑魔法实现的基本原理。
*/
#if __OBJC2__
size_t count;
_getObjc2SelectorRefs(hi, &count);
selrefCount += count;
_getObjc2MessageRefs(hi, &count);
selrefCount += count;
#else
_getObjcSelectorRefs(hi, &selrefCount);
#endif
}
hList[hCount++] = hi;
}
}
if (firstTime) {
/*Alex注释:
* 初始化全局的namedSelectors(NXMapTable类型)变量,并注册必要的的方法如 load、initialize、retain、release、autorelease、retainCount等...
*/
sel_init(selrefCount);
/*Alex注释:
* 初始化自动释放池AutoreleasePool(AutoreleasePoolPage)、全局SideTableBuf变量(StripedMap<SideTable>)
* SideTableBuf是Runtime的核心、
关于SideTableBuf本人的其余博客有介绍欢迎翻阅。
*/
arr_init();
}
if (hCount > 0) {
/*Alex注释:
* 通过以上处理将mach_header结构体数据转化为了header_info结构体数据,接着苹果将header_info的处理转交给了_read_images方法去实现。
*/
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
firstTime = NO;
}
复制代码
苹果对于_read_images的注释以下: Perform initial processing of the headers in the linked list beginning with headerList. Called by: map_images_nolock Google翻译以后大意为: 对连接中的header_info列表执行初始化处理,由map_images_nolock调用ide
_read_images主要作了以下这些事情:布局
接下来经过源码逐行来了解苹果是如何处理header_info列表的,
_read_images(有精简)源码实现以下:
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
header_info *hi;
uint32_t hIndex;
size_t count;
size_t i;
Class *resolvedFutureClasses = nil;
size_t resolvedFutureClassCount = 0;
static bool doneOnce;
runtimeLock.assertLocked();
//Alex注释:首次加载初始化
if (!doneOnce) {
doneOnce = YES;
//Alex注释:ISA适配
#if SUPPORT_NONPOINTER_ISA
# if SUPPORT_INDEXED_ISA
//Alex注释: SwiftVersion3以前的版本不支持NonpointerIsa
for (EACH_HEADER) {
if (hi->info()->containsSwift() &&
hi->info()->swiftVersion() < objc_image_info::SwiftVersion3)
{
DisableNonpointerIsa = true;
break;
}
}
# endif
# if TARGET_OS_OSX
//Alex注释: OS X 10.11以前的版本不支持NonpointerIsa
if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_11) {
DisableNonpointerIsa = true;
}
//Alex注释: 有__DATA,__objc_rawisa段的不支持NonpointerIsa
for (EACH_HEADER) {
if (hi->mhdr()->filetype != MH_EXECUTE) continue;
unsigned long size;
if (getsectiondata(hi->mhdr(), "__DATA", "__objc_rawisa", &size)) {
DisableNonpointerIsa = true;
}
break;
}
# endif
#endif
//Alex注释: 是否禁用TaggedPointers
if (DisableTaggedPointers) {
disableTaggedPointers();
}
//Alex注释: 初始化混淆器(随机产生),保护代码安全。
initializeTaggedPointerObfuscator();
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
/*Alex注释:
* gdb_objc_realized_classes 保存不在动态共享缓存(dyld shared cache)中的 命名类 -> hash表
* allocatedClasses 缓存已经被objc_allocateClassPair方法初始化的类和元类 -> hash表
*/
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
}
for (EACH_HEADER) {
//Alex注释: 加载每一个header_info结构体的Classref_t结构体列表
classref_t *classlist = _getObjc2ClassList(hi, &count);
if (! mustReadClasses(hi)) {
continue;
}
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->isPreoptimized();
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[i];
/*Alex注释:
* readClass主要工做以下:
* 一、从future_named_class_map(NXMapTable)
* 全局对象中查找未实现的newCls,取出该Class的(class_rw_t)data数据
* (rwNew),将cls数据拷贝到newCls,将拷贝后的newCls的
* (class_rw_t)data强转为class_ro_t并赋值到rwNew的ro成员变量,
* 最后将rwNew赋值给newCls的(class_rw_t)data
* 二、将newCls添加到全局变量(NXMapTable)gdb_objc_realized_classes的
* MapTable中,gdb_objc_realized_classes保存不在动态共享缓存中的类
* 不管是否实现
* 三、将newCls添加到全局变量(NXMapTable)allocatedClasses的
* MapTable中,allocatedClasses表保存已经Allocated的类
*/
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
if (newCls != cls && newCls) {
//Alex注释: newCls添加进数组
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
*******************************须要仔细阅读源码
//Alex注释: noClassesRemapped建立remapped_class_map静态变量(NXMapTable),
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
//Alex注释: 返回实时的类指针,该指针可能指向已经从新分配内存的类结构
remapClassRef(&classrefs[i]);
}
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[i]);
}
}
}
//Alex注释: 将SEL保存到全局的namedSelectors(NXMapTable表)中
static size_t UnfixedSelectors;
{
mutex_locker_t lock(selLock);
for (EACH_HEADER) {
if (hi->isPreoptimized()) continue;
bool isBundle = hi->isBundle();
SEL *sels = _getObjc2SelectorRefs(hi, &count);
UnfixedSelectors += count;
for (i = 0; i < count; i++) {
const char *name = sel_cname(sels[i]);
sels[i] = sel_registerNameNoLock(name, isBundle);
}
}
}
//Alex注释: 加载protocols,并保存到静态变量(NXMapTable)protocol_map中
for (EACH_HEADER) {
extern objc_class OBJC_CLASS_$_Protocol;
Class cls = (Class)&OBJC_CLASS_$_Protocol;
assert(cls);
NXMapTable *protocol_map = protocols();
bool isPreoptimized = hi->isPreoptimized();
bool isBundle = hi->isBundle();
protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
//Alex注释: 加载ProtocolRefs,并保存到静态变量(NXMapTable)protocol_map中
for (EACH_HEADER) {
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[i]);
}
}
//Alex注释: 加载非惰性类,用于+load和静态的instance
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
#if TARGET_OS_SIMULATOR
if (cls->cache._buckets == (void*)&_objc_empty_cache &&
(cls->cache._mask || cls->cache._occupied))
{
cls->cache._mask = 0;
cls->cache._occupied = 0;
}
if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache &&
(cls->ISA()->cache._mask || cls->ISA()->cache._occupied))
{
cls->ISA()->cache._mask = 0;
cls->ISA()->cache._occupied = 0;
}
#endif
*******************************敲重点:::须要仔细阅读源码
//Alex注释:将cls添加到allocatedClasses全局map中
addClassTableEntry(cls);
/* Alex注释: 加载非惰性类、执行初始化
* 递归布局class、superClass、metaClass的树形结构
* reconcileInstanceVariables方法协调实例变量的偏移量,内存布局
*/
realizeClass(cls);
}
}
//Alex注释:实现新的未解决的将来类?????????
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
realizeClass(resolvedFutureClasses[i]);
resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
}
free(resolvedFutureClasses);
}
//Alex注释:加载类别(categories)列表
for (EACH_HEADER) {
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
catlist[i] = nil;
continue;
}
bool classExists = NO;
//Alex注释:判断是否有类的实例方法、协议、实例属性
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
//Alex注释:category存放在静态的MapTable(静态变量category_map)中,
* 苹果是将category和header_info封装成locstamped_category_t结构体,
* 以后将(locstamped_category_t){category, header_info} 添加到全局
* 变量category_map中category->cls映射的locstamped_category_t结构
* 体数组中
*/
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
//Alex注释: 若是类已经初始化将category的方法、属性、协议追加到类的
* class_rw_t结构体对应的methods、properties、protocols成员
* 变量中
* 添加到class_rw_t结构体的操做是在attachCategories方法中实现的。
*/
remethodizeClass(cls);
classExists = YES;
}
}
//Alex注释:这里处理的是元类的方法、协议、属性
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
}
}
}
//Alex注释:调试不易碎的Ivars
if (DebugNonFragileIvars) {
//Alex注释:非惰性的实现全部已知的未实现的类
realizeAllClasses();
}
//Alex注释:打印预选的方法列表、优化方法列表、所有的Classes、优化的Classes, DEBUG
if (PrintPreopt) {
static unsigned int PreoptTotalMethodLists;
static unsigned int PreoptOptimizedMethodLists;
static unsigned int PreoptTotalClasses;
static unsigned int PreoptOptimizedClasses;
for (EACH_HEADER) {
classref_t *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
PreoptTotalClasses++;
if (hi->isPreoptimized()) {
PreoptOptimizedClasses++;
}
const method_list_t *mlist;
if ((mlist = ((class_ro_t *)cls->data())->baseMethods())) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
if ((mlist=((class_ro_t *)cls->ISA()->data())->baseMethods())) {
PreoptTotalMethodLists++;
if (mlist->isFixedUp()) {
PreoptOptimizedMethodLists++;
}
}
}
}
}
#undef EACH_HEADER
}
复制代码
通过以上处理类的装载已经完成、而后执行load_images方法、处理已经被加载的类的+load方法。
苹果对于load_images的注释以下: Process +load in the given images which are being mapped in by dyld. Google翻译后大意是:处理在dyld被mapped的images的+load方法。
load_images的代码并很少,主要是对load_images执行递归锁操做,以后调用prepare_load_methods装载+load方法,最后经过call_load_methods调用Class和Category的+load方法。
void load_images(const char *path __unused, const struct mach_header *mh)
{
if (!hasLoadMethods((const headerType *)mh)) return;
//Alex注释: loadMethod加锁
recursive_mutex_locker_t lock(loadMethodLock);
{
mutex_locker_t lock2(runtimeLock);
//Alex注释: 装载+load方法
prepare_load_methods((const headerType *)mh);
}
//Alex注释: 调用+load方法
call_load_methods();
}
复制代码
咱们知道category和class均可以实现+load方法,下面经过源码来细究Category和Class的+load方法是如何实现的。
prepare_load_methods方法实现比较简单、主要是对mhdr中的Class和Category进行遍历、最后将+load的方法处理分发到了schedule_class_load和add_category_to_loadable_list方法、分别用于装载Class的+load和Category的+load方法。
prepare_load_methods源码实现以下:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
/*Alex注释:
* 加载类的 +load方法,并将Class的load方法和类保存到全局的
* (struct loadable_class *)loadable_classes 结构体数组中
*/
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;
realizeClass(cls);
assert(cls->ISA()->isRealized());
/*Alex注释:
* 加载category的 +load方法, 并将category的load方法和category存储到
* (struct loadable_category *)loadable_categories 结构体数组中
*/
add_category_to_loadable_list(cat);
}
}
复制代码
关于类的+load方法的装载、经过源码能够看出苹果是采用递归的方式从父类到子类逐个遍历,最后将具体的装载处理交由add_class_to_loadable_list方法执行。
add_class_to_loadable_list实现就更加简明了,大体分为下面几步:
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized());
if (cls->data()->flags & RW_LOADED) return;
schedule_class_load(cls->superclass);
add_class_to_loadable_list(cls);
<!-- add_class_to_loadable_list 实现 {
IMP method;
loadMethodLock.assertLocked();
method = cls->getLoadMethod();
if (!method) return;
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;
loadable_classes_used++;
} -->
cls->setInfo(RW_LOADED);
}
复制代码
关于Category的+load方法的装载,其实和类的装载大体相同。只是这里在调用add_category_to_loadable_list方法装载时、若是Class没有realize的话会对Class进行realize,还要注意的是Category的+load方法是被单独保存在一个全局的struct loadable_category *loadable_categories 静态变量中的,他和类的+load方法是分开存放的。
+load方法装载以后苹果经过call_load_methods方法来调用Class和Category的+load方法。Class和Category的+load调用顺序在这里也能够获得想要的答案。
call_load_methods的实现源码以下:
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
//Alex注释: 循环调用Class的+load方法
while (loadable_classes_used > 0) {
call_class_loads();
}
//Alex注释:调用category的+load方法,仅调用一次
more_categories = call_category_loads();
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
复制代码
类的+load的调用源码:
static void call_class_loads(void)
{
int i;
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
//Alex注释:遍历调用Class的+load方法
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
//Alex注释:调用+load方法
(*load_method)(cls, SEL_load);
}
if (classes) free(classes);
}
复制代码
Category的+load方法的调用源码:
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
struct loadable_category *cats = loadable_categories;
int used = loadable_categories_used;
int allocated = loadable_categories_allocated;
loadable_categories = nil;
loadable_categories_allocated = 0;
loadable_categories_used = 0;
//Alex注释: 循环调用Category的+load方法
for (i = 0; i < used; i++) {
Category cat = cats[i].cat;
load_method_t load_method = (load_method_t)cats[i].method;
Class cls;
if (!cat) continue;
cls = _category_getClass(cat);
if (cls && cls->isLoadable()) {
//Alex注释:调用+load方法
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
shift = 0;
for (i = 0; i < used; i++) {
if (cats[i].cat) {
cats[i-shift] = cats[i];
} else {
shift++;
}
}
used -= shift;
new_categories_added = (loadable_categories_used > 0);
for (i = 0; i < loadable_categories_used; i++) {
if (used == allocated) {
allocated = allocated*2 + 16;
cats = (struct loadable_category *)
realloc(cats, allocated *
sizeof(struct loadable_category));
}
cats[used++] = loadable_categories[i];
}
if (loadable_categories) free(loadable_categories);
if (used) {
loadable_categories = cats;
loadable_categories_used = used;
loadable_categories_allocated = allocated;
} else {
if (cats) free(cats);
loadable_categories = nil;
loadable_categories_used = 0;
loadable_categories_allocated = 0;
}
return new_categories_added;
}
复制代码
unmap_image对应map_images有map操做就有对应的unmap。 苹果对于unmap的注释以下: Process the given image which is about to be unmapped by dyld. Google翻译大意为:处理由dyld取消映射的image。
unmap_image的源码以下:
void unmap_image(const char *path __unused, const struct mach_header *mh)
{
recursive_mutex_locker_t lock(loadMethodLock);
mutex_locker_t lock2(runtimeLock);
//Alex注释: 加锁将具体的任务转交unmap_image_nolock方法
unmap_image_nolock(mh);
}
复制代码
从全局的header_info结构体链表中遍历查找Runtime的header_info结构体,调用_unload_image处理header_info结构体、最后将其从链表中移除。
void unmap_image_nolock(const struct mach_header *mh)
{
header_info *hi;
for (hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
if (hi->mhdr() == (const headerType *)mh) {
break;
}
}
if (!hi) return;
_unload_image(hi);
removeHeader(hi);
free(hi);
}
复制代码
void _unload_image(header_info *hi)
{
size_t count, i;
loadMethodLock.assertLocked();
runtimeLock.assertLocked();
//Alex注释: 中止附加Category和调用Category的+load方法
category_t **catlist = _getObjc2CategoryList(hi, &count);
for (i = 0; i < count; i++) {
category_t *cat = catlist[i];
if (!cat) continue;
Class cls = remapClass(cat->cls);
assert(cls);
removeUnattachedCategoryForClass(cat, cls);
remove_category_from_loadable_list(cat);
}
NXHashTable *classes = NXCreateHashTable(NXPtrPrototype, 0, nil);
classref_t *classlist;
//Alex注释: 从header_info结构体中加载已经remap的Classs
classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (cls) NXHashInsert(classes, cls);
}
classlist = _getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (cls) NXHashInsert(classes, cls);
}
NXHashState hs;
Class cls;
hs = NXInitHashState(classes);
while (NXNextHashState(classes, &hs, (void**)&cls)) {
//Alex注释: 将loadable表中的类移除
remove_class_from_loadable_list(cls);
//Alex注释: 将类、元类从已经初始化的全局静态表中移除
detach_class(cls->ISA(), YES);
detach_class(cls, NO);
}
hs = NXInitHashState(classes);
while (NXNextHashState(classes, &hs, (void**)&cls)) {
//Alex注释: 释放类和元类指针
free_class(cls->ISA());
free_class(cls);
}
//Alex注释: 释放hashTable
NXFreeHashTable(classes);
}
复制代码
此次源码阅读学习到了类是如何从共享缓存、读取、转化、以及初始化这样一个装载过程,学到了类、方法、协议等在内存中存储的形式以及类、元类、超类之间的关系。在load_images模块也证明了以前关于category和class中+load方法的认知。 将来可期,加油少年!!