底层结构
新建一个类Person
,分类Person+test
,用之前学过的命令
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc Person+test.m -o person+test.c++
当程序编译的时候.会生成这样一个_category_t
结构体
struct _category_t {
const char *name;//类名
struct _class_t *cls;//0
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;//属性列表
};
extern "C" __declspec(dllimport) struct objc_cache _objc_empty_cache;
#pragma warning(disable:4273)
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"test", "v16@0:8", (void *)_I_Person_test_test}}
};
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_Person_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"age","Ti,N"}}
};
extern "C" __declspec(dllimport) struct _class_t OBJC_CLASS_$_Person;
//赋值给_category_t
static struct _category_t _OBJC_$_CATEGORY_Person_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"Person",
0, // &OBJC_CLASS_$_Person,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_test,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Person_$_test,
};
static void OBJC_CATEGORY_SETUP_$_Person_$_test(void ) {
_OBJC_$_CATEGORY_Person_$_test.cls = &OBJC_CLASS_$_Person;
}
源码分析
objc-os.mm
---->_objc_init
---->map_image
---->map_images_nolock
--->_read_images
// Discover 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) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
这块核心方法remethodizeClass
(重组方法),这里参数是class
ormeta-class
.最终来到attachCategories
attachCategories(cls, cats, true /*flush caches*/);
核心实现
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_t,,method_t]]
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);
}
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;
//array()->lists 原来的方法列表 往后移动addedCount
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//addedLists填充到前面
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
- 通过
runtime
把 所有category
的类方法,属性,协议各自合并为一个大数组. - 将合并后的分类数据,通过
attachLists
方法插入到类原来的数据的前面.(所以同样的方法会优先调用分类,并不是覆盖)
总结: category的实现原理.
- category编译过后的底层结构是category_t,里面包含实例方法列表,类方法列表,协议列表,属性列表.通过runtime的方式,合并到类信息(类对象,元类对象)中.
Load 方法
查看源码
**********************************************************************/
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;
}
这里可以看出是先调用类的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);
}
这里是根据load
方法的地址直接调用(*load_method)(cls, SEL_load);
并不经过objc_msgsend
;
如果是继承关系,load方法的调用顺序如何?
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
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);
}
总结:
- 每个类,分类的
load
方法在程序运行过程中只调用一次. - 先调用类的
load
方法,然后根据编译顺序调用分类的load
方法(先编译先调用) - 调用子类的
load
方法前会调用父类的load
方法.
initialize
- 类第一次接收到消息的时候调用,走的是objc_msgsend
- 会先调用父类的
initialize
,再调用子类的initialize
. - 如果分类实现该方法,会优先调用.
objc_msgSend
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);
}
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
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);
}
// Try to atomically set CLS_INITIALIZING.
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
}
}
- class_initialize. Send the '+initialize' message on demand to any
- uninitialized class. Force initialization of superclasses first.
Load 和 initialize 区别?
- load是直接找到该方法地址调用的,而initialize是通过objc_msgSend,在第一次接受消息时候调用.
- 父类的initialize可能会被调用多次.
- 分类的initialize被调用的级别高.
补充:关联对象
分类添加属性
在分类中添加
@interface Person (test)
@property (nonatomic,assign) int age;
@end
只会生成set方法.
分类里面不能直接添加成员变量.但是可以通过runtime间接实现.
const void *nameKey = &nameKey;//存自己的地址
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, nameKey, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, nameKey);
}
other
//static const void *nameKey = &nameKey;//存自己的地址 static 防止外部extern
static const char nameKey2;
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, &nameKey2, name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, &nameKey2);
}
other
//@"name" 常量区 地址不变
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
return objc_getAssociatedObject(self, @"name");
}
other
- (void)setName:(NSString *)name{
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name{
//_cmd = @selector(name)
return objc_getAssociatedObject(self, _cmd);
}
通过这种方式是不会影响原先类的成员变量的,也就是说不会以runtime的方式添加到类对象的成员变量列表里面.关联的这些属性是单独存放的.具体的可以看下图: