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
:全局容器