category在编译阶段会被编译成category_t这种结构体,在运行时分类的方法、协议、属性会被合并到类和元类中。
首先我们先看下category_t的结构:
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;//属性列表
};
下面是我新建的一个分类的.m文件,该分类只有一个load方法。
//
// YYPerson+Test.m
// CategoryDemo
//
// Created by apple on 2018/8/25.
// Copyright © 2018年 liyayun. All rights reserved.
//
#import "YYPerson+Test.h"
@implementation YYPerson (Test)
+ (void)load
{
NSLog(@"YYPerson (test) load");
}
@end
然后我们查看此文件编译后的代码:
static struct _category_t _OBJC_$_CATEGORY_YYPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"YYPerson",
0, // &OBJC_CLASS_$_YYPerson,
0,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_YYPerson_$_Test,
0,
0,
};
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_YYPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"load", "v16@0:8", (void *)_C_YYPerson_Test_load}}
};
main函数是iOS app程序的入口,但其实从程序开始运行到main函数被调用之间,程序做了很多事情。系统先读取app的可执行文件,从里面获得dyld的路径去加载dyld,然后dyld初始化运行环境,开启缓存策略;加载程序相关依赖库并对这些库进行链接,最后调用每个库的初始化方法,在这一步,runtime被初始化;当所有的依赖库初始化后,轮到最后一个可执行文件初始化时,runtime会对项目中的所有类进行类结构初始化,然后调用所有的load方法;最后dyld返回main函数地址,main函数被调用。
load方法调用的时机:在程序启动,类初始化时runtime系统自动调用,且只调用一次;
load方法调用的方式:直接找到方法的地址直接调用,不是通过消息发送;
load方法调用的顺序:先调用父类的load方法,再调用子类的load方法(这里是递归调用),最后调用分类的load(分类的load方法调用的顺序跟编译文件的顺序有关,先编译先调用);
从schedule_class_load的实现中我们可以看出父类会先加入到class_loadable_list中
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);
}
下面是runtime源码中call_load_methods方法的实现:
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
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);
objc_autoreleasePoolPop(pool);
loading = NO;
}
从该方法我们可以看出runtime会先调用所有类的load方法,然后再调用所有分类的load方法;
下面是call_class_loads的实现:
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
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;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
从该方法的实现可以看出,runtime取出所有需要调用load方法的类,然后按顺序(跟类的编译顺序有关,先编译先调用)找到类对应的load方法地址去调用load方法;
同样看下call_category_loads,runtime取出所有需要调用load方法的分类,然后按顺序(跟分类的编译顺序有关,先编译先调用)找到分类对应的load方法地址去调用load方法;
static bool call_category_loads(void)
{
int i, shift;
bool new_categories_added = NO;
// Detach current loadable list.
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;
// Call all +loads for the detached list.
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()) {
if (PrintLoading) {
_objc_inform("LOAD: +[%s(%s) load]\n",
cls->nameForLogging(),
_category_getName(cat));
}
(*load_method)(cls, SEL_load);
cats[i].cat = nil;
}
}
......
}
initialize调用的方式:通过objc_msgSend进行调用;
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
initialize调用的顺序:先调用父类的initialize方法,再调用子类的initialize方法(这里是递归调用),最后调用分类的initialize(分类的initialize方法调用的顺序跟编译文件的顺序有关,先编译先调用);
void _class_initialize(Class cls)
{
assert(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->superclass;
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
......
}
initialize方法调用的时机:第一次给类发送消息的时候,只调用一次,由于initialize是通过消息发送机制实现的,所以当子类没有实现initialize方法时,父类的initialize会调用多次;分类如果实现了initialize方法,则会调用分类的initialize方法;
下面我们通过runtime源码查看category的信息是怎么合并到类中的。
// 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;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// 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--) {
auto& entry = cats->list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
rw->properties.attachLists(proplists, propcount);
free(proplists);
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
从源码的注解我们也能看出attachCategories方法会将分类的方法列表、属性列表、协议列表添加到类中并且是根据加载的顺序倒序添加,这也当分类重写方法时,类中的方法被“覆盖”的原因,因为分类的方法再列表的前面,所以优先被找到;
下面是具体添加的实现方法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 {
// 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]));
}
}
从分类的结构中,我们看到分类中是没有成员变量的,在分类中声明成员变量会报错。分类虽然可以声明属性,但声明的属性只会自动生成setter、getter方法的声明,并不会生成对应的实现。但我们可以通过runtime中关联对象的API实现访问分类中属性的效果;
关联对象有下面三个API:
/**********************************************************************
* Associative Reference Support
**********************************************************************/
//根据key取出关联对象的值
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
//设置关联对象的值
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
//删除关联对象
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
关联对象的核心对象是下面这四个:
AssociationsManager //管理关联对象全局的manager对象
AssociationsHashMap //存放对象的关联对象信息
ObjectAssociationMap //存放关联的对象信息
ObjcAssociation //存放关联对象的值和内存策略
首先来看设置关联对象的实现文件,关键步骤我都加了注释:
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager;
//从manager中取出所有关联类的信息
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {//新值存在
// break any existing association.
//根据被关联的对象,取出对象所有的关联信息
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {//该对象关联过对象
// secondary table exists
ObjectAssociationMap *refs = i->second;
//根据key,取出关联信息
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {//若存在,设置新值
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {//不存在,新建一个ObjcAssociation对象,存入ObjectAssociationMap中
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {//该对象未关联过对象,新建一个ObjectAssociationMap对象,存入AssociationsHashMap对象中
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {//新值不存在
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);//新值为空,删除对应的ObjectAssociationMap对象
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
接着看关联对象取值的实现文件,也是一步步取值最终取到ObjcAssociation对象,将值赋值给value并返回value:
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
最后看下删除关联对象的实现文件:
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
//如果associations.size为0代表没有关联过对象
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {//如果该对象关联过对象
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
网友评论